Skip to content
This repository has been archived by the owner on Feb 13, 2024. It is now read-only.

[PREVIEW] When loading from the database, initialize everything right in the constructor #3928

Open
wants to merge 17 commits into
base: kingfisher/master
Choose a base branch
from

Conversation

MartinBriza
Copy link
Contributor

@MartinBriza MartinBriza commented Mar 25, 2020

📒 Description

This change consists of three main parts:

  1. The src/ folder is now reorganized, the src/model and src/database folders were added to contain models and database-related classes, respectively. Also, some helper classes were moved to the src/util folder.
  2. The internal database now contains a guid field for all tables representing BaseModel-derived classes.
  3. Loading from the database is now being done right in the BaseModel constructor using the newly added BaseModelQuery 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:

  1. We can find the right place in the ProtectedContainer right when constructing the value (no shifting around after using the setter)
  2. 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.

@NghiaTranUIT NghiaTranUIT self-requested a review March 25, 2020 13:21
I spent 2 days trying to make this work in Windows but due to MSVC bugs,
I have to revert to just doing it with strings and defining columns in
the constructor as well
Notice how the deleted number count is higher than added now
@MartinBriza MartinBriza force-pushed the kingfisher/unify-constructors branch from fcc8663 to 9737ca5 Compare March 31, 2020 07:26
@NghiaTranUIT NghiaTranUIT removed their request for review May 14, 2020 08:58
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
1 participant