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