2

Please, advice me on such a question about the Rules module.

Background: I have two types of users and 2 types on content. Each user can create one type of content. User of type2 can create a content of content_type_2 which has an entity reference field to a node of content_type_1. But this user should have a limit - such user may reference a node of content_type_1 only once.

How I implemented it:

  • rule is triggered when user_type2 is visiting content_type_2 node-add form. A NID of reference to content_type_1 is present as URL parameter.

  • rule fetches nodes by property (where author is current user)

  • for each item of the list of fetched entities is triggered another one rules component

  • component checks is current list item is of "content_type_2"

  • component checks current list item's entity reference field (nid of the referenced node) is equal to the nid value from URL parameter.

  • if both conditions are true - prevent node saving and display and error message.

It works fine and does it's job. But I worry about the following: what will happened if a user of user_type2 already has let say 10000+ of nodes of content_type_2? Does it mean that "fetch entity by property" will query for all that number of nodes at once? Can it highly slow down my site performance?

If it is true, maybe there is a better way of organizing this checking process? Will be thankful for any comment and advice!

UPD 1 (to make the question more clear):

  • User type "Client" can create nodes of type "Request" .

  • User type "Provider" can create nodes of type "Proposal".

  • "Proposal" type has entity reference field to "Request" node (only 1 value allowed, it is required).

  • Provider can create multiple number of "Proposal" nodes.

  • But he has to be restricted to create only 1 "Proposal" per a certain "Request" node. So he should not be allowed to reference a "Request" node if he has already referenced it in any of his previous Proposals.

  • Reference field is filled automatically ("Provider" can't do it by hands). "Provider" also can't edit any of published Proposals, but can delete any of them.

UPD 2 (Rules export)

Reaction Rule which is triggered at "node/add/proposal"

{ "rules_check_unique" : {
    "LABEL" : "Check Proposal is unique per Request",
    "PLUGIN" : "reaction rule",
    "OWNER" : "rules",
    "TAGS" : [ "business_custom" ],
    "REQUIRES" : [ "rules", "rules_url_argument" ],
    "ON" : { "init" : [] },
    "IF" : [
      { "data_is" : { "data" : [ "site:current-page:path" ], "value" : "node\/add\/proposal" } },
      { "rules_url_argument_argument_exists" : { "argument" : "field_request_reference" } }
    ],
    "DO" : [
      { "rules_url_argument_rules_action_get_argument_from_url" : {
          "USING" : { "type" : "integer", "argument_name" : "field_request_reference" },
          "PROVIDE" : { "argument_from_url" : { "argument_from_url" : "Argument from URL" } }
        }
      },
      { "entity_query" : {
          "USING" : {
            "type" : "node",
            "property" : "author",
            "value" : [ "site:current-user" ]
          },
          "PROVIDE" : { "entity_fetched" : { "entity_fetched" : "Fetched entity" } }
        }
      },
      { "LOOP" : {
          "USING" : { "list" : [ "entity-fetched" ] },
          "ITEM" : { "list_item" : "Current list item" },
          "DO" : [
            { "component_rules_if_proposal_is_already_posted_for_a_request" : { "cnid" : [ "argument-from-url" ], "proposal" : [ "list-item" ] } }
          ]
        }
      }
    ]
  }
}

Component Rules which check if Proposal for the referenced Request already exists

{ "rules_if_proposal_is_already_posted_for_a_request" : {
    "LABEL" : "If proposal is already posted for a request",
    "PLUGIN" : "rule",
    "OWNER" : "rules",
    "REQUIRES" : [ "rules" ],
    "USES VARIABLES" : {
      "cnid" : { "label" : "Current NID", "type" : "integer" },
      "proposal" : { "label" : "Proposal", "type" : "node" }
    },
    "IF" : [
      { "node_is_of_type" : {
          "node" : [ "proposal" ],
          "type" : { "value" : { "proposal" : "proposal" } }
        }
      },
      { "data_is" : { "data" : [ "proposal:field-request-reference:nid" ], "value" : [ "cnid" ] } }
    ],
    "DO" : [
      { "drupal_message" : {
          "message" : "You have already made a proposal for this request.",
          "type" : "error"
        }
      },
      { "redirect" : { "url" : "node\/[cnid:value]" } }
    ]
  }
}
0

2 Answers 2

2

Preface

Even though I didn't test it, I trust your rule (+ Rules Component) works. But, you really made it complicated. Moreover, it seems that your rule is using Rules Event "Drupal is initializing". You're right, you must use this event if you want to have Rules intercept something like "a node is going to be added". Because if you'd use something like "before saving content of type ABC", you wouldn't be able to STOP such saving (you would be too late).

But using that Rules Event also implies that this rule gets triggered for every (repeat every) page load on your site, and each time at least the Rules Condition to check for that node/add/proposal path has to be performed. So only because of using that Rules Event, I think you should try to go for an alternative implementation to achieve the same goal. That goal is, if I correctly interpreted your question, like so: A Provider can only reference a "Request" node in a single "Proposal".

Read on for a blueprint with some more details about how I would implement this ... (BTW: in D7).

Step 1: Flag a Request after a Proposal was submitted

Use the Flag module to create a (non-global) flag to be used for flagging a node of type "Request", so that whenever a Provider submits a proposal, the referenced Request gets flagged by that Provider. So the flag is to indicated that such provider did submit a Proposal to the specific Request.

To actually do so, simply use the Rules module to do so (this is a kind of getting started with Rules, not further explained in this answer).

Step 2: Create a view with an Entity Reference display

Create a view (with the Views module) to create a list of all the Requests that are allowed to be selected by a specific user. Some more details (specs) about the view to be created:

  • The display type of such view must be an Entity Reference display.
  • It should list all nodes of content type "Request".
  • It should have a filter like "Not flagged (with the flag from Step 1) by the current user".

Step 3: Adapt your Entity Reference field

Your entity reference field (to select from a filtered list of Requests) should be field that looks like so:

  • Field Name = Anything you want, eg field_requests_list.
  • Field Type = Entity Reference, using these additional settings for it:

    • Target type = node.
    • Number of values = 1.
    • "Mode" (within "entity selection") = Views: Filter by an entity reference view. However, before you can select an appropriate view at this point (which is eligible to do so), you first need to create a view with an Entity Reference display, or add such a display to an existing view. In your case, you should select the view created in "Step 2".
    • Pick any of the other options to define this field as you see fit.

Step 4: Disable your custom rule

After you completed the previous steps, there is no more need for your custom rule "Check Proposal is unique per Request", so at least disable that rule (and optionally just delete it). If this was the only Rule using your Rules Component, you might also want to delete that Rules Component (not needed anymore either).

Step 5: Reduce the risk to run in a new (Views) performance issue

You have a valid point in the extra comment you posted below this answer, ie:

If I have over 10000 nodes of type "Request" on the website which are not flagged by the current Provider, the view will query for all that number of stuff as I understand. Doesn't it influence the performance as well?

I don't expect such performance issue for as long as you have only like a few hundreds of nodes of type "Request". But for 10.000 nodes I'm not sure. However, if you also want to take away that risk, you could improve the above solution a bit further like so:

  • Introduce an additional (non-global) flag, which a "Provider" must use to indicated something like "I am interested in preparing a proposal for this request".
  • Improve the view in Step 2, by adding this additional flag as an additional filter of that view.

Obviously, the downside of introducing this extra flag is that the Provider must first "flag" relevant Requests. But the benefit of doing (enforcing?) so is that the number of results in your view will probably be limited to only a few (or a few dozens at max).

Bottomline

After you completed the previous steps, you will:

  • No longer use that Rule (+ related component) for which you wondered about its performance.
  • You will have relevant business logic in place so that a Provider can only reference a "Request" node in a single "Proposal" (which IMO is what you're trying to implement).

Homework

The above is just a blueprint, you may want to further refine it. E.g. what if some smart Provider tries to "unflag" a Request that the Provider (automatically) flagged before (via Step 1)? If that happens, such Provider could submit another proposal to the same Request (which is not what you'd want). Hint: intercept that event also, and don't allow that to happen ... also using ... euh ... Rules!

3
  • Many thanks for your solution! Actually I have a question Step 2 - if I have over 10000 nodes of type "Request" on the website which are not flagged by the current Provider, the view will query for all that number of stuff as I understand. Doesn't it infuelce the performance as well?
    – john
    Feb 2, 2017 at 18:59
  • @john ... Solution to the problem, changes the problem, right? Check my expanded answer (= my addition in "Step 5"). Feb 2, 2017 at 19:28
  • @john : merci for the accept! Feb 2, 2017 at 19:37
1

let say 10000+ of nodes of content_type_2? Does it mean that "fetch enitity by property" will query for all that number of nodes at once? Can it highly slow down my site perfomance?

I think you answered your own question,

  • rule fetches nodes by property (where author is current user)
  • for each item of the list of fetched entities is triggered another one rules component

Haha yea, not a good approach. A better approach:

Use the flag module and create flag for the Request nodes.

Use Rules Conditional

Then in rules, when Proposal is created >> component of request node (loaded using the Reference NID), if flagged is unflaged, flag. Else prevent node from saving and show error message.

When Proposal node is deleted >> component of request node (loaded using the Reference NID), unflag if it's flagged.

9
  • How about using the Flag module to flag such node for which you suggest storing its nid? Do you think that might be a valid alternative also? As you must know, checking if a node is yes/no flagged in Rules is a piece of cake, no? Feb 2, 2017 at 9:28
  • @Pierre.Vriens interesting idea, but the flag would have to be universal. So how would you deal with removing the 1 reference on node x?
    – No Sssweat
    Feb 2, 2017 at 9:32
  • not sure if it would make sense to you, but wouldn't "unflag" that node be the equivalent of that? disclaimer: I'm still confused about the actual question here, so sorry if this makes no sense. Feb 2, 2017 at 9:41
  • @Pierre.Vriens yes "unflag" would do, but what would be the check before that? If entity reference field is empty? but what about all the other nodes that will be created with an empty reference, since only 1 node is allowed to have it? They would trigger the unflag, causing a false positive.
    – No Sssweat
    Feb 2, 2017 at 9:46
  • Good (extra) question ... not sure about how to answer that. Except: if entity ref field is empty, that's because the user deleted the reference (= your words in your own answer). But "user deletes the reference" appears to me like something you should be able to capture as a rules event, via which maybe you could make other rules logic happen? Sorry, I'm only a kind of guessing here (need more time to digest the original question here, after OPer addresses my original comment also). Au revoir! Feb 2, 2017 at 9:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.