Skip to main content

TinyORM: Collections

Introduction​

Stability: 2 - Stable

The Orm::Tiny::Types::ModelsCollection is specialized container which provides a fluent, convenient wrapper for working with vector of models. All TinyORM methods that return more than one model result, will return instances of the ModelsCollection class, including results retrieved via the get method or methods that return relationships like the getRelation and getRelationValue.

The ModelsCollection class extends QList<Model>, so it naturally inherits dozens of methods used to work with the underlying vector of TinyORM models. Be sure to review the QList documentation to learn all about these helpful methods!

info

The ModelsCollection template parameter can be declared only with the model type or a model type pointer, it also can't be const and can't be a reference. It's constrained using the DerivedCollectionModel concept.

You can iterate over the ModelsCollection the same way as over the QList:

using Models::User;

ModelsCollection<User> users = User::whereEq("active", true)->get();

for (const auto &user : users)
qDebug() << user.getAttribute<QString>("name");

However, as previously mentioned, collections are much more powerful than vectors and expose a variety of map / reduce operations that may be chained using an intuitive interface. For example, we may remove all active users and then gather the first name of each remaining user:

auto names = User::all().reject([](User *const user)
{
return user->getAttribute<bool>("active");
})
.pluck("name");

As you can see, the ModelsCollection class allows you to chain its methods to perform fluent mapping and reducing of the underlying vector. In general, collections are immutable, meaning every ModelsCollection method returns an entirely new ModelsCollection instance.

info

The ModelsCollection<Model> is returning from the Models' methods like get, all, findMany, chunk; the ModelsCollection<Model *> is returning from the relationship-related methods as getRelation and getRelationValue.

Collection Conversion​

While most TinyORM collection methods return a new instance of ModelsCollection, the modelKeys, mapWithKeys, and pluck methods return a base QList or std unordered/map instances. Likewise, one of the map methods overload returns the QList<T>.

Creating Collections​

Creating a ModelsCollection is as simple as:

ModelsCollection<User> users {
{{"name", "Kate"}, {"votes", 150}},
{{"name", "John"}, {"votes", 200}},
};

You can also create a collection of pointers, eg. ModelsCollection<User *>:

ModelsCollection<User *> userPointers {
&users[0], &users[1],
};

The ModelsCollection<Model> is implicitly convertible and assignable from the QList<Model>:

QList<User> usersVector {
{{"name", "Kate"}, {"votes", 150}},
{{"name", "John"}, {"votes", 200}},
};

ModelsCollection<User> users(usersVector);
users = usersVector;

Alternatively, you can use the Orm::collect<Model> helper function to create a ModelsCollection from the given attributes:

ModelsCollection<User> users = Orm::collect<User>({
{{"name", "Kate"}, {"added_on", QDateTime::currentDateTimeUtc()}},
{{"name", "John"}, {"added_on", QDateTime({2023, 6, 1}, {13, 46, 15}, QTimeZone::UTC)}},
});
caution

The Orm::collect<Model> function is mandatory if your attributes contain the QDateTime instance, you can read more about this problem here.

note

The results of TinyORM queries are always returned as ModelsCollection instances.

Available Methods​

For the majority of the remaining collection documentation, we'll discuss each method available on the ModelsCollection class. Remember, all of these methods may be chained to fluently manipulate the underlying vector.

Furthermore, almost every method returns a new ModelsCollection instance, allowing you to preserve the original copy of the collection when necessary:

note

For a better understanding of the following examples, many of the variable declarations below use actual types instead of the auto keyword.

all()​

The all method returns a copy of the underlying vector represented by the collection:

QList<User> = users.all();
note

The toBase is an alias to the all method.

contains()​

The contains method may be used to determine if a given model instance is contained by the collection. This method accepts a primary key or a model instance:

users.contains(1);

users.contains(User::find(1));

Alternatively, you may pass a lambda expression to the contains method to determine if a model exists in the collection matching a given truth test:

users.contains([](const User *const user)
{
return user->getKeyCasted() == 2;
});

For the inverse of contains, see the doesntContain method.

doesntContain()​

The doesntContain method determines whether the collection does not contain a given item. This method accepts a primary key or a model instance:

users.doesntContain(1);

users.doesntContain(User::find(1));

Alternatively, you may pass a lambda expression to the doesntContain method to determine if a model does not exist in the collection matching a given truth test:

users.doesntContain([](const User *const user)
{
return user->getKeyCasted() == 2;
});

For the inverse of doesntContain, see the contains method.

each()​

The each method iterates over the models in the collection and passes each model to the lambda expression:

ModelsCollection<User> users = Post::whereEq("user_id", 1)->get();

users.each([](User *const user)
{
// ...
});

If you would like to stop iterating through the models, you may return false from your lambda expression:

users.each([](User *const user)
{
if (/* condition */)
return false;

// Some logic

return true;
});

You may also pass the lambda expression with two parameters, whereas the second one is an index:

users.each([](User *const user, const std::size_t index)
{
// ...
});

The each method returns an lvalue reference to the currently processed collection.

It can be also called on ModelsCollection rvalues, it returns an rvalue reference in this case.

except()​

The except method returns all of the models that do not have the given primary keys:

ModelsCollection<User *> usersResult = users.except({1, 2, 3});

All of the models are returned if the ids argument is empty except({}).

The order of models in the collection is preserved.

For the inverse of except, see the only method.

filter()​

The filter method filters the collection using the lambda expression, keeping only those models that pass a given truth test:

auto usersBanned = users.filter([](const User *const user)
{
return user->getAttribute<bool>("is_banned");
});

You may also pass the lambda expression with two parameters, whereas the second one is an index:

auto usersBanned = users.filter([](const User *const user,
const std::size_t index)
{
return index < 10 && user->getAttribute<bool>("is_banned");
});

If no lambda expression is supplied, all models of the collection that are equivalent to the nullptr will be removed:

ModelsCollection<User> usersRaw = User::findMany({1, 2});
ModelsCollection<User *> users {&usersRaw[0], nullptr, &usersRaw[1]};

ModelsCollection<User *> filtered = users.filter();

// {1, 2}

For the inverse of filter, see the reject method.

find()​

The find method returns the model that has a primary key matching the given key:

User *const user = users.find(1);

If you pass a model instance, find will attempt to return a model matching the primary key:

User *user = users.find(anotherUser);

The two overloads above also accept the second defaultModel model argument, which will be returned if a model was not found in the collection, its default value is the nullptr.

Alternatively, may pass more IDs and find will return all models which have a primary key within the given unordered set:

ModelsCollection<User *> usersMany = users.find({1, 2});

This overload internally calls the only method.

first()​

The first method returns the first model in the collection that passes a given truth test:

ModelsCollection<User> users {
{{"name", "Kate"}, {"votes", 150}},
{{"name", "John"}, {"votes", 200}},
{{"name", "Jack"}, {"votes", 400}},
};

User *user = users.first([](User *const user)
{
return user->getAttribute<quint64>("votes") > 150;
});

// {{"name", "John"}, {"votes", 200}}

If no model passes a given truth test then the value of the second defaultModel argument will be returned, its default value is the nullptr.

using NullVariant = Orm::Utils::NullVariant;

User defaultUser {{"name", NullVariant::QString()},
{"votes", NullVariant::ULongLong()}};

User *user = users.first([](User *const user)
{
return user->getAttribute<quint64>("votes") > 500;
},
&defaultUser);

/*
{{"name", NullVariant::QString()},
{"votes", NullVariant::ULongLong()}}
*/

You can also call all first overloads provided by the QList::first.

firstWhere()​

The firstWhere method returns the first model in the collection with the given column / value pair:

using NullVariant = Orm::Utils::NullVariant;

ModelsCollection<User> users {
{{"name", "Leon"}, {"age", NullVariant::UShort()}},
{{"name", "Jill"}, {"age", 14}},
{{"name", "Jack"}, {"age", 23}},
{{"name", "Jill"}, {"age", 84}},
};

auto user = users.firstWhereEq("name", "Linda");

// {{"name", "Jill"}, {"age", 14}}

You may also call the firstWhere method with a comparison operator:

users.firstWhere("age", ">=", 18);

// {{"name", "Jack"}, {"age", 23}}

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 method.

implode()​

The implode method joins attributes by the given column and the "glue" string you wish to place between the values:

ModelsCollection<Product> products {
{{"product", "Desk"}, {"price", 200}},
{{"product", "Chair"}, {"price", 100}},
};

products.implode("product", ", ");

// {Desk, Chair}

The default "glue" value is an empty string "".

isEmpty()​

The isEmpty method returns true if the collection is empty; otherwise, false is returned:

ModelsCollection<User>().isEmpty();

// true

isNotEmpty()​

The isNotEmpty method returns true if the collection is not empty; otherwise, false is returned:

ModelsCollection<User>().isNotEmpty();

// false

last()​

The last method returns the last model in the collection that passes a given truth test:

ModelsCollection<User> users {
{{"name", "Kate"}, {"votes", 150}},
{{"name", "John"}, {"votes", 200}},
{{"name", "Jack"}, {"votes", 400}},
{{"name", "Rose"}, {"votes", 350}},
};

User *user = users.last([](User *const user)
{
return user->getAttribute<quint64>("votes") < 300;
});

// {{"name", "John"}, {"votes", 200}}

If no model passes a given truth test then the value of the second defaultModel argument will be returned, its default value is the nullptr.

using NullVariant = Orm::Utils::NullVariant;

User defaultUser {{"name", NullVariant::QString()},
{"votes", NullVariant::ULongLong()}};

User *user = users.last([](User *const user)
{
return user->getAttribute<quint64>("votes") < 100;
},
&defaultUser);

/*
{{"name", NullVariant::QString()},
{"votes", NullVariant::ULongLong()}}
*/

You can also call all last overloads provided by the QList::last.

load()​

The load method eager loads the given relationships for all models in the collection:

users.load({"comments", "posts"});

users.load("comments.author");

users.load({{"comments"}, {"posts", [](auto &query)
{
query.whereEq("active", true);
}}});

The relations argument format is the same as for TinyBuilder's load method.

map()​

The map method iterates through the collection and passes a copy of each model to the given lambda expression. The lambda expression is free to modify the model and return it, thus forming a new collection of modified models:

ModelsCollection<User> users {
{{"name", "John"}, {"votes", 200}},
{{"name", "Jack"}, {"votes", 400}},
};

auto usersAdded = users.map([](User &&userCopy)
{
if (userCopy.getAttribute<QString>("name") == "John")
userCopy["votes"] = userCopy.getAttribute<quint64>("votes") + 1;

return std::move(userCopy);
});

/*
{
{{"name", "John"}, {"price", 201}},
{{"name", "Jack"}, {"price", 400}},
}
*/

The second map overload allows to return the QList<T>:

QList<quint64> usersAdded = users.map<quint64>([](User &&userCopy)
{
const auto votesRef = userCopy["votes"];

if (userCopy.getAttribute<QString>("name") == "John")
votesRef = userCopy.getAttribute<quint64>("votes") + 1;

return votesRef->value<quint64>();
});

// {201, 400}

Both overloads allow to pass the lambda expression with two arguments, whereas the second argument can be an index of the std::size_t type.

caution

Like most other collection methods, map returns a new collection instance; it does not modify the collection it is called on. If you want to modify the original collection in place, use the each method.

info

The model copy is passed to the lambda expression even if the map iterates over a collection of model pointers ModelsCollection<Model *>. The models are dereferenced behind the scene.

mapWithKeys()​

The mapWithKeys method iterates through the collection and passes each model to the given lambda expression. It returns the std::unordered_map<K, V> and the lambda expression should return the std::pair<K, V> containing a single column / value pair:

ModelsCollection<User> users {
{{"id", 1}, {"name", "John"}, {"email", "john@example.com"}},
{{"id", 2}, {"name", "Jill"}, {"email", "jill@example.com"}},
};

auto usersMap = users.mapWithKeys<quint64, QString>(
[](User *const user) -> std::pair<quint64, QString>
{
return {user->getKeyCasted(), user->getAttribute<QString>("name")};
});

// {{1, 'John'}, {2, 'Jill'}}

You can also map IDs to the model pointers:

auto usersMap = users.mapWithKeys<quint64, User *>(
[](User *const user) -> std::pair<quint64, User *>
{
return {user->getKeyCasted(), user};
});

mapWithModelKeys()​

The mapWithModelKeys maps the primary keys to the Model *, it returns the std::unordered_map<Model::KeyType, Model *>:

auto usersMap = users.mapWithModelKeys();

modelKeys()​

The modelKeys method returns the primary keys for all models in the collection:

ModelsCollection<User> users {
{{"id", 1}, {"name", "John"}},
{{"id", 2}, {"name", "Jill"}},
{{"id", 3}, {"name", "Kate"}},
{{"id", 5}, {"name", "Rose"}},
};

users.modelKeys(); // Returns QList<QVariant>
users.modelKeys<quint64>();

// {1, 2, 3, 5}

only()​

The only method returns all of the models that have the given primary keys:

ModelsCollection<User *> usersResult = users.only({1, 2, 3});

An empty collection is returned if the ids argument is empty only({}).

The order of models in the collection is preserved.

For the inverse of only, see the except method.

pluck()​

The pluck method retrieves all of the values for a given column, the following overload returns the QList<QVariant>:

ModelsCollection<Product> products {
{{"id", 1}, {"name", "Desk"}},
{{"id", 2}, {"name", "Chair"}},
};

auto plucked = products.pluck("name");

// {Desk, Chair}

The second overload allows returning the custom type QList<T>:

auto plucked = products.pluck<QString>("name");

You may also specify how you wish the resulting collection to be keyed, this overload returns the std::map<T, QVariant>:

auto plucked = products.pluck<quint64>("name", "id");

// {{1, "Desk"}, {2, "Chair"}}

If duplicate keys exist, the last matching attribute will be inserted into the plucked collection:

ModelsCollection<Product> collection {
{{"brand", "Tesla"}, {"color", "red"}},
{{"brand", "Pagani"}, {"color", "white"}},
{{"brand", "Tesla"}, {"color", "black"}},
{{"brand", "Pagani"}, {"color", "orange"}},
};

auto plucked = collection.pluck<QString>("color", "brand");

// {{'Tesla', 'black'}, {'Pagani', 'orange"}}

reject()​

The reject method filters the collection using the given lambda expression. The lambda should return true if the model should be removed from the resulting collection:

auto usersWithNote = users.reject([](const User *const user)
{
return user->getAttribute("note").isNull();
});

You may also pass the lambda expression with two arguments, whereas the second argument can be an index of the std::size_t type.

For the inverse of the reject method, see the filter method.

sort()​

The sort method sorts the models collection by primary keys:

ModelsCollection<User> users {
{{"id", 2}, {"name", "Kate"}},
{{"id", 3}, {"name", "John"}},
{{"id", 1}, {"name", "Jack"}},
};

auto sorted = users.sort();

/*
{
{{"id", 1}, {"name", "Jack"}},
{{"id", 2}, {"name", "Kate"}},
{{"id", 3}, {"name", "John"}},
}
*/

You may pass a predicate and projection callbacks to the sort method with your own algorithms. Refer to the CPP reference documentation on ranges::sort, which is what the sort method calls internally.

You can eg. sort by multiple columns, for an alternative method of multi-column sorting look at sortBy:

ModelsCollection<User> users {
{{"name", "Kate"}, {"votes", 350}},
{{"name", "John"}, {"votes", 200}},
{{"name", "John"}, {"votes", 150}},
{{"name", "Kate"}, {"votes", 200}},
};

auto sorted = users.sort([](const User *const left,
const User *const right)
{
const auto leftValue = left->getAttribute<QString>("name");
const auto rightValue = right->getAttribute<QString>("name");

if (leftValue == rightValue)
return left->getAttribute<quint64>("votes") <
right->getAttribute<quint64>("votes");

return leftValue < rightValue;
});

/*
{
{{"name", "John"}, {"votes", 150}},
{{"name", "John"}, {"votes", 200}},
{{"name", "Kate"}, {"votes", 200}},
{{"name", "Kate"}, {"votes", 350}},
}
*/

The order of equal elements is not guaranteed to be preserved.

info

You can use the stable sort method variants to preserve the order of equal models.

sortBy()​

The sortBy method sorts the collection by the given column, this overload needs the template argument so it can cast the attribute value before comparing:

ModelsCollection<User> users {
{{"name", "Kate"}, {"votes", 150}},
{{"name", "John"}, {"votes", 200}},
{{"name", "Jack"}, {"votes", 400}},
};

auto sorted = users.sortBy<QString>("name");

/*
{
{{"name", "Jack"}, {"votes", 400}},
{{"name", "John"}, {"votes", 200}},
{{"name", "Kate"}, {"votes", 150}},
}
*/

You may pass the projection callback to determine how to sort the collection's models:

auto sorted = users.sortBy([](User *const user)
{
return user->getAttribute<quint64>("votes");
});

/*
{
{{"name", "Kate"}, {"votes", 150}},
{{"name", "John"}, {"votes", 200}},
{{"name", "Jack"}, {"votes", 400}},
}
*/

If you would like to sort your collection by multiple columns, you may pass a vector of comparison lambda expressions that define each sort operation to the sortBy method, in the following example is the name column sorted in ascending order and the second votes column is sorted in descending order:

using AttributeUtils = Orm::Tiny::Utils::Attribute;

ModelsCollection<User> users {
{{"name", "Kate"}, {"votes", 350}},
{{"name", "John"}, {"votes", 200}},
{{"name", "John"}, {"votes", 150}},
{{"name", "Kate"}, {"votes", 200}},
};

auto sorted = users.sortBy({
[](const User *const left, const User *const right)
{
return AttributeUtils::compareForSortBy(
left->getAttribute<QString>("name"),
right->getAttribute<QString>("name"));
},
[](const User *const left, const User *const right)
{
return AttributeUtils::compareForSortByDesc(
left->getAttribute<quint64>("votes"),
right->getAttribute<quint64>("votes"));
},
});

/*
{
{{"name", "John"}, {"votes", 200}},
{{"name", "John"}, {"votes", 150}},
{{"name", "Kate"}, {"votes", 350}},
{{"name", "Kate"}, {"votes", 200}},
}
*/

The AttributeUtils::compareForSortBy and compareForSortByDesc methods are helper methods, they are needed because the Qt framework doesn't define <=> spaceship operator on its types, it doesn't support the three-way comparison.

The order of equal elements is not guaranteed to be preserved.

sortByDesc()​

This method has the same signature as the sortBy method but will sort the collection in the opposite order.

The order of equal elements is not guaranteed to be preserved.

sortDesc()​

This method will sort the collection in the opposite order as the sort method:

ModelsCollection<User> users {
{{"id", 2}, {"name", "Kate"}},
{{"id", 3}, {"name", "John"}},
{{"id", 1}, {"name", "Jack"}},
};

auto sorted = users.sortDesc();

/*
{
{{"id", 3}, {"name", "John"}},
{{"id", 2}, {"name", "Kate"}},
{{"id", 1}, {"name", "Jack"}},
}
*/

The order of equal elements is not guaranteed to be preserved.

stableSort()​

This method has the same signature as the sort method but will preserve the order of equal elements (guaranteed to be preserved).

stableSortBy()​

This method has the same signature as the sortBy method but will preserve the order of equal elements (guaranteed to be preserved).

stableSortByDesc()​

This method has the same signature as the sortByDesc method but will sort the collection in the opposite order and preserve the order of equal elements (guaranteed to be preserved).

stableSortDesc()​

This method has the same signature as the sortDesc method but will sort the collection in the opposite order and preserve the order of equal elements (guaranteed to be preserved).

tap()​

The tap method passes a collection to the given lambda expression, allowing you to "tap" into the collection at a specific point and do something with the models while not affecting the collection itself:

ModelsCollection<User> users {
{{"id", 2}, {"name", "Kate"}},
{{"id", 3}, {"name", "John"}},
{{"id", 1}, {"name", "Jack"}},
};

users.sort()
.tap([](/*const */ModelsCollection<User *> &usersRef)
{
qDebug() << "IDs after sorting:"
<< usersRef.template modelKeys<quint64>();
})
.value<quint64>("id");

// 1

The tap method returns an lvalue reference to the currently processed collection.

It can be also called on ModelsCollection rvalues, it returns an rvalue reference in this case.

toBase()​

The toBase method returns a copy of the underlying vector represented by the collection:

QList<User> = users.toBase();
note

The toBase is an alias to the all method.

toJson()​

The toJson method converts the collection of models with all nested relations into a JSON serialized QByteArray.

It returns an empty array for empty many type relations and null for empty one type relations.

info

The toJson method accepts the QJsonDocument::JsonFormat, possible values are QJsonDocument::Indented or QJsonDocument::Compact.

toJsonArray()​

The toJsonArray method converts the collection of models with all nested relations into a QJsonArray.

toJsonDocument()​

The toJsonDocument method converts the collection of models with all nested relations into a QJsonDocument.

toMap()​

The toMap method converts the collection of models with all nested relations into an attributes map QList<QVariantMap>.

It returns an empty QVariantList for empty many type relations and a null QVariant for empty one type relations.

toMapVariantList()​

The toMapVariantList method converts the collection of models with all nested relations into an attributes map, but it returns the QVariantList instead of the QList<QVariantMap>.

It returns an empty QVariantList for empty many type relations and a null QVariant for empty one type relations.

note

The toMapVariantList method is internally needed by the toJson related methods.

toQuery()​

The toQuery method returns the TinyBuilder instance containing a whereIn constraint with the collection of models' primary keys:

using Models::User;

ModelsCollection<User> users = User::whereEq("status", "VIP")->get();

users.toQuery()->update({
{"status", "Administrator"},
});

toList()​

The toList method converts the collection of models with all nested relations into an attributes vector QList<QList<AttributeItem>>.

It returns an empty QVariantList for empty many type relations and a null QVariant for empty one type relations.

toListVariantList()​

The toListVariantList method converts the collection of models with all nested relations into an attributes vector, but it returns the QVariantList instead of the QList<QList<AttributeItem>>.

It returns an empty QVariantList for empty many type relations and a null QVariant for empty one type relations.

note

The toListVariantList method is internally needed by the toJson related methods.

unique()​

The unique method returns all of the unique models in the sorted collection. Any models with the same primary key as another model in the collection are removed:

ModelsCollection<User> users {
{{"id", 2}, {"name", "Kate"}},
{{"id", 1}, {"name", "Jack"}},
{{"id", 3}, {"name", "John"}},
{{"id", 1}, {"name", "Jack"}},
};

auto unique = users.unique();

/*
{
{{"id", 1}, {"name", "Jack"}},
{{"id", 2}, {"name", "Kate"}},
{{"id", 3}, {"name", "John"}},
}
*/

It sorts the collection internally because the ranges::unique can correctly operate only on the sorted container. You can disable it by passing false using the first sort parameter:

auto unique = users.sort().unique(false);

/*
{
{{"id", 1}, {"name", "Jack"}},
{{"id", 2}, {"name", "Kate"}},
{{"id", 3}, {"name", "John"}},
}
*/

uniqueBy()​

The uniqueBy method returns all of the unique models in the sorted collection by the given column. Any models with the same column value as another model in the collection are removed. It needs the template argument, so it can cast the attribute value before comparing:

ModelsCollection<User> users {
{{"name", "Kate"}},
{{"name", "Jack"}},
{{"name", "John"}},
{{"name", "Jack"}},
};

auto unique = users.uniqueBy<QString>("name");

/*
{
{{"name", "Jack"}},
{{"name", "John"}},
{{"name", "Kate"}},
}
*/

It sorts the collection internally because the ranges::unique can correctly operate only on the sorted container. You can disable it by passing false using the second sort parameter:

auto unique = users.sortBy<QString>("name")
.uniqueBy<QString>("name", false);

/*
{
{{"name", "Jack"}},
{{"name", "John"}},
{{"name", "Kate"}},
}
*/

uniqueRelaxed()​

The uniqueRelaxed method returns all of the unique models in the collection, it doesn't need a sorted collection. Any models with the same primary key as another model in the collection are removed:

ModelsCollection<User> users {
{{"id", 2}, {"name", "Kate"}},
{{"id", 1}, {"name", "Jack"}},
{{"id", 3}, {"name", "John"}},
{{"id", 1}, {"name", "Jack"}},
};

auto unique = users.uniqueRelaxed();

/*
{
{{"id", 2}, {"name", "Kate"}},
{{"id", 1}, {"name", "Jack"}},
{{"id", 3}, {"name", "John"}},
}
*/

uniqueRelaxedBy()​

The uniqueRelaxedBy method returns all of the unique models in the collection by the given column, it doesn't need a sorted collection, but it needs the template argument, so it can cast the attribute value before comparing:

ModelsCollection<User> users {
{{"name", "Kate"}},
{{"name", "Jack"}},
{{"name", "John"}},
{{"name", "Jack"}},
};

auto unique = users.uniqueRelaxedBy<QString>("name");

/*
{
{{"name", "Kate"}},
{{"name", "Jack"}},
{{"name", "John"}},
}
*/

value()​

The value method retrieves a given value from the first model of the collection:

ModelsCollection<User> users {
{{"name", "John"}, {"votes", 200}},
{{"name", "Jack"}, {"votes", 400}},
};

QVariant votes = users.value("votes");

// 200

Alternatively, you can cast an obtained QVariant value to the given type by the second value overload:

quint64 votes = users.value<quint64>("votes");

The value method also accepts the second defaultValue argument, which will be returned if a collection is empty, the first model is nullptr, or a model doesn't contain the given column:

auto votes = ModelsCollection<User>().value("votes", 0);

// 0

You can also call all value overloads provided by the QList::value.

where()​

The where method filters the collection by a given column / value pair:

ModelsCollection<Product> products {
{{"product", "Desk"}, {"price", 200}},
{{"product", "Chair"}, {"price", 100}},
{{"product", "Bookcase"}, {"price", 150}},
{{"product", "Door"}, {"price", 100}},
};

auto filtered = products.where("price", "=", 100);

/*
{
{{"product", "Chair"}, {"price", 100}},
{{"product", "Door"}, {"price", 100}},
}
*/

For convenience, if you want to verify that a column is = to a given value, you may call whereEq method. Similar XxxEq methods are also defined for other commands:

auto filtered = products.whereEq("price", 100);

Optionally, you may pass a comparison operator as the second argument.
Supported operators are =, !=, <, >, <=, and >=:

ModelsCollection<Product> products {
{{"product", "Desk"}, {"price", 200}},
{{"product", "Chair"}, {"price", 100}},
{{"product", "Bookcase"}, {"price", 150}},
{{"product", "Door"}, {"price", 250}},
};

auto filtered = products.where("price", ">", 150);

/*
{
{{"product", "Desk"}, {"price", 200}},
{{"product", "Door"}, {"price", 250}},
}
*/

whereBetween()​

The whereBetween method filters the collection by determining if a specified models' attribute value is within a given range:

ModelsCollection<Product> products {
{{"product", "Desk"}, {"price", 200}},
{{"product", "Chair"}, {"price", 80}},
{{"product", "Bookcase"}, {"price", 150}},
{{"product", "Pencil"}, {"price", 30}},
{{"product", "Door"}, {"price", 100}},
};

auto filtered = products.whereBetween<quint64>("price", {100, 200});

/*
{
{{"product", "Desk"}, {"price", 200}},
{{"product", "Bookcase"}, {"price", 150}},
{{"product", "Door"}, {"price", 100}},
}
*/

whereIn()​

The whereIn method filters models from the collection that have a specified attribute value that is contained within the given unordered set:

ModelsCollection<Product> products {
{{"product", "Desk"}, {"price", 200}},
{{"product", "Chair"}, {"price", 100}},
{{"product", "Bookcase"}, {"price", 150}},
{{"product", "Door"}, {"price", 250}},
};

auto filtered = products.whereIn<quint64>("price", {100, 200});

/*
{
{{"product", "Desk"}, {"price", 200}},
{{"product", "Chair"}, {"price", 100}},
}
*/

An empty collection is returned if the values argument is empty whereIn("price", {}).

The order of models in the collection is preserved.

whereNotBetween()​

The whereNotBetween method filters the collection by determining if a specified models' attribute value is outside of a given range:

ModelsCollection<Product> products {
{{"product", "Desk"}, {"price", 200}},
{{"product", "Chair"}, {"price", 80}},
{{"product", "Bookcase"}, {"price", 150}},
{{"product", "Pencil"}, {"price", 30}},
{{"product", "Door"}, {"price", 100}},
};

auto filtered = products.whereNotBetween<quint64>("price", {100, 200});

/*
{
{{"product", "Chair"}, {"price", 80}},
{{"product", "Pencil"}, {"price", 30}},
}
*/

whereNotIn()​

The whereNotIn method removes models from the collection that have a specified attribute value that is contained within the given unordered set:

ModelsCollection<Product> products {
{{"product", "Desk"}, {"price", 200}},
{{"product", "Chair"}, {"price", 100}},
{{"product", "Bookcase"}, {"price", 150}},
{{"product", "Door"}, {"price", 250}},
};

auto filtered = products.whereNotIn<quint64>("price", {100, 200});

/*
{
{{"product", "Bookcase"}, {"price", 150}},
{{"product", "Door"}, {"price", 250}},
}
*/

All of the models are returned if the values argument is empty whereNotIn("price", {}).

The order of models in the collection is preserved.

whereNotNull()​

The whereNotNull method returns models from the collection where the given column is not null QVariant:

#include <orm/utils/nullvariant.hpp>

using NullVariant = Orm::Utils::NullVariant;

ModelsCollection<User> users {
{{"name", "John"}},
{{"name", NullVariant::QString()}},
{{"name", "Jack"}},
};

auto filtered = users.whereNotNull("name");

/*
{
{{"name", "John"}},
{{"name", "Jack"}},
}
*/
note

The NullVariant class returns the correct null QVariant for both Qt 5 QVariant(QVariant::String) and also Qt 6 QVariant(QMetaType(QMetaType::QString)). The reason why this class still exists even after Qt v5.15 support was removed is the performance boost.

whereNull()​

The whereNull method returns models from the collection where the given column is null QVariant:

#include <orm/utils/nullvariant.hpp>

using NullVariant = Orm::Utils::NullVariant;

ModelsCollection<User> users {
{{"name", "John"}},
{{"name", NullVariant::QString()}},
{{"name", "Jack"}},
};

auto filtered = users.whereNotNull("name");

// {{"name", NullVariant::QString()}}
note

The NullVariant class returns the correct null QVariant for both Qt 5 QVariant(QVariant::String) and also Qt 6 QVariant(QMetaType(QMetaType::QString)). The reason why this class still exists even after Qt v5.15 support was removed is the performance boost.