toggldesktop
toggldesktop copied to clipboard
[PREVIEW] When loading from the database, initialize everything right in the constructor
📒 Description
This change consists of three main parts:
- The
src/
folder is now reorganized, thesrc/model
andsrc/database
folders were added to contain models and database-related classes, respectively. Also, some helper classes were moved to thesrc/util
folder. - The internal database now contains a
guid
field for all tables representingBaseModel
-derived classes. - Loading from the database is now being done right in the
BaseModel
constructor using the newly addedBaseModelQuery
class that contains information about the table structure and SQL query necessary to reconstruct the model.
Especially point 3 is interesting in this PR:
BaseModelQuery is now used like this (probably subject to change because of a MSVC bug on Windows):
using Query = BaseModelQuery;
// in base_model.h:
std::string modelName {};
static const Query BaseModel::query {
Query::Table{},
Query::Columns{
Query::Bind("local_id", &BaseModel::local_id_, Query::Binding::REQUIRED),
Query::Bind("id", &BaseModel::id_, Query::Binding::OPTIONAL),
Query::Bind("uid", &BaseModel::uid_, Query::Binding::REQUIRED),
Query::Bind("guid", &BaseModel::guid_, Query::Binding::OPTIONAL)
},
Query::Join{},
Query::OrderBy{},
nullptr
};
// in workspace.h:
static const Query Workspace::query {
Query::Table{"workspaces"},
Query::Columns{
Query::Bind("name", &Workspace::name_, Query::Binding::REQUIRED),
Query::Bind("premium", &Workspace::premium_, Query::Binding::REQUIRED),
Query::Bind("only_admins_may_create_projects", &Workspace::only_admins_may_create_projects_, Query::Binding::REQUIRED),
Query::Bind("admin", &Workspace::admin_, Query::Binding::REQUIRED),
Query::Bind("projects_billable_by_default", &Workspace::projects_billable_by_default_, Query::Binding::REQUIRED),
Query::Bind("is_business", &Workspace::business_, Query::Binding::REQUIRED),
Query::Bind("locked_time", &Workspace::locked_time_, Query::Binding::REQUIRED),
},
Query::Join{},
Query::OrderBy{"name"},
&BaseModel::query
};
It's especially worth noticing that the Query instance in Workspace
is referencing the one in BaseModel
. That is because the id
, uid
, guid
and local_id
fields are now loaded in the BaseModel
(to avoid handling them in all models individually).
A different important difference to how things are done now is that previously, we created an empty Workspace
instance and then used setters to modify its properties. Not doing that is beneficial especially in two ways:
- We can find the right place in the
ProtectedContainer
right when constructing the value (no shifting around after using the setter) - Calling setters is more expensive than writing to the properties directly (we don't want to call
SetDirty()
when loading the values from the DB anyway).
Having the database model defined inside the class that's represented by it also benefits us by letting us use a single template method to load all data - reduces boilerplate code in the Database
class that's pretty large now.
The constructor now can look like this:
Workspace(ProtectedBase *container, Poco::Data::RecordSet &rs)
: BaseModel(container, rs)
{
for (size_t i = 0; i < query.ColumnCount(); i++) {
bool result = query.columns_[i].load(this, rs, query.Offset() + i);
}
ClearDirty();
}
And the universal loading method in Database
is much simpler (simplified code without error handling, full source is at the end of the database.cc
file):
Poco::Data::Statement select(*session_);
select << ProtectedModel<Workspace>::GetSelect("uid"), useRef(UID);
Poco::Data::RecordSet recordset(select);
while (!select.done()) {
select.execute();
bool more = recordset.moveFirst(); {
while (more) {
auto model = ProtectedModel<Workspace>::create(recordset);
// model now contains a new Workspace instance with data loaded from the current line pointed to by recordset
// everything is handled automatically in the constructor (show above) and loaded according to the description in `Query`
This is now used for Workspace only, to verify everything works right (which it doesn't because it's not possible to compile the code using MSVC).
🕶️ Types of changes
- New (internal) feature (non-breaking change which adds functionality to the library)
👫 Relationships
Part of the Kingfisher project
🔎 Review hints
This is a preview. Test the functionality and tell me your opinions on the code.