From 19a551e09264513766c569770539d55b6cc00d35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Nowacki?= Date: Fri, 4 Apr 2014 15:19:47 +0200 Subject: Allow update all item's properties in one EnginioModel::setData call This change adds a new preconfigured role to EnginioModel, which can be used to update multiple properties of an item in one function call. [ChangeLog] EnginioModel is able to update multiple properties of an item in one setData function call. It can be achieved by using Enginio::JsonObjectRole. Change-Id: Ibc640d44f28d40fa349c6efdf65e3bb321a4fc79 Reviewed-by: Frederik Gladhorn --- src/enginio_client/enginio.h | 1 + src/enginio_client/enginiobasemodel_p.h | 22 ++- src/enginio_client/enginiomodel.cpp | 30 ++++ src/enginio_client/enginiomodel.h | 1 + src/enginio_client/enginiostring_p.h | 1 + tests/auto/enginiomodel/tst_enginiomodel.cpp | 202 ++++++++++++++++++++++++--- 6 files changed, 229 insertions(+), 28 deletions(-) diff --git a/src/enginio_client/enginio.h b/src/enginio_client/enginio.h index fb3088b..666678d 100644 --- a/src/enginio_client/enginio.h +++ b/src/enginio_client/enginio.h @@ -91,6 +91,7 @@ public: UpdatedAtRole, IdRole, ObjectTypeRole, + JsonObjectRole, CustomPropertyRole = Qt::UserRole + 10 // the first fully dynamic role }; diff --git a/src/enginio_client/enginiobasemodel_p.h b/src/enginio_client/enginiobasemodel_p.h index c815aad..dbcb403 100644 --- a/src/enginio_client/enginiobasemodel_p.h +++ b/src/enginio_client/enginiobasemodel_p.h @@ -830,11 +830,21 @@ public: EnginioReplyState *setDataNow(const int row, const QVariant &value, int role, const QJsonObject &oldObject, const QString &id) { Q_ASSERT(!id.isEmpty()); - const QString roleName(_roles.value(role)); - Q_ASSERT(!roleName.isEmpty()); QJsonObject deltaObject; QJsonObject newObject = oldObject; - deltaObject[roleName] = newObject[roleName] = QJsonValue::fromVariant(value); + if (role != Enginio::JsonObjectRole) { + const QString roleName(_roles.value(role)); + Q_ASSERT(!roleName.isEmpty()); + deltaObject[roleName] = newObject[roleName] = QJsonValue::fromVariant(value); + } else { + const QJsonObject updateObject = value.toJsonObject(); + if (updateObject.isEmpty()) { + QNetworkReply *nreply = new EnginioFakeReply(_enginio, EnginioClientConnectionPrivate::constructErrorMessage(EnginioString::EnginioModel_Trying_to_update_an_object_with_unknown_role)); + return _enginio->createReply(nreply); + } + for (QJsonObject::const_iterator i = updateObject.constBegin(); i != updateObject.constEnd(); ++i) + deltaObject[i.key()] = i.value(); + } deltaObject[EnginioString::id] = id; deltaObject[EnginioString::objectType] = newObject[EnginioString::objectType]; ObjectAdaptor aDeltaObject(deltaObject); @@ -876,11 +886,11 @@ public: const QJsonObject object = _data.at(row).toObject(); if (!object.isEmpty()) { + if (role == Qt::DisplayRole || role == Enginio::JsonObjectRole) + return _data.at(row); const QString roleName = _roles.value(role); if (!roleName.isEmpty()) return object[roleName]; - else if (role == Qt::DisplayRole) - return _data.at(row); } return QVariant(); @@ -1021,7 +1031,7 @@ struct EnginioModelPrivateT : public EnginioBaseModelPrivate Reply *remove(int row) { return static_cast(Base::remove(row)); } Reply *setValue(int row, const QString &role, const QVariant &value) { return static_cast(Base::setValue(row, role, value)); } Reply *reload() { return static_cast(Base::reload()); } - + Reply *setData(const int row, const QVariant &value, int role) { return static_cast(Base::setData(row, value, role)); } bool queryIsEmpty() const Q_DECL_OVERRIDE { return ObjectAdaptor(_query, static_cast(_enginio)).isEmpty(); diff --git a/src/enginio_client/enginiomodel.cpp b/src/enginio_client/enginiomodel.cpp index 722a2fc..3ff34a3 100644 --- a/src/enginio_client/enginiomodel.cpp +++ b/src/enginio_client/enginiomodel.cpp @@ -404,6 +404,7 @@ EnginioBaseModel::~EnginioBaseModel() \value ObjectTypeRole \c What is item type \value SyncedRole \c Mark if an item is in sync with the backend \value CustomPropertyRole \c The first role id that may be used for dynamically created roles. + \value JsonObjectRole \c Object like representation of an item \omitvalue InvalidRole Additionally EnginioModel supports dynamic roles which are mapped @@ -562,6 +563,35 @@ EnginioReply *EnginioModel::setData(int row, const QVariant &value, const QStrin return d->setValue(row, role, value); } +/*! + \overload + Update a \a value on \a row of this model's local cache + and send an update request to the Enginio backend. + + All properties of the \a value will be used to update the item in \a row. + This can be useful to update multiple item's properties with one request. + + \return reply from backend + \sa EnginioClient::update() +*/ +EnginioReply *EnginioModel::setData(int row, const QJsonObject &value) +{ + Q_D(EnginioModel); + if (Q_UNLIKELY(!d->enginio())) { + qWarning("EnginioModel::setData(): Enginio client is not set"); + return 0; + } + + if (unsigned(row) >= unsigned(d->rowCount())) { + EnginioClientConnectionPrivate *client = EnginioClientConnectionPrivate::get(d->enginio()); + QNetworkReply *nreply = new EnginioFakeReply(client, EnginioClientConnectionPrivate::constructErrorMessage(EnginioString::EnginioModel_setProperty_row_is_out_of_range)); + EnginioReply *ereply = new EnginioReply(client, nreply); + return ereply; + } + + return d->setData(row, value, Enginio::JsonObjectRole); +} + Qt::ItemFlags EnginioBaseModel::flags(const QModelIndex &index) const { return QAbstractListModel::flags(index) | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; diff --git a/src/enginio_client/enginiomodel.h b/src/enginio_client/enginiomodel.h index f3cb0ca..538a532 100644 --- a/src/enginio_client/enginiomodel.h +++ b/src/enginio_client/enginiomodel.h @@ -80,6 +80,7 @@ public: Q_INVOKABLE EnginioReply *append(const QJsonObject &value); Q_INVOKABLE EnginioReply *remove(int row); Q_INVOKABLE EnginioReply *setData(int row, const QVariant &value, const QString &role); + Q_INVOKABLE EnginioReply *setData(int row, const QJsonObject &value); using EnginioBaseModel::setData; Q_INVOKABLE EnginioReply *reload(); diff --git a/src/enginio_client/enginiostring_p.h b/src/enginio_client/enginiostring_p.h index 559f4a3..dcc45a2 100644 --- a/src/enginio_client/enginiostring_p.h +++ b/src/enginio_client/enginiostring_p.h @@ -128,6 +128,7 @@ F(EnginioModel_was_removed_before_this_request_was_prepared, "EnginioModel was removed before this request was prepared")\ F(EnginioModel_The_query_was_changed_before_the_request_could_be_sent, "EnginioModel: The query was changed before the request could be sent")\ F(EnginioModel_Trying_to_update_an_object_with_unknown_role, "EnginioModel: Trying to update an object with unknown role")\ + F(EnginioModel_Trying_to_update_an_item_with_an_empty_object, "EnginioModel: Trying to update an item with an empty object")\ F(Content_Range, "Content-Range")\ F(Content_Type, "Content-Type")\ F(Get, "GET")\ diff --git a/tests/auto/enginiomodel/tst_enginiomodel.cpp b/tests/auto/enginiomodel/tst_enginiomodel.cpp index 47ad81b..0dc651a 100644 --- a/tests/auto/enginiomodel/tst_enginiomodel.cpp +++ b/tests/auto/enginiomodel/tst_enginiomodel.cpp @@ -99,9 +99,11 @@ private slots: void deleteModelDurringRequests(); void updatingRoles(); void setData(); + void setJsonData(); + void data(); + void setInvalidJsonData(); void reload(); void identityChange(); - private: template void externallyRemovedImpl(); @@ -1275,25 +1277,31 @@ void tst_EnginioModel::delayedRequestBeforeInitialModelReset() EnginioClient client; client.setBackendId(_backendId); client.setServiceUrl(EnginioTests::TESTAPP_URL); - for (int i = 0; i < 24 ; ++i) { - QString objectType = "objects." + EnginioTests::CUSTOM_OBJECT1; - QJsonObject query; - query.insert("objectType", objectType); + QString objectType = "objects." + EnginioTests::CUSTOM_OBJECT1; + QJsonObject query; + query.insert("objectType", objectType); + query.insert("title", QString::fromUtf8("appendAndRemoveModel")); + + QJsonObject object; + object["title"] = "New Title"; + + for (int i = 0; i < 24 ; ++i) { EnginioModel model; QSignalSpy resetSpy(&model, SIGNAL(modelReset())); model.setQuery(query); model.setClient(&client); - query.insert("title", QString::fromUtf8("appendAndRemoveModel")); EnginioReply *append1 = model.append(query); EnginioReply *append2 = model.append(query); - EnginioReply *update = model.setData(0, QString::fromUtf8("appendAndRemoveModel1"), "title"); + EnginioReply *update1 = model.setData(0, QString::fromUtf8("appendAndRemoveModel1"), "title"); + EnginioReply *update2 = model.setData(0, object); EnginioReply *remove = model.remove(1); - QTRY_VERIFY(append1->isFinished() && append2->isFinished() && remove->isFinished() && update->isFinished()); + QTRY_VERIFY(append1->isFinished() && append2->isFinished() && remove->isFinished() && update1->isFinished() && update2->isFinished()); QVERIFY(!append1->isError() || append1->errorString().contains("EnginioModel: The query was changed before the request could be sent")); QVERIFY(!append2->isError() || append2->errorString().contains("EnginioModel: The query was changed before the request could be sent")); - QVERIFY(!update->isError() || update->errorString().contains("EnginioModel: The query was changed before the request could be sent")); + QVERIFY(!update1->isError() || update1->errorString().contains("EnginioModel: The query was changed before the request could be sent")); + QVERIFY(!update2->isError() || update1->errorString().contains("EnginioModel: The query was changed before the request could be sent")); QVERIFY(!remove->isError() || remove->errorString().contains("EnginioModel: The query was changed before the request could be sent")); if (resetSpy.isEmpty()) break; @@ -1353,10 +1361,15 @@ void tst_EnginioModel::deleteModelDurringRequests() EnginioModel model; model.setQuery(query); model.setClient(&client); + query.insert("title", QString::fromUtf8("deleteModelDurringRequests")); + QJsonObject object = query; + object.insert("count", 111); + replies.append(model.append(query)); replies.append(model.append(query)); replies.append(model.setData(0, QString::fromUtf8("deleteModelDurringRequests1"), "title")); + replies.append(model.setData(1, object)); replies.append(model.remove(0)); } @@ -1449,6 +1462,21 @@ void tst_EnginioModel::updatingRoles() QCOMPARE(model.roleNames()[CustomModel::InvalidRole], QByteArray("objectType")); } +struct CustomModel: public EnginioModel { + enum Roles { + TitleRole = Enginio::CustomPropertyRole, + CountRole + }; + + virtual QHash roleNames() const Q_DECL_OVERRIDE + { + QHash roles = EnginioModel::roleNames(); + roles.insert(TitleRole, "title"); + roles.insert(CountRole, "count"); + return roles; + } +}; + void tst_EnginioModel::setData() { QString propertyName = "title"; @@ -1460,18 +1488,7 @@ void tst_EnginioModel::setData() client.setBackendId(_backendId); client.setServiceUrl(EnginioTests::TESTAPP_URL); - struct Model: public EnginioModel { - enum Roles { - TitleRole = Enginio::CustomPropertyRole - }; - - virtual QHash roleNames() const Q_DECL_OVERRIDE - { - QHash roles = EnginioModel::roleNames(); - roles.insert(TitleRole, "title"); - return roles; - } - } model; + CustomModel model; model.disableNotifications(); model.setQuery(query); @@ -1505,7 +1522,148 @@ void tst_EnginioModel::setData() QVERIFY(!model.setData(model.index(-1, 1), QVariant())); // make a correct setData call - QVERIFY(model.setData(model.index(0), QString::fromLatin1("1111"), Model::TitleRole)); + QVERIFY(model.setData(model.index(0), QString::fromLatin1("1111"), CustomModel::TitleRole)); +} + +void tst_EnginioModel::setJsonData() +{ + EnginioClient client; + client.setBackendId(_backendId); + client.setServiceUrl(EnginioTests::TESTAPP_URL); + + QString propertyName1 = "title"; + QString propertyName2 = "count"; + QString objectType = "objects." + EnginioTests::CUSTOM_OBJECT1; + QJsonObject query; + query.insert("objectType", objectType); + + CustomModel model; + + model.disableNotifications(); + model.setQuery(query); + + { // init the model + QSignalSpy spy(&model, SIGNAL(modelReset())); + model.setClient(&client); + + QTRY_VERIFY(spy.count() > 0); + } + + if (model.rowCount() < 1) { + QJsonObject o; + o.insert(propertyName1, QString::fromLatin1("o")); + o.insert(propertyName2, 123); + o.insert("objectType", objectType); + model.append(o); + } + + QTRY_VERIFY(model.rowCount()); + + QJsonObject object = model.data(model.index(0)).toJsonValue().toObject(); + QVERIFY(!object.isEmpty()); + + object[propertyName1] = object[propertyName1].toString() + QString::fromLatin1("o"); + object[propertyName2] = object[propertyName2].toInt() + 123; + + EnginioReply *reply = model.setData(0, object); + QTRY_VERIFY(reply->isFinished()); + CHECK_NO_ERROR(reply); + + QJsonObject data = reply->data(); + QCOMPARE(data[propertyName1], object[propertyName1]); + QCOMPARE(data[propertyName2], object[propertyName2]); +} + +void tst_EnginioModel::data() +{ + EnginioClient client; + client.setBackendId(_backendId); + client.setServiceUrl(EnginioTests::TESTAPP_URL); + + QString propertyName1 = "title"; + QString propertyName2 = "count"; + QString objectType = "objects." + EnginioTests::CUSTOM_OBJECT1; + QJsonObject query; + query.insert("objectType", objectType); + + CustomModel model; + + model.disableNotifications(); + model.setQuery(query); + + { // init the model + QSignalSpy spy(&model, SIGNAL(modelReset())); + model.setClient(&client); + + QTRY_VERIFY(spy.count() > 0); + } + + if (model.rowCount() < 1) { + QJsonObject o; + o.insert(propertyName1, QString::fromLatin1("o")); + o.insert(propertyName2, 123); + o.insert("objectType", objectType); + model.append(o); + } + + QTRY_VERIFY(model.rowCount()); + QModelIndex index = model.index(0); + QJsonObject item = model.data(index, Enginio::JsonObjectRole).toJsonValue().toObject(); + QVERIFY(!item.isEmpty()); + + QCOMPARE(model.data(index, Enginio::IdRole).toJsonValue(), QJsonValue(item["id"])); + QCOMPARE(model.data(index, Enginio::CreatedAtRole).toJsonValue(), QJsonValue(item["createdAt"])); + QCOMPARE(model.data(index, Enginio::UpdatedAtRole).toJsonValue(), QJsonValue(item["updatedAt"])); +} + +void tst_EnginioModel::setInvalidJsonData() +{ + EnginioClient client; + client.setBackendId(_backendId); + client.setServiceUrl(EnginioTests::TESTAPP_URL); + + QString propertyName1 = "title"; + QString propertyName2 = "count"; + QString objectType = "objects." + EnginioTests::CUSTOM_OBJECT1; + QJsonObject query; + query.insert("objectType", objectType); + + CustomModel model; + + model.disableNotifications(); + model.setQuery(query); + + QJsonObject object; + { + QSignalSpy spy(&model, SIGNAL(modelReset())); + model.setClient(&client); + + object.insert(propertyName1, QString::fromLatin1("o")); + object.insert(propertyName2, 123); + object.insert("objectType", objectType); + + QTRY_VERIFY(spy.count() > 0); + } + + if (model.rowCount() < 1) { + QJsonObject o; + o.insert(propertyName1, QString::fromLatin1("o")); + o.insert(propertyName2, 123); + o.insert("objectType", objectType); + model.append(o); + } + + QVector replies; + replies.append(model.setData(-1, object)); + replies.append(model.setData(model.rowCount(), object)); + + QTRY_VERIFY(model.rowCount()); + replies.append(model.setData(0, QJsonObject())); + + foreach (const EnginioReply *reply, replies){ + QTRY_VERIFY(reply->isFinished()); + QVERIFY(reply->isError()); + } } struct DeleteReplyCountHelper -- cgit v1.2.1