Primary keys
Integral to most of these operations is an ID parameter used as the primary key in our table. To support the persistence of our entities using this new database controller, we need to add a property to our Entity class that uniquely identifies an instance of that entity.
In entity.cpp, add a member variable to Entity::Implementation:
QString id;
Then, initialize it in the constructor:
Implementation(Entity* _entity, IDatabaseController* _databaseController, const QString& _key) : entity(_entity) , databaseController(_databaseController) , key(_key) , id(QUuid::createUuid().toString()) { }
When we instantiate a new Entity, we need to generate a new unique ID, and we use the QUuid class to this for us with the createUuid() method. A Universally Unique Identifier (UUID) is essentially a randomly generated number that we then convert to a string in the “{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}" format, where "x" is a hex digit. You will need to #include <QUuid>.
Next, provide a public accessor method for it:
const QString& Entity::id() const { return implementation->id; }
The challenge now is that if we are creating an Entity that already has an ID (for example, loading a client from the database), we need some mechanism for overwriting the generated ID value with the known value. We’ll do this in the update() method:
void Entity::update(const QJsonObject& jsonObject) { if (jsonObject.contains("id")) { implementation->id = jsonObject.value("id").toString(); } … }
Similarly, when we serialize the object to JSON, we need to include the ID too:
QJsonObject Entity::toJson() const { QJsonObject returnValue; returnValue.insert("id", implementation->id); …
}
Great! This gives us automatically generated unique IDs for all of our data models, which we can use as the primary key in our database table. However, a common usecase with database tables is that there is actually an existing field that is a great candidate for use as a primary key, for example, a National Insurance or Social Security number, an account reference, or site ID. Let’s add a mechanism for specifying a data decorator to use as the ID that will override the default UUID, if set.
In our Entity class, add a new private member in Implementation:
class Entity::Implementation { ... StringDecorator* primaryKey{nullptr}; ... }
You will need to #include the StringDecorator header. Add a protected mutator method to set it:
void Entity::setPrimaryKey(StringDecorator* primaryKey)
{
implementation->primaryKey = primaryKey;
}
We can then tweak our id() method to return us the primary key value if appropriate, otherwise default to the generated UUID value:
const QString& Entity::id() const { if(implementation->primaryKey != nullptr && !implementation->primaryKey->value().isEmpty()) { return implementation->primaryKey->value(); } return implementation->id; }
Then, in the client.cpp constructor, after we have instantiated all the data decorators, we can specify that we want to use the reference field as our primary key:
Client::Client(QObject* parent) : Entity(parent, "client") { ... setPrimaryKey(reference); }
Let’s add a couple of tests to verify this behavior. We’ll verify that if a reference value is set, the id() method returns that value, otherwise it returns a generated UUID loosely of the “{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}" format.
In client-tests.h of the cm-tests project, add two new tests in the private slots scope:
void id_givenPrimaryKeyWithNoValue_returnsUuid(); void id_givenPrimaryKeyWithValue_returnsPrimaryKey();
Then, implement the tests in client-tests.cpp:
void ClientTests::id_givenPrimaryKeyWithNoValue_returnsUuid() { Client testClient(this);
// Using individual character checks QCOMPARE(testClient.id().left(1), QString("{")); QCOMPARE(testClient.id().mid(9, 1), QString("-")); QCOMPARE(testClient.id().mid(14, 1), QString("-")); QCOMPARE(testClient.id().mid(19, 1), QString("-")); QCOMPARE(testClient.id().mid(24, 1), QString("-")); QCOMPARE(testClient.id().right(1), QString("}"));
// Using regular expression pattern matching QVERIFY(QRegularExpression("\{.{8}-(.{4})-(.{4})-(.{4})-(.
{12})\}").match(testClient.id()).hasMatch()); }
void ClientTests::id_givenPrimaryKeyWithValue_returnsPrimaryKey() { Client testClient(this, QJsonDocument::fromJson(jsonByteArray).object()); QCOMPARE(testClient.reference->value(), QString("CM0001")); QCOMPARE(testClient.id(), testClient.reference->value()); }
Note that the checks are effectively performed twice in the first test just to demonstrate a couple of different approaches you can take. First, we check using individual character matches (‘{‘, ‘-’, and ‘}’), which is quite long-winded but easy for other developers to read and understand. Then, we perform the check again using Qt’s regular expression helper class. This is much shorter but more difficult to parse for normal humans who don’t speak regular expression syntax.
Build and run the tests, and they should validate the changes we have just implemented.