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!
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.
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)}},
});
The Orm::collect<Model>
function is mandatory if your attributes contain the QDateTime
instance, you can read more about this problem here.
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:
all contains doesntContain each except filter find first firstWhere fresh implode isEmpty isNotEmpty last load map mapWithKeys mapWithModelKeys modelKeys only pluck reject sort sortBy sortByDesc sortDesc stableSort stableSortBy stableSortByDesc stableSortDesc tap toBase toJson toJsonArray toJsonDocument toMap toMapVariantList toQuery toList toListVariantList unique uniqueBy uniqueRelaxed uniqueRelaxedBy value where whereBetween whereIn whereNotBetween whereNotIn whereNotNull whereNull
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();
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.
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.
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.
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();
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.
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.
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.
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"}},
}
*/
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()}}
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.