added ModelsCollection::fresh()

- added unit tests
This commit is contained in:
silverqx
2023-05-28 15:35:55 +02:00
parent 6d52565329
commit 8bb8e090b9
4 changed files with 554 additions and 0 deletions

View File

@@ -93,6 +93,7 @@ Furthermore, almost every method returns a new `ModelsCollection` instance, allo
[find](#method-find)
[first](#method-first)
[firstWhere](#method-first-where)
[fresh](#method-fresh)
[implode](#method-implode)
[isEmpty](#method-isempty)
[isNotEmpty](#method-isnotempty)
@@ -310,6 +311,20 @@ You may also call the `firstWhere` method with a comparison operator:
// {{"name", "Jack"}, {"age", 23}}
#### `fresh()` {#method-fresh}
The `fresh` method retrieves a fresh instance of each model in the collection from the database. In addition, any specified relationships will be eager loaded:
auto usersFresh = users.fresh();
auto usersFresh = users.fresh("comments");
auto usersFresh = users.fresh("posts:id,name");
auto usersFresh = users.fresh({"comments", "posts:id,name"});
The `relations` argument format is the same as for TinyBuilder's [`load`](relationships.mdx#lazy-eager-loading) method.
#### `implode()` {#method-implode}
The `implode` method joins attributes by the given column and the "glue" string you wish to place between the values:

View File

@@ -265,6 +265,17 @@ namespace Types
std::unique_ptr<TinyBuilder<ModelRawType>> toQuery();
/* Collection - Relations related */
/*! Reload a fresh model instance from the database for all the entities. */
template<typename = void>
ModelsCollection<ModelRawType> fresh(const QVector<WithItem> &relations = {});
/*! Reload a fresh model instance from the database for all the entities. */
template<typename = void>
ModelsCollection<ModelRawType> fresh(QString relation);
/*! Reload a fresh model instance from the database for all the entities. */
ModelsCollection<ModelRawType> fresh(const QVector<QString> &relations);
/*! Reload a fresh model instance from the database for all the entities. */
ModelsCollection<ModelRawType> fresh(QVector<QString> &&relations);
/*! Load a set of relationships onto the collection. */
template<typename = void>
ModelsCollection &load(const QVector<WithItem> &relations) &;
@@ -1154,6 +1165,56 @@ namespace Types
/* Collection - Relations related */
template<DerivedCollectionModel Model>
template<typename>
ModelsCollection<typename ModelsCollection<Model>::ModelRawType>
ModelsCollection<Model>::fresh(const QVector<WithItem> &relations)
{
// Nothing to do
if (this->isEmpty())
return {};
// Don't handle the nullptr
// Ownership of a unique_ptr()
auto freshModelsRaw = toPointer(first())->newQueryWithoutScopes()
->with(relations)
.whereKey(modelKeys<QVariant>())
.get();
const auto freshModels = freshModelsRaw.mapWithModelKeys();
return filter([&freshModels](ModelRawType *const model)
{
return model->exists && freshModels.contains(model->getKeyCasted());
})
.map([&freshModels](ModelRawType &&model)
{
return *freshModels.at(model.getKeyCasted());
});
}
template<DerivedCollectionModel Model>
template<typename>
ModelsCollection<typename ModelsCollection<Model>::ModelRawType>
ModelsCollection<Model>::fresh(QString relation)
{
return fresh(QVector<WithItem> {{std::move(relation)}});
}
template<DerivedCollectionModel Model>
ModelsCollection<typename ModelsCollection<Model>::ModelRawType>
ModelsCollection<Model>::fresh(const QVector<QString> &relations)
{
return fresh(WithItem::fromStringVector(relations));
}
template<DerivedCollectionModel Model>
ModelsCollection<typename ModelsCollection<Model>::ModelRawType>
ModelsCollection<Model>::fresh(QVector<QString> &&relations)
{
return fresh(WithItem::fromStringVector(std::move(relations)));
}
template<DerivedCollectionModel Model>
template<typename>
ModelsCollection<Model> &

View File

@@ -18,6 +18,7 @@ using Orm::Constants::SIZE_;
using Orm::Constants::SPACE_IN;
using Orm::Exceptions::InvalidArgumentError;
using Orm::One;
using Orm::Tiny::ConnectionOverride;
using Orm::Tiny::Exceptions::RelationNotFoundError;
using Orm::Tiny::Types::ModelsCollection;
@@ -118,6 +119,12 @@ private Q_SLOTS:
void toQuery() const;
/* Collection - Relations related */
void fresh_QVector_WithItem() const;
void fresh_WithSelectConstraint() const;
void fresh_QString() const;
void fresh_EmptyCollection() const;
void fresh_EmptyRelations() const;
void load_lvalue() const;
void load_lvalue_WithSelectConstraint() const;
void load_lvalue_WithLambdaConstraint() const;
@@ -1324,6 +1331,222 @@ void tst_Collection_Models::toQuery() const
QCOMPARE(result.constFirst().getAttributes().size(), 7);
}
void tst_Collection_Models::fresh_QVector_WithItem() const
{
auto images = AlbumImage::whereIn(ID, {1, 2, 3})->get();
QCOMPARE(images.size(), 3);
QCOMPARE(typeid (images), typeid (ModelsCollection<AlbumImage>));
QVERIFY(Common::verifyIds(images, {1, 2, 3}));
// Verify before
for (auto &image : images) {
QVERIFY(image.getRelations().empty());
const auto nameRef = image[NAME];
const auto nameNew = SPACE_IN.arg(nameRef->value<QString>(), "fresh");
nameRef = nameNew;
QCOMPARE(image.getAttribute<QString>(NAME), nameNew);
}
// Load fresh models with the album relationship
auto imagesFresh = images.fresh({{"album", [](auto &query)
{
query.select({ID, NAME});
}}});
QCOMPARE(imagesFresh.size(), 3);
QCOMPARE(typeid (imagesFresh), typeid (ModelsCollection<AlbumImage>));
QVERIFY(Common::verifyIds(imagesFresh, {1, 2, 3}));
// Verify
QVector<QString> actualNames;
actualNames.reserve(imagesFresh.size());
ModelsCollection<AlbumImage>::size_type index = 0;
for (auto &image : imagesFresh) {
QVERIFY(image.exists);
QVERIFY(&images[index] != &image);
// Check relations
QVERIFY(image.relationLoaded("album"));
QCOMPARE(image.getRelations().size(), 1);
actualNames << image.getAttribute<QString>(NAME);
// Check whether constraints were correctly applied
auto *album = image.getRelation<Album, One>("album");
const auto &attributes = album->getAttributes();
QCOMPARE(attributes.size(), 2);
std::unordered_set<QString> expectedAttributes {ID, NAME};
for (const auto &attribute : attributes)
QVERIFY(expectedAttributes.contains(attribute.key));
++index;
}
QVector<QString> expectedNames {
"album1_image1", "album2_image1", "album2_image2",
};
QCOMPARE(actualNames, expectedNames);
}
void tst_Collection_Models::fresh_WithSelectConstraint() const
{
auto images = AlbumImage::whereIn(ID, {1, 2, 3})->get();
QCOMPARE(images.size(), 3);
QCOMPARE(typeid (images), typeid (ModelsCollection<AlbumImage>));
QVERIFY(Common::verifyIds(images, {1, 2, 3}));
// Verify before
for (auto &image : images) {
QVERIFY(image.getRelations().empty());
const auto nameRef = image[NAME];
const auto nameNew = SPACE_IN.arg(nameRef->value<QString>(), "fresh");
nameRef = nameNew;
QCOMPARE(image.getAttribute<QString>(NAME), nameNew);
}
// Load fresh models with the album relationship
auto imagesFresh = images.fresh("album:id,name");
QCOMPARE(imagesFresh.size(), 3);
QCOMPARE(typeid (imagesFresh), typeid (ModelsCollection<AlbumImage>));
QVERIFY(Common::verifyIds(imagesFresh, {1, 2, 3}));
// Verify
QVector<QString> actualNames;
actualNames.reserve(imagesFresh.size());
ModelsCollection<AlbumImage>::size_type index = 0;
for (auto &image : imagesFresh) {
QVERIFY(image.exists);
QVERIFY(&images[index] != &image);
// Check relations
QVERIFY(image.relationLoaded("album"));
QCOMPARE(image.getRelations().size(), 1);
actualNames << image.getAttribute<QString>(NAME);
// Check whether constraints were correctly applied
auto *album = image.getRelation<Album, One>("album");
const auto &attributes = album->getAttributes();
QCOMPARE(attributes.size(), 2);
std::unordered_set<QString> expectedAttributes {ID, NAME};
for (const auto &attribute : attributes)
QVERIFY(expectedAttributes.contains(attribute.key));
++index;
}
QVector<QString> expectedNames {
"album1_image1", "album2_image1", "album2_image2",
};
QCOMPARE(actualNames, expectedNames);
}
void tst_Collection_Models::fresh_QString() const
{
auto images = AlbumImage::whereIn(ID, {1, 2, 3})->get();
QCOMPARE(images.size(), 3);
QCOMPARE(typeid (images), typeid (ModelsCollection<AlbumImage>));
QVERIFY(Common::verifyIds(images, {1, 2, 3}));
// Verify before
for (auto &image : images) {
QVERIFY(image.getRelations().empty());
const auto nameRef = image[NAME];
const auto nameNew = SPACE_IN.arg(nameRef->value<QString>(), "fresh");
nameRef = nameNew;
QCOMPARE(image.getAttribute<QString>(NAME), nameNew);
}
// Load fresh models with the album relationship
auto imagesFresh = images.fresh("album");
QCOMPARE(imagesFresh.size(), 3);
QCOMPARE(typeid (imagesFresh), typeid (ModelsCollection<AlbumImage>));
QVERIFY(Common::verifyIds(imagesFresh, {1, 2, 3}));
// Verify
QVector<QString> actualNames;
actualNames.reserve(imagesFresh.size());
ModelsCollection<AlbumImage>::size_type index = 0;
for (const auto &image : imagesFresh) {
QVERIFY(image.exists);
QVERIFY(&images[index] != &image);
QVERIFY(image.relationLoaded("album"));
QCOMPARE(image.getRelations().size(), 1);
actualNames << image.getAttribute<QString>(NAME);
++index;
}
QVector<QString> expectedNames {
"album1_image1", "album2_image1", "album2_image2",
};
QCOMPARE(actualNames, expectedNames);
}
void tst_Collection_Models::fresh_EmptyCollection() const
{
auto albums = ModelsCollection<Album>().fresh("albumImages");
QCOMPARE(typeid (albums), typeid (ModelsCollection<Album>));
QVERIFY(albums.isEmpty());
}
void tst_Collection_Models::fresh_EmptyRelations() const
{
auto images = AlbumImage::whereIn(ID, {1, 2, 3})->get();
QCOMPARE(images.size(), 3);
QCOMPARE(typeid (images), typeid (ModelsCollection<AlbumImage>));
QVERIFY(Common::verifyIds(images, {1, 2, 3}));
// Verify before
for (auto &image : images) {
QVERIFY(image.getRelations().empty());
const auto nameRef = image[NAME];
const auto nameNew = SPACE_IN.arg(nameRef->value<QString>(), "fresh");
nameRef = nameNew;
QCOMPARE(image.getAttribute<QString>(NAME), nameNew);
}
// Load fresh models without any relationships
auto imagesFresh = images.fresh();
QCOMPARE(imagesFresh.size(), 3);
QCOMPARE(typeid (imagesFresh), typeid (ModelsCollection<AlbumImage>));
QVERIFY(Common::verifyIds(imagesFresh, {1, 2, 3}));
// Verify
QVector<QString> actualNames;
actualNames.reserve(imagesFresh.size());
ModelsCollection<AlbumImage>::size_type index = 0;
for (const auto &image : imagesFresh) {
QVERIFY(image.exists);
QVERIFY(&images[index] != &image);
QVERIFY(image.getRelations().empty());
actualNames << image.getAttribute<QString>(NAME);
++index;
}
QVector<QString> expectedNames {
"album1_image1", "album2_image1", "album2_image2",
};
QCOMPARE(actualNames, expectedNames);
}
/* Collection - Relations related */
/*! Expected album images many type relation after the load() method invoked. */

View File

@@ -17,6 +17,7 @@ using Orm::Constants::SIZE_;
using Orm::Constants::SPACE_IN;
using Orm::Exceptions::InvalidArgumentError;
using Orm::One;
using Orm::Tiny::ConnectionOverride;
using Orm::Tiny::Exceptions::RelationNotFoundError;
using Orm::Tiny::Types::ModelsCollection;
@@ -118,6 +119,12 @@ private Q_SLOTS:
void toQuery() const;
/* Collection - Relations related */
void fresh_QVector_WithItem() const;
void fresh_WithSelectConstraint() const;
void fresh_QString() const;
void fresh_EmptyCollection() const;
void fresh_EmptyRelations() const;
void load_lvalue() const;
void load_lvalue_WithSelectConstraint() const;
void load_lvalue_WithLambdaConstraint() const;
@@ -1699,6 +1706,254 @@ void tst_Collection_Relations::toQuery() const
/* Collection - Relations related */
void tst_Collection_Relations::fresh_QVector_WithItem() const
{
auto images = AlbumImage::whereIn(ID, {1, 2, 3})->get();
QCOMPARE(images.size(), 3);
QCOMPARE(typeid (images), typeid (ModelsCollection<AlbumImage>));
QVERIFY(Common::verifyIds(images, {1, 2, 3}));
// Prepare
ModelsCollection<AlbumImage *> imagesInit {
&images[0], // NOLINT(readability-container-data-pointer)
&images[1],
&images[2],
};
// Verify before
for (auto *const image : imagesInit) {
QVERIFY(image->getRelations().empty());
const auto nameRef = (*image)[NAME];
const auto nameNew = SPACE_IN.arg(nameRef->value<QString>(), "fresh");
nameRef = nameNew;
QCOMPARE(image->getAttribute<QString>(NAME), nameNew);
}
// Load fresh models with the album relationship
auto imagesFresh = imagesInit.fresh({{"album", [](auto &query)
{
query.select({ID, NAME});
}}});
QCOMPARE(imagesFresh.size(), 3);
QCOMPARE(typeid (imagesFresh), typeid (ModelsCollection<AlbumImage>));
QVERIFY(Common::verifyIds(imagesFresh, {1, 2, 3}));
// Verify
QVector<QString> actualNames;
actualNames.reserve(imagesFresh.size());
ModelsCollection<AlbumImage>::size_type index = 0;
for (auto &image : imagesFresh) {
QVERIFY(image.exists);
// The images here is correct
QVERIFY(&images[index] != &image);
// Check relations
QVERIFY(image.relationLoaded("album"));
QCOMPARE(image.getRelations().size(), 1);
actualNames << image.getAttribute<QString>(NAME);
// Check whether constraints were correctly applied
auto *album = image.getRelation<Album, One>("album");
const auto &attributes = album->getAttributes();
QCOMPARE(attributes.size(), 2);
std::unordered_set<QString> expectedAttributes {ID, NAME};
for (const auto &attribute : attributes)
QVERIFY(expectedAttributes.contains(attribute.key));
++index;
}
QVector<QString> expectedNames {
"album1_image1", "album2_image1", "album2_image2",
};
QCOMPARE(actualNames, expectedNames);
}
void tst_Collection_Relations::fresh_WithSelectConstraint() const
{
auto images = AlbumImage::whereIn(ID, {1, 2, 3})->get();
QCOMPARE(images.size(), 3);
QCOMPARE(typeid (images), typeid (ModelsCollection<AlbumImage>));
QVERIFY(Common::verifyIds(images, {1, 2, 3}));
// Prepare
ModelsCollection<AlbumImage *> imagesInit {
&images[0], // NOLINT(readability-container-data-pointer)
&images[1],
&images[2],
};
// Verify before
for (auto *const image : imagesInit) {
QVERIFY(image->getRelations().empty());
const auto nameRef = (*image)[NAME];
const auto nameNew = SPACE_IN.arg(nameRef->value<QString>(), "fresh");
nameRef = nameNew;
QCOMPARE(image->getAttribute<QString>(NAME), nameNew);
}
// Load fresh models with the album relationship
auto imagesFresh = imagesInit.fresh("album:id,name");
QCOMPARE(imagesFresh.size(), 3);
QCOMPARE(typeid (imagesFresh), typeid (ModelsCollection<AlbumImage>));
QVERIFY(Common::verifyIds(imagesFresh, {1, 2, 3}));
// Verify
QVector<QString> actualNames;
actualNames.reserve(imagesFresh.size());
ModelsCollection<AlbumImage>::size_type index = 0;
for (auto &image : imagesFresh) {
QVERIFY(image.exists);
// The images here is correct
QVERIFY(&images[index] != &image);
// Check relations
QVERIFY(image.relationLoaded("album"));
QCOMPARE(image.getRelations().size(), 1);
actualNames << image.getAttribute<QString>(NAME);
// Check whether constraints were correctly applied
auto *album = image.getRelation<Album, One>("album");
const auto &attributes = album->getAttributes();
QCOMPARE(attributes.size(), 2);
std::unordered_set<QString> expectedAttributes {ID, NAME};
for (const auto &attribute : attributes)
QVERIFY(expectedAttributes.contains(attribute.key));
++index;
}
QVector<QString> expectedNames {
"album1_image1", "album2_image1", "album2_image2",
};
QCOMPARE(actualNames, expectedNames);
}
void tst_Collection_Relations::fresh_QString() const
{
auto images = AlbumImage::whereIn(ID, {1, 2, 3})->get();
QCOMPARE(images.size(), 3);
QCOMPARE(typeid (images), typeid (ModelsCollection<AlbumImage>));
QVERIFY(Common::verifyIds(images, {1, 2, 3}));
// Prepare
ModelsCollection<AlbumImage *> imagesInit {
&images[0], // NOLINT(readability-container-data-pointer)
&images[1],
&images[2],
};
// Verify before
for (auto *const image : imagesInit) {
QVERIFY(image->getRelations().empty());
const auto nameRef = (*image)[NAME];
const auto nameNew = SPACE_IN.arg(nameRef->value<QString>(), "fresh");
nameRef = nameNew;
QCOMPARE(image->getAttribute<QString>(NAME), nameNew);
}
// Load fresh models with the album relationship
auto imagesFresh = imagesInit.fresh("album");
QCOMPARE(imagesFresh.size(), 3);
QCOMPARE(typeid (imagesFresh), typeid (ModelsCollection<AlbumImage>));
QVERIFY(Common::verifyIds(imagesFresh, {1, 2, 3}));
// Verify
QVector<QString> actualNames;
actualNames.reserve(imagesFresh.size());
ModelsCollection<AlbumImage *>::size_type index = 0;
for (const auto &image : imagesFresh) {
QVERIFY(image.exists);
// The images here is correct
QVERIFY(&images[index] != &image);
QVERIFY(image.relationLoaded("album"));
QCOMPARE(image.getRelations().size(), 1);
actualNames << image.getAttribute<QString>(NAME);
++index;
}
QVector<QString> expectedNames {
"album1_image1", "album2_image1", "album2_image2",
};
QCOMPARE(actualNames, expectedNames);
}
void tst_Collection_Relations::fresh_EmptyCollection() const
{
auto albums = ModelsCollection<Album *>().fresh("albumImages");
QCOMPARE(typeid (albums), typeid (ModelsCollection<Album>));
QVERIFY(albums.isEmpty());
}
void tst_Collection_Relations::fresh_EmptyRelations() const
{
auto images = AlbumImage::whereIn(ID, {1, 2, 3})->get();
QCOMPARE(images.size(), 3);
QCOMPARE(typeid (images), typeid (ModelsCollection<AlbumImage>));
QVERIFY(Common::verifyIds(images, {1, 2, 3}));
// Prepare
ModelsCollection<AlbumImage *> imagesInit {
&images[0], // NOLINT(readability-container-data-pointer)
&images[1],
&images[2],
};
// Verify before
for (auto *const image : imagesInit) {
QVERIFY(image->getRelations().empty());
const auto nameRef = (*image)[NAME];
const auto nameNew = SPACE_IN.arg(nameRef->value<QString>(), "fresh");
nameRef = nameNew;
QCOMPARE(image->getAttribute<QString>(NAME), nameNew);
}
// Load fresh models without any relationships
auto imagesFresh = imagesInit.fresh();
QCOMPARE(imagesFresh.size(), 3);
QCOMPARE(typeid (imagesFresh), typeid (ModelsCollection<AlbumImage>));
QVERIFY(Common::verifyIds(imagesFresh, {1, 2, 3}));
// Verify
QVector<QString> actualNames;
actualNames.reserve(imagesFresh.size());
ModelsCollection<AlbumImage *>::size_type index = 0;
for (const auto &image : imagesFresh) {
QVERIFY(image.exists);
// The images here is correct
QVERIFY(&images[index] != &image);
QVERIFY(image.getRelations().empty());
actualNames << image.getAttribute<QString>(NAME);
++index;
}
QVector<QString> expectedNames {
"album1_image1", "album2_image1", "album2_image2",
};
QCOMPARE(actualNames, expectedNames);
}
/*! Expected album images many type relation after the load() method invoked. */
struct ExpectedImages
{