Drupal rules line items - drupal-7

I'm doing something very simple. I have an order where rules fires on
completed checkout.
I'm trying to iterate through the existing line items and schedule recurring billing based on a text list that has either yearly or monthly.
It wasn't working so I broke it down it its bases of elements in the actions.
Here is my exported rule:
{ "rules_schedule_next_billing_cloned_" : {
"LABEL" : "schedule next billing (cloned)",
"PLUGIN" : "reaction rule",
"OWNER" : "rules",
"REQUIRES" : [ "rules", "commerce_checkout" ],
"ON" : { "commerce_checkout_complete" : [] },
"IF" : [
{ "entity_has_field" : {
"entity" : [ "commerce-order:commerce-line-items:0" ],
"field" : "commerce_product"
}
},
{ "entity_has_field" : {
"entity" : [ "commerce-order:commerce-line-items:0:commerce-product" ],
"field" : "field_cycle_start"
}
},
{ "entity_has_field" : {
"entity" : [ "commerce-order:commerce-line-items:0:commerce-product" ],
"field" : "field_cycle_period_list"
}
},
{ "entity_has_field" : {
"entity" : [ "commerce-order:commerce-line-items:0:commerce-product" ],
"field" : "field_client"
}
},
{ "entity_is_of_bundle" : {
"entity" : [ "commerce-order:commerce-line-items:0:commerce-product" ],
"type" : "commerce_product",
"bundle" : { "value" : { "recurring_service" : "recurring_service" } }
}
}
],
"DO" : [
{ "LOOP" : {
"USING" : { "list" : [ "commerce-order:commerce-line-items" ] },
"ITEM" : { "cycled_billing_charge" : "cycled billing charge" },
"DO" : [
{ "drupal_message" : { "message" : "the period is [commerce-order:commerce-line-items:0:commerce-product:field-cycle-period-list]" } }
]
}
}
]
}
}
What's happening is it iterates through the proper amount of line items but it isn't incrementing; it is only messaging the first line item it sees.
I have looked at several articles and I have done some debugging. I'm at a very rudimentary level.

Your Rule seems to "work as implemented", since within your "drupal_message", you're using commerce-order:commerce-line-items:0... (which refers to your very first line item, as per the "0" at the end). Instead, you should use the "current item being processed" (for which you use the variable with machine name cycled_billing_charge)
Try replacing that "commerce-order:commerce-line-items:0:commerce-product:field-cycle-period-list" in your "drupal_message" by (simply) "cycled_billing_charge:commerce-product:field-cycle-period-list", so that your Rules loop looks something like so (also in Rules export format):
{ "LOOP" : {
"USING" : { "list" : [ "commerce-order:commerce-line-items" ] },
"ITEM" : { "cycled_billing_charge" : "cycled billing charge" },
"DO" : [
{ "drupal_message" : { "message" : "the period is [cycled_billing_charge:commerce-product:field-cycle-period-list]" } }
]
}
}

Related

Query to update an array field by using another array field of same document in mongodb

Scenario
I've the following document from Chat collection with an array of messages and members in the chat.
And for each message, there will be status field which will store the delivered and read timestamp with respect to users.
{
"_id" : ObjectId("60679797b4365465745065b2"),
"members" : [
ObjectId("604e02033f4fc07b6b82771c"),
ObjectId("6056ef4630d7b103d8043abd"),
ObjectId("6031e3dce8934f11f8c9a79c")
],
"isGroup" : true,
"createdAt" : 1617401743720.0,
"updatedAt" : 1617436504453.0,
"messages" : [
{
"createdAt" : 1617401743719.0,
"updatedAt" : 1617401743719.0,
"_id" : ObjectId("60679797b4365465745065b3"),
"body" : "This is test message",
"senderId" : ObjectId("6031e3dce8934f11f8c9a79c"),
"status" : []
}
]
}
So, I want to insert the following data, into messages.status array, to know when the message is received/read by the member.
{
receiverId: <member of chat>
deliveredAt: <timestamp>
readAt: <timestamp>
}
Question
How to write a query to insert the above json for each member (except the sender) in the status array by using the data from existing field?
So that, after query, the document should look like this:
{
"_id" : ObjectId("60679797b4365465745065b2"),
"members" : [
ObjectId("604e02033f4fc07b6b82771c"),
ObjectId("6056ef4630d7b103d8043abd"),
ObjectId("6031e3dce8934f11f8c9a79c")
],
"isGroup" : true,
"createdAt" : 1617401743720.0,
"updatedAt" : 1617436504453.0,
"messages" : [
{
"createdAt" : 1617401743719.0,
"updatedAt" : 1617401743719.0,
"_id" : ObjectId("60679797b4365465745065b3"),
"body" : "This is test message",
"senderId" : ObjectId("6031e3dce8934f11f8c9a79c"),
"status" : [{
"receiverId": ObjectId("604e02033f4fc07b6b82771c")
"deliveredAt": <timestamp>
"readAt": <timestamp>
}, {
"receiverId": ObjectId("6056ef4630d7b103d8043abd")
"deliveredAt": <timestamp>
"readAt": <timestamp>
}]
}
]
}
Edit
I'm able to do this for static data.
Link: https://mongoplayground.net/p/LgVPfRoXL5p
For easy understanding: I've to map the members array and insert it into the status field of the messages
MongoDB Version: 4.0.5
You can use the $function operator to define custom functions to implement behavior not supported by the MongoDB Query Language. So along with updates-with-aggregate-pipeline and $function you can update messages.status array with only receiver's details as shown below:
NOTE: Works only with MongoDB version >= 4.4.
Try this:
let messageId = ObjectId("60679797b4365465745065b3");
db.chats.update(
{ "messages._id": messageId },
[
{
$set: {
"messages": {
$map: {
input: "$messages",
as: "message",
in: {
$cond: {
if: { $eq: ["$$message._id", messageId] },
then: {
$function: {
body: function (message, members) {
message.status = [];
for (let i = 0; i < members.length; i++) {
if (message.senderId.valueOf() != members[i].valueOf()) {
message.status.push({
receiverId: members[i],
deliveredAt: new Date().getTime(),
readAt: new Date().getTime()
})
}
}
return message;
},
args: ["$$message", "$members"],
lang: "js"
}
},
else: "$$message"
}
}
}
}
}
}
]
);
Output:
{
"_id" : ObjectId("60679797b4365465745065b2"),
"members" : [
ObjectId("604e02033f4fc07b6b82771c"),
ObjectId("6056ef4630d7b103d8043abd"),
ObjectId("6031e3dce8934f11f8c9a79c")
],
"isGroup" : true,
"createdAt" : 1617401743720,
"updatedAt" : 1617436504453,
"messages" : [
{
"_id" : ObjectId("60679797b4365465745065b3"),
"createdAt" : 1617401743719,
"updatedAt" : 1617401743719,
"body" : "This is test message",
"senderId" : ObjectId("6031e3dce8934f11f8c9a79c"),
"status" : [
{
"receiverId" : ObjectId("604e02033f4fc07b6b82771c"),
"deliveredAt" : 1617625735318,
"readAt" : 1617625735318
},
{
"receiverId" : ObjectId("6056ef4630d7b103d8043abd"),
"deliveredAt" : 1617625735318,
"readAt" : 1617625735318
}
]
},
{
"_id" : ObjectId("60679797b4365465745065b4"),
"createdAt" : 1617401743719,
"updatedAt" : 1617401743719,
"body" : "This is test message",
"senderId" : ObjectId("6031e3dce8934f11f8c9a79d"),
"status" : [ ]
}
]
}
Demo - https://mongoplayground.net/p/FoOvxXp6nji
https://docs.mongodb.com/manual/reference/operator/update/positional-filtered/
The filtered positional operator $[] identifies the array elements that match the arrayFilters conditions for an update operation, e.g.
db.collection.update({
"messages.senderId": "6031e3dce8934f11f8c9a79c" // query
},
{
"$push": {
"messages.$[m].status": [ // push into the matching element of arrayFilters
{
"receiverId": ObjectId("604e02033f4fc07b6b82771c")
},
{
"receiverId": ObjectId("6056ef4630d7b103d8043abd")
}
]
}
},
{
arrayFilters: [
{
"m.senderId": "6031e3dce8934f11f8c9a79c" // matches array element where senderId is 6031e3dce8934f11f8c9a79c
}
]
})
Note- add index to messages.senderId for performance

How to remove a document inside an array in mongodb using $pull

I have the following document structure
{
"_id" : ObjectId("5ffef283f1f06ff8524aa2c2"),
"applicationName" : "TestApp",
"pName" : "",
"environments" : [],
"stages" : [],
"createdAt" : ISODate("2021-01-15T09:51:35.546Z"),
"workflows" : [
[
{
"pName" : "Test1",
"wName" : "TestApp_Test1",
"agent" : ""
},
{
"pName" : "Test2",
"wName" : "TestApp_Test2",
"agent" : ""
}
],
[
{
"pName" : "Test1",
"wName" : "TestApp_Test1",
"agent" : ""
}
]
],
"updatedAt" : Date(-62135596800000)
}
I wish to remove the occurrences of
{
"pName" : "Test1",
"wName" : "TestApp_Test1",
"agent" : ""
}
The resultant document should look like
{
"_id" : ObjectId("5ffef283f1f06ff8524aa2c2"),
"applicationName" : "TestApp",
"pName" : "",
"environments" : [],
"stages" : [],
"createdAt" : ISODate("2021-01-15T09:51:35.546Z"),
"workflows" : [
[
{
"pName" : "Test2",
"wName" : "TestApp_Test2",
"agent" : ""
}
]
],
"updatedAt" : Date(-62135596800000)
}
I've tried the below mongo query
db.getCollection('workflows').update({_id:ObjectId('5ffef283f1f06ff8524aa2c2')},
{$pull:{workflows: { $elemMatch: {pipelineName: 'Test1'}}}} )
This is removing all the documents from workflows field including Test2 since Test1 is matched.
How can we remove only the entries for Test1 and keep the others?
You can do it using the positional operator "$[]" :
db.getCollection('workflows').update({_id: ObjectId("5ffef283f1f06ff8524aa2c2") }, {$pull: {"workflows.$[]":{pName:"Test1" } } } )
but the schema looks abit strange and after the update you will have empty arrays inside workflows if all elements got deleted in the sub-array.
To fix the empty sub-arrays you will need to perform second operation to remove them:
db.getCollection('workflows').update({_id: ObjectId("5ffef283f1f06ff8524aa2c2") }, {$pull: {"workflows":[] } } )
You cannot use $elemMatch as it returns the first matching element in the array.
I am not sure there is another best way to do this with the provided schema design.
play
db.collection.aggregate({
"$unwind": "$workflows"
},
{
"$unwind": "$workflows"
},
{
"$match": {
"workflows.pName": {
"$ne": "Test1"
}
}
},
{
"$group": {
"_id": "$_id",
workflows: {
$push: "$workflows"
},
applicationName: {
"$first": "$applicationName"
}
}
},
{
"$group": {
"_id": "$_id",
workflows: {
$push: "$workflows"
},
applicationName: {
"$first": "$applicationName"
}
}
})
unwind twice required to de-normalize the data
match to filter out the unnecessary doc
group twice required to bring the required output
You can save this to a collection using $out as last stage.

MongoDB: find in array of objects where items are only one value

The data is like this:
{
"_id" : ObjectId("5ae9f2188857ce20f516315c"),
"meta" : {
"participants" : [
{
"gender" : [
{
"text" : "weiblich",
"id" : "LABEL.FEMALE"
}
]
},
{
"gender" : [
{
"text" : "männlich",
"id" : "LABEL.MALE"
}
]
},
{
"gender" : [
{
"text" : "weiblich",
"id" : "LABEL.FEMALE"
}
]
}
]
}
}
{
"_id" : ObjectId("5af00e1070bb5a707634cb12"),
"meta" : {
"participants" : [
{
"gender" : [
{
"text" : "männlich",
"id" : "LABEL.MALE"
}
]
},
{
"gender" : [
{
"text" : "männlich",
"id" : "LABEL.MALE"
}
]
}
]
}
}
{
"_id" : ObjectId("5af1ef01cfd317006694a6e6"),
"meta" : {
"participants" : [
{
"gender" : [
{
"text" : "weiblich",
"id" : "LABEL.FEMALE"
}
]
},
{
"gender" : [
{
"text" : "weiblich",
"id" : "LABEL.FEMALE"
}
]
}
]
}
}
So meta.participants contains some items with different properties e.g. gender with is also an array of one item (don't ask, historic reasons; it's always one item, never two and never empty).
I need a query which returns the documents which contains only male participants (the second doc 5af00e1070bb5a707634cb12).
I already tried my luck but I can't get it right.
These queries give me every doc which has a male participant:
{'meta.participants.gender.id': 'LABEL.MALE'}
{'meta.participants.gender': { $elemMatch: {id: 'LABEL.MALE'}}}
These queries give me 0 results..
{'meta.participants.gender': {id: 'LABEL.MALE'}}
{'meta.participants.gender[0]': { $elemMatch: {id: 'LABEL.MALE'}}}
Try the below:
db.collection.find({
"meta.participants": {
"$not": {
"$elemMatch": {
"gender.id": {
"$nin": [
"LABEL.MALE"
]
}
}
}
}
})
The query to retrieve documents with gender.id field do not have LABEL.FEMALE.
db.test.find( { "meta.participants.gender.id": { $ne: "LABEL.FEMALE" } } )
As such you don't need to use the $elemMatch for Single Query Condition (but, it not an error and the results will be same).

MongoDB update $ update operator with multiple array selector

I'm trying to update an element inside an array using the $ update operator. The document contains 2 array fields and I have to query both to select the correct document.
The collection is called locations and the document looks like this:
{
"_id" : "XqEQYpitGFG3nnf3C",
"wallpapers" : [
{
"_metadata" : {
"master" : "vwb22W4MhkqtvAp89",
"isMaster" : false
},
"role" : "master",
"_id" : ""
},
{
"_metadata" : {
"master" : "vwb22W4MhkqtvAp89",
"isMaster" : false
},
"role" : "clone",
"_id" : ""
},
{
"_metadata" : {
"master" : "vwb22W4MhkqtvAp89",
"isMaster" : false
},
"role" : "pod",
"_id" : ""
}
],
"ancestors" : [
"vwb22W4MhkqtvAp89",
"tqzqfum9uMs47xcHW",
"b4d83aqTkq6TGvXts",
"XqEQYpitGFG3nnf3C"
]
}
The update operator looks like this:
db.getCollection('locations').update(
{
"ancestors": "b4d83aqTkq6TGvXts",
"wallpapers": {
"$elemMatch": {
"role": "clone",
"_metadata.master": "vwb22W4MhkqtvAp89"
}
}
},
{
"$set": {
"wallpapers.$": {
"_id": "D33WNZh7Bg4itPdhk",
"_metadata": {
"master": "b4d83aqTkq6TGvXts",
"isMaster": false
},
"role": "clone"
}
}
}
)
So I would like to have the element in the wallpapers array replaced. However, the result I get is:
{
"_id" : "XqEQYpitGFG3nnf3C",
"wallpapers" : [
{
"_metadata" : {
"master" : "vwb22W4MhkqtvAp89",
"isMaster" : false
},
"role" : "master",
"_id" : ""
},
{
"_metadata" : {
"master" : "vwb22W4MhkqtvAp89",
"isMaster" : false
},
"role" : "clone",
"_id" : ""
},
{
"_id" : "D33WNZh7Bg4itPdhk",
"_metadata" : {
"master" : "b4d83aqTkq6TGvXts",
"isMaster" : false
},
"role" : "clone"
}
],
"ancestors" : [
"vwb22W4MhkqtvAp89",
"tqzqfum9uMs47xcHW",
"b4d83aqTkq6TGvXts",
"XqEQYpitGFG3nnf3C"
]
}
So it replaces the wrong element.
It seems that the position .$ refers to is the one from the selector of the ancestors field.
Is this a bug or a limitation? Is there a solution (e.g. anything like .$1 and .$2 ?
I'm using MongoDB 3.2.6.
In your update operation query, use the dot notation as:
db.getCollection('locations').update(
{
"ancestors": "b4d83aqTkq6TGvXts",
"wallpapers.role": "clone", // <--- dot notation
"wallpapers._metadata.master": "vwb22W4MhkqtvAp89" // <-- dot notation
},
{
"$set": {
"wallpapers.$": {
"_id": "D33WNZh7Bg4itPdhk",
"_metadata": {
"master": "b4d83aqTkq6TGvXts",
"isMaster": false
},
"role": "clone"
}
}
}
)

Mongodb filtering deeply nested arrays with aggregation framework

Thank you in advance for looking at this...
My document:
{
"_id" : { "$oid" : "550b2873e9dd90068070c31b" },
"dateCreated" : { "$date" : 1426794611867 },
"sections" : [
{
"_id" : { "$oid" : "550b2881e9dd90068070c31d" },
"index" : 0,
"name" : "Section 2",
"slides" : [
{
"_id" : { "$oid" : "550b288ce9dd90068070c321" },
"index" : 0,
"status" : "Unpublished"
},
{
"_id" : { "$oid" : "55105b87e9dd90068033ba4a" },
"index" : 1,
"status" : "Published"
}
]
},
{
"_id" : { "$oid" : "550b287ae9dd90068070c31c" },
"index" : 1,
"name" : "Section 1",
"slides" : [
{
"_id": { "$oid": "550b2888e9dd90068070c31f" },
"index" : 0,
"status": "Unpublished"
},
{
"_id" : { "$oid" : "550b288be9dd90068070c320" },
"index" : 1,
"status" : "Unpublished"
}
]
}
]
}
and the desired result where we return only sections with at least one published slide containing all the published slides
{
"_id" : { "$oid" : "550b2873e9dd90068070c31b" },
"dateCreated" : { "$date" : 1426794611867 },
"sections" : [
{
"_id" : { "$oid" : "550b2881e9dd90068070c31d" },
"index" : 0,
"name" : "Section 2",
"slides" : [
{
"_id" : { "$oid" : "55105b87e9dd90068033ba4a" },
"index" : 1,
"status" : "Published"
}
]
}
]
}
So far I have this:
col.aggregate
([
{$match : {'name': name}},
{$unwind:'$sections'},
{$unwind:'$sections.slides'},
{$match:{'$sections.slides.status': "Published"}},
{$group:{_id:'$_id', slides:{$push:'$slides'}}}
])
I am having a hard time wrapping my head around the grouping, specifically with nesting each slides array in its parent section array. Also, I would like to omit any empty sections.
I have never used the aggregate method before but I believe this is the correct approach. The mongo docs are a bit sparse when it comes to deeply nested arrays.
Read about $redact stage of MongoDB's pipeline.
At first iteration I did this:
.aggregate({
$redact: {
$cond: {
if: { $eq: ["$status", "Unpublished"] },
then: "$$PRUNE",
else: "$$DESCEND"
}
}
}, {
$redact: {
$cond: {
if: { $eq: ["$slides", []] },
then: "$$PRUNE",
else: "$$DESCEND"
}
}
})
You can try to rewrite it in more elegant way. I'm pretty sure there is a way to make it with single $redact stage, but you have to skim over the manual and find proper operators. Hope it'll help. Good luck.

Resources