Skip to content

Commit

Permalink
fixed handling of timestamp columns on Pivots
Browse files Browse the repository at this point in the history
Now, the timestamp column names on the Pivot tables can be different
than timestamp columns on the Parent model.
They can be set by the withTimestamps() method. If they are not set
by this method they still be inferred from attributes and the timestamp
column names will be inferred from the Parent model.

Also the usesTimestamps() logic was enhanced on the Pivot models,
both timestamp column names (createdAt and updatedAt) have to be set
in the attributes, to set usesTimestamps() to true.
  • Loading branch information
silverqx committed Aug 31, 2022
1 parent b47ae78 commit 490b93e
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 41 deletions.
19 changes: 15 additions & 4 deletions include/orm/tiny/model.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ namespace Orm::Tiny
/*! The "type" of the primary key ID. */
using KeyType = quint64;

/*! The base model type. */
using BaseModelType = Model<Derived, AllRelations...>;
/*! The Derived model type. */
using DerivedType = Derived;

/* Constructors */
/*! Create a new TinORM model instance. */
Model();
Expand Down Expand Up @@ -205,7 +210,9 @@ namespace Orm::Tiny
template<typename PivotType = Relations::Pivot, typename Parent>
PivotType
newPivot(const Parent &parent, const QVector<AttributeItem> &attributes,
const QString &table, bool exists) const;
const QString &table, bool exists, bool withTimestamps = false,
const QString &createdAt = Constants::CREATED_AT,
const QString &updatedAt = Constants::UPDATED_AT) const;

/* Static cast this to a child's instance type (CRTP) */
TINY_CRTP_MODEL_DECLARATIONS
Expand Down Expand Up @@ -993,19 +1000,23 @@ namespace Orm::Tiny
});
}

// NOTE api different, passing down a pivot timestamps data silverqx
template<typename Derived, AllRelationsConcept ...AllRelations>
template<typename PivotType, typename Parent>
PivotType
Model<Derived, AllRelations...>::newPivot(
const Parent &parent, const QVector<AttributeItem> &attributes,
const QString &table, const bool exists_) const
const QString &table, const bool exists_, const bool withTimestamps,
const QString &createdAt, const QString &updatedAt) const
{
if constexpr (std::is_same_v<PivotType, Relations::Pivot>)
return PivotType::template fromAttributes<Parent>(
parent, attributes, table, exists_);
parent, attributes, table, exists_, withTimestamps,
createdAt, updatedAt);
else
return PivotType::template fromRawAttributes<Parent>(
parent, attributes, table, exists_);
parent, attributes, table, exists_, withTimestamps,
createdAt, updatedAt);
}

/* Static cast this to a child's instance type (CRTP) */
Expand Down
123 changes: 101 additions & 22 deletions include/orm/tiny/relations/basepivot.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,21 @@ namespace Orm::Tiny::Relations
template<typename Parent>
static PivotModel
fromAttributes(const Parent &parent, const QVector<AttributeItem> &attributes,
const QString &table, bool exists = false);
const QString &table, bool exists = false,
bool withTimestamps = false,
const QString &createdAt = Constants::CREATED_AT,
const QString &updatedAt = Constants::UPDATED_AT);
template<typename Parent>
/*! Create a new pivot model from raw values returned from a query. */
static PivotModel
fromRawAttributes(const Parent &parent, const QVector<AttributeItem> &attributes,
const QString &table, bool exists = false);
const QString &table, bool exists = false,
bool withTimestamps = false,
const QString &createdAt = Constants::CREATED_AT,
const QString &updatedAt = Constants::UPDATED_AT);

/*! Determine if the pivot model or given attributes has timestamp attributes. */
bool hasTimestampAttributes(const QVector<AttributeItem> &attributes) const;
static bool hasTimestampAttributes(const QVector<AttributeItem> &attributes);
/*! Determine if the pivot model or given attributes has timestamp attributes. */
bool hasTimestampAttributes() const;

Expand All @@ -70,11 +76,14 @@ namespace Orm::Tiny::Relations
/*! Set the key names for the pivot model instance. */
PivotModel &setPivotKeys(const QString &foreignKey, const QString &relatedKey);

// TODO fuckup, timestamps in pivot, the solution is to set CREATED_AT and UPDATED_AT right away in the fromAttributes method when I still have access to the parent, then I won't have to save a pointer to the parent. I can still save pointer to parent, but not for obtaining this timestamp column names. old - I will solve it when I will have to use timestamps in the code, anyway may be I will not need it, because I can pass to the method right away what I will need silverqx
// TODO also don't forget unsetRelations() if pivotParent will be implemented silverqx
/*! The parent model of the relationship. */
// template<typename Parent>
// inline static const Parent *pivotParent = nullptr;
/*! Determine whether the PivotType is a custom pivot. */
constexpr static bool isCustomPivot();

/* HasTimestamps */
/*! The name of the "created at" column. */
inline static const QString CREATED_AT = Constants::CREATED_AT; // NOLINT(cppcoreguidelines-interfaces-global-init)
/*! The name of the "updated at" column. */
inline static const QString UPDATED_AT = Constants::UPDATED_AT; // NOLINT(cppcoreguidelines-interfaces-global-init)

protected:
/* AsPivot */
Expand Down Expand Up @@ -105,21 +114,39 @@ namespace Orm::Tiny::Relations
QString m_foreignKey;
/*! The name of the "other key" column. */
QString m_relatedKey;

private:
/* AsPivot */
/*! Set timestamp column names from a parent if they are not the same. */
template<typename Parent>
static void
syncTimestampsFromParent(bool withTimestamps, const QString &createdAt,
const QString &updatedAt) noexcept;
};

/* public */

/* AsPivot */

// NOTE api different, passing down a pivot timestamps data silverqx
template<typename PivotModel>
template<typename Parent>
PivotModel
BasePivot<PivotModel>::fromAttributes(
const Parent &parent, const QVector<AttributeItem> &attributes,
const QString &table, const bool exists)
const QString &table, const bool exists, const bool withTimestamps,
const QString &createdAt, const QString &updatedAt)
{
// Set timestamp column names from the parent if they are not the same
syncTimestampsFromParent<Parent>(withTimestamps, createdAt, updatedAt);

PivotModel instance;

/* I will not store a pointer to the parent model because we don't have
the reference to it, the parent argument is the reference to a copy and
this copy will be destroyed before the BTM relationship method returns. */

// Guess whether the pivot uses timestamps (u_timestamps)
instance.setUseTimestamps(instance.hasTimestampAttributes(attributes));

/* The pivot model is a "dynamic" model since we will set the tables dynamically
Expand All @@ -130,25 +157,24 @@ namespace Orm::Tiny::Relations
.forceFill(attributes)
.syncOriginal();

/* We store off the parent instance so we will access the timestamp column names
for the model, since the pivot model timestamps aren't easily configurable
from the developer's point of view. We can use the parents to get these. */
// pivotParent<Parent> = &parent;

instance.exists = exists;

return instance;
}

// NOTE api different, passing down a pivot timestamps data silverqx
template<typename PivotModel>
template<typename Parent>
PivotModel
BasePivot<PivotModel>::fromRawAttributes(
const Parent &parent, const QVector<AttributeItem> &attributes,
const QString &table, const bool exists)
const QString &table, const bool exists, const bool withTimestamps,
const QString &createdAt, const QString &updatedAt)
{
auto instance = fromAttributes(parent, {}, table, exists);
auto instance = fromAttributes(parent, {}, table, exists, withTimestamps,
createdAt, updatedAt);

// Guess whether the pivot uses timestamps (u_timestamps)
instance.setUseTimestamps(instance.hasTimestampAttributes(attributes));

// Support Default Attribute Values
Expand All @@ -157,6 +183,7 @@ namespace Orm::Tiny::Relations
rawOriginals.isEmpty()
)
instance.setRawAttributes(attributes, exists);

// Merge a new attributes from the database to the default attributes
else
instance.setRawAttributes(mergeAttributes(rawOriginals, attributes), exists);
Expand All @@ -166,15 +193,22 @@ namespace Orm::Tiny::Relations

template<typename PivotModel>
bool BasePivot<PivotModel>::hasTimestampAttributes(
const QVector<AttributeItem> &attributes) const
const QVector<AttributeItem> &attributes)
{
const auto &createdAtColumn = this->getCreatedAtColumn();
// NOTE api different, has to contain both timestamp columns silverqx
auto hasCreatedAt = false;
auto hasUpdatedAt = false;

for (const auto &attribute : attributes)
if (attribute.key == createdAtColumn)
return true;
for (const auto &attribute : attributes) {
const auto &attributeKey = attribute.key;

if (attributeKey == PivotModel::CREATED_AT)
hasCreatedAt = true;
else if (attributeKey == PivotModel::UPDATED_AT)
hasUpdatedAt = true;
}

return false;
return hasCreatedAt && hasUpdatedAt;
}

template<typename PivotModel>
Expand Down Expand Up @@ -259,6 +293,12 @@ namespace Orm::Tiny::Relations
return this->model();
}

template<typename PivotModel>
constexpr bool BasePivot<PivotModel>::isCustomPivot()
{
return !std::is_same_v<PivotModel, Relations::Pivot>;
}

/* protected */

/* AsPivot */
Expand Down Expand Up @@ -340,6 +380,45 @@ namespace Orm::Tiny::Relations
return mergedAttributes;
}

/* AsPivot */

/* private */

template<typename PivotModel>
template<typename Parent>
void BasePivot<PivotModel>::syncTimestampsFromParent(
const bool withTimestamps, const QString &createdAt,
const QString &updatedAt) noexcept
{
// NOTE api different, timestamp column names passed to the withTimestamps() are also considered silverqx
/* Pivot timestamp column names are configured/set using the withTimestamps()
method or they will be inferred from the parent model if they are not set.
The logic has to be the same as in the BelongsToMany::createdAt/updatedAt
methods! */

// Configured using the withTimestamps() method
if (withTimestamps) {
if (createdAt != CREATED_AT)
const_cast<QString &>(CREATED_AT) = createdAt;

if (updatedAt != UPDATED_AT)
const_cast<QString &>(UPDATED_AT) = updatedAt;
}

// Inferre from the parent model
else {
if (const auto &parentCreatedAt = Parent::getCreatedAtColumn();
parentCreatedAt != CREATED_AT
)
const_cast<QString &>(CREATED_AT) = parentCreatedAt;

if (const auto &parentUpdatedAt = Parent::getUpdatedAtColumn();
parentUpdatedAt != UPDATED_AT
)
const_cast<QString &>(UPDATED_AT) = parentUpdatedAt;
}
}

} // namespace Orm::Tiny::Relations

TINYORM_END_COMMON_NAMESPACE
Expand Down
19 changes: 9 additions & 10 deletions include/orm/tiny/relations/belongstomany.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ namespace Orm::Tiny::Relations
/*! Determine if the 'pivot' model uses timestamps. */
inline bool usesTimestamps() const noexcept;
/*! Specify that the pivot table has creation and update timestamps. */
BelongsToMany &withTimestamps(const QString &createdAt = "",
const QString &updatedAt = "");
BelongsToMany &withTimestamps(const QString &createdAt = Constants::CREATED_AT,
const QString &updatedAt = Constants::UPDATED_AT);
/*! Get the name of the "created at" column. */
const QString &createdAt() const;
/*! Get the name of the "updated at" column. */
Expand Down Expand Up @@ -635,6 +635,7 @@ namespace Orm::Tiny::Relations
return m_withTimestamps;
}

// NOTE api different, the createdAt and updatedAt parameters have default column values, so they are less confusing silverqx
template<class Model, class Related, class PivotType>
BelongsToMany<Model, Related, PivotType> &
BelongsToMany<Model, Related, PivotType>::withTimestamps(
Expand All @@ -651,19 +652,17 @@ namespace Orm::Tiny::Relations
template<class Model, class Related, class PivotType>
const QString &BelongsToMany<Model, Related, PivotType>::createdAt() const
{
if (m_pivotCreatedAt.isEmpty())
return this->m_parent.getCreatedAtColumn();

return m_pivotCreatedAt;
// NOTE api different, checking the m_withTimestamps instead of m_pivotCreatedAt.isEmpty() silverqx
return m_withTimestamps ? m_pivotCreatedAt
: this->m_parent.getCreatedAtColumn();
}

template<class Model, class Related, class PivotType>
const QString &BelongsToMany<Model, Related, PivotType>::updatedAt() const
{
if (m_pivotUpdatedAt.isEmpty())
return this->m_parent.getUpdatedAtColumn();

return m_pivotUpdatedAt;
// NOTE api different, checking the m_withTimestamps instead of m_pivotCreatedAt.isEmpty() silverqx
return m_withTimestamps ? m_pivotUpdatedAt
: this->m_parent.getUpdatedAtColumn();
}

template<class Model, class Related, class PivotType>
Expand Down
32 changes: 27 additions & 5 deletions include/orm/tiny/relations/concerns/interactswithpivottable.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,10 @@ namespace Concerns
inline const QString &updatedAt_() const;
/*! If we're touching the parent model, touch. */
void touchIfTouching_() const;
/*! Get the name of the "created at" column passed to the withTimestamps(). */
inline const QString &pivotCreatedAt() const;
/*! Get the name of the "updated at" column passed to the withTimestamps(). */
inline const QString &pivotUpdatedAt() const;

/* Others */
/*! Qualify the given column name by the pivot table. */
Expand Down Expand Up @@ -580,7 +584,9 @@ namespace Concerns
const QVector<AttributeItem> &attributes, const bool exists) const
{
return getRelated_().template newPivot<PivotType, Model>(
getParent_(), attributes, getTable_(), exists)
getParent_(), attributes, getTable_(), exists,
relation().usesTimestamps(),
pivotCreatedAt(), pivotUpdatedAt())

.setPivotKeys(getForeignPivotKeyName_(), getRelatedPivotKeyName_());
}
Expand Down Expand Up @@ -789,7 +795,8 @@ namespace Concerns
pivots << std::move(
PivotType::fromRawAttributes(
getParent_(), attributesFromRecord(query.record()),
getTable_(), true)
getTable_(), true, relation().usesTimestamps(),
pivotCreatedAt(), pivotUpdatedAt())

.setPivotKeys(getForeignPivotKeyName_(),
getRelatedPivotKeyName_()));
Expand All @@ -805,9 +812,10 @@ namespace Concerns
auto query = newPivotStatementForId(id)->first();

return std::move(
PivotType::fromRawAttributes(getParent_(),
attributesFromRecord(query.record()),
getTable_(), true)
PivotType::fromRawAttributes(
getParent_(), attributesFromRecord(query.record()),
getTable_(), true, relation().usesTimestamps(),
pivotCreatedAt(), pivotUpdatedAt())

.setPivotKeys(getForeignPivotKeyName_(),
getRelatedPivotKeyName_()));
Expand Down Expand Up @@ -1167,6 +1175,20 @@ namespace Concerns
return relation().touchIfTouching();
}

template<class Model, class Related, class PivotType>
const QString &
InteractsWithPivotTable<Model, Related, PivotType>::pivotCreatedAt() const
{
return relation().m_pivotCreatedAt;
}

template<class Model, class Related, class PivotType>
const QString &
InteractsWithPivotTable<Model, Related, PivotType>::pivotUpdatedAt() const
{
return relation().m_pivotUpdatedAt;
}

/* Others */

template<class Model, class Related, class PivotType>
Expand Down

0 comments on commit 490b93e

Please sign in to comment.