From 490b93e82c7239d8ae6b13907fdfb2231998aa03 Mon Sep 17 00:00:00 2001 From: silverqx Date: Wed, 31 Aug 2022 19:19:13 +0200 Subject: [PATCH] fixed handling of timestamp columns on Pivots 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. --- include/orm/tiny/model.hpp | 19 ++- include/orm/tiny/relations/basepivot.hpp | 123 ++++++++++++++---- include/orm/tiny/relations/belongstomany.hpp | 19 ++- .../concerns/interactswithpivottable.hpp | 32 ++++- 4 files changed, 152 insertions(+), 41 deletions(-) diff --git a/include/orm/tiny/model.hpp b/include/orm/tiny/model.hpp index f7ba3a8d7..477394958 100644 --- a/include/orm/tiny/model.hpp +++ b/include/orm/tiny/model.hpp @@ -79,6 +79,11 @@ namespace Orm::Tiny /*! The "type" of the primary key ID. */ using KeyType = quint64; + /*! The base model type. */ + using BaseModelType = Model; + /*! The Derived model type. */ + using DerivedType = Derived; + /* Constructors */ /*! Create a new TinORM model instance. */ Model(); @@ -205,7 +210,9 @@ namespace Orm::Tiny template PivotType newPivot(const Parent &parent, const QVector &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 @@ -993,19 +1000,23 @@ namespace Orm::Tiny }); } + // NOTE api different, passing down a pivot timestamps data silverqx template template PivotType Model::newPivot( const Parent &parent, const QVector &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) return PivotType::template fromAttributes( - parent, attributes, table, exists_); + parent, attributes, table, exists_, withTimestamps, + createdAt, updatedAt); else return PivotType::template fromRawAttributes( - parent, attributes, table, exists_); + parent, attributes, table, exists_, withTimestamps, + createdAt, updatedAt); } /* Static cast this to a child's instance type (CRTP) */ diff --git a/include/orm/tiny/relations/basepivot.hpp b/include/orm/tiny/relations/basepivot.hpp index 675e76042..97c42a991 100644 --- a/include/orm/tiny/relations/basepivot.hpp +++ b/include/orm/tiny/relations/basepivot.hpp @@ -37,15 +37,21 @@ namespace Orm::Tiny::Relations template static PivotModel fromAttributes(const Parent &parent, const QVector &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 /*! Create a new pivot model from raw values returned from a query. */ static PivotModel fromRawAttributes(const Parent &parent, const QVector &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 &attributes) const; + static bool hasTimestampAttributes(const QVector &attributes); /*! Determine if the pivot model or given attributes has timestamp attributes. */ bool hasTimestampAttributes() const; @@ -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 -// 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 */ @@ -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 + 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 template PivotModel BasePivot::fromAttributes( const Parent &parent, const QVector &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(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 @@ -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; - instance.exists = exists; return instance; } + // NOTE api different, passing down a pivot timestamps data silverqx template template PivotModel BasePivot::fromRawAttributes( const Parent &parent, const QVector &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 @@ -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); @@ -166,15 +193,22 @@ namespace Orm::Tiny::Relations template bool BasePivot::hasTimestampAttributes( - const QVector &attributes) const + const QVector &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 @@ -259,6 +293,12 @@ namespace Orm::Tiny::Relations return this->model(); } + template + constexpr bool BasePivot::isCustomPivot() + { + return !std::is_same_v; + } + /* protected */ /* AsPivot */ @@ -340,6 +380,45 @@ namespace Orm::Tiny::Relations return mergedAttributes; } + /* AsPivot */ + + /* private */ + + template + template + void BasePivot::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(CREATED_AT) = createdAt; + + if (updatedAt != UPDATED_AT) + const_cast(UPDATED_AT) = updatedAt; + } + + // Inferre from the parent model + else { + if (const auto &parentCreatedAt = Parent::getCreatedAtColumn(); + parentCreatedAt != CREATED_AT + ) + const_cast(CREATED_AT) = parentCreatedAt; + + if (const auto &parentUpdatedAt = Parent::getUpdatedAtColumn(); + parentUpdatedAt != UPDATED_AT + ) + const_cast(UPDATED_AT) = parentUpdatedAt; + } + } + } // namespace Orm::Tiny::Relations TINYORM_END_COMMON_NAMESPACE diff --git a/include/orm/tiny/relations/belongstomany.hpp b/include/orm/tiny/relations/belongstomany.hpp index 2bf9cbdfd..e1d6ac22c 100644 --- a/include/orm/tiny/relations/belongstomany.hpp +++ b/include/orm/tiny/relations/belongstomany.hpp @@ -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. */ @@ -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 BelongsToMany & BelongsToMany::withTimestamps( @@ -651,19 +652,17 @@ namespace Orm::Tiny::Relations template const QString &BelongsToMany::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 const QString &BelongsToMany::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 diff --git a/include/orm/tiny/relations/concerns/interactswithpivottable.hpp b/include/orm/tiny/relations/concerns/interactswithpivottable.hpp index a742c84a1..413a88c72 100644 --- a/include/orm/tiny/relations/concerns/interactswithpivottable.hpp +++ b/include/orm/tiny/relations/concerns/interactswithpivottable.hpp @@ -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. */ @@ -580,7 +584,9 @@ namespace Concerns const QVector &attributes, const bool exists) const { return getRelated_().template newPivot( - getParent_(), attributes, getTable_(), exists) + getParent_(), attributes, getTable_(), exists, + relation().usesTimestamps(), + pivotCreatedAt(), pivotUpdatedAt()) .setPivotKeys(getForeignPivotKeyName_(), getRelatedPivotKeyName_()); } @@ -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_())); @@ -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_())); @@ -1167,6 +1175,20 @@ namespace Concerns return relation().touchIfTouching(); } + template + const QString & + InteractsWithPivotTable::pivotCreatedAt() const + { + return relation().m_pivotCreatedAt; + } + + template + const QString & + InteractsWithPivotTable::pivotUpdatedAt() const + { + return relation().m_pivotUpdatedAt; + } + /* Others */ template