I thought about this when trying to stick together data in the UI. I need an argument for an action (self defined Rules data type) and want to put it together from several other provided variables.

So in the action configuration one would first select a data type to produce, then get a form where each property can be entered as value or can be assigned with the data selector. If a property is a nested complex data type itself, then we would need to expand this property further ... this could get huge and messy. I don't know if the Google Summer of Code project Rules Transformers ( http://groups.drupal.org/node/72233 ) also targets this idea.

Comments

fago’s picture

Component: Rules Core » Rules Engine

Yep. In 6.x there is a "add variable" action. I think we need something like that for 7.x too. First off make the data type configurable, then as next step we provide the input form for the data type - then in case if it is 'struct' or a sub-type of it we can turn each property into a parameter, for which the user may provide a value. For nested complex types, the user just would have to create the need complex type beforehand...

I don't think sebgil plans or has time to work on this though.

klausi’s picture

Assigned: Unassigned » klausi
StatusFileSize
new2.37 KB

I'll work on this. Here is a first step patch that adds the action info and the option list to select the data type.

fago’s picture

Great! So far so good. Does natcasesort() keep the keys? RulesPluginUI::getOptions() so far uses asort().

klausi’s picture

Yes, natcasesort() maintains key/value associations, but has the advantage of treating the values case insensitive. I experienced that "URI" was on top of the list, because it was the only data type with a capital letter, very ugly ...

klausi’s picture

StatusFileSize
new4.66 KB

Not much progress today, here is an intermediate patch.
* I re-used the form_alter method from the enttiy create action
* a bugfix was necessary to allow empty data selectors in my use case (replacement of isset() with !empty())
* nothing here for primitive variables yet, just experimented with structures and properties

fago’s picture

So far so good. If the data type has a separate input form, we should use that. Else if it has property info + a creation callback we can use that. Else, we cannot create a new data item of the type. In this case we also need to remove it from the options.

klausi’s picture

Status: Active » Needs review

Setting to needs review for a testbot check

klausi’s picture

StatusFileSize
new5.53 KB

Now including the first action execution implementation.

>> If the data type has a separate input form, we should use that.

Yes, I'm copying over the data info currently.

>> Else if it has property info + a creation callback we can use that.

Ok, looking at the creation callback is not implemented yet and is a TODO.

>>Else, we cannot create a new data item of the type. In this case we also need to remove it from the options.

Why? If we have properties we can assume it is a simple array structure and create that. If we don't have anything we can just provide a field to allow single values.

This patch is not working yet, as I'm a little bit confused about what is coming in as parameters to rules_action_data_create(). Debugging is also difficult as the class RulesState interferes with dpm() for example and breaks the output.

fago’s picture

I'd suggest to turn on the 'named parameter' feature - that way dealing with the passed arguments is much easier. You just get the keyed array $arguments and $element.

>> If the data type has a separate input form, we should use that.

Yes, I'm copying over the data info currently.

If it has an *input form*. There might be data structures coming with property info that implement their own input form. Indeed we have that already in rules with 'text_formatted'.

>Why? If we have properties we can assume it is a simple array structure and create that. If we don't have anything we can just provide a field to allow single values.

Still there is the possibility of a data type coming without any property info at all. Defaulting to simple arrays would be possible, however this makes the callback a mandatory. I don't think this is a good idea, as it might be un-needed or even un-possible in some cases.
Just using "array" for data structures not fitting into that (object trees, differently organized arrays) is not possible as that way we would create a wrong $data instance and lead to errors in further use (e.g. when used by a certain action for that type).

klausi’s picture

StatusFileSize
new6.12 KB

>> I'd suggest to turn on the 'named parameter' feature

Ah, I missed that, thanks. Now in the patch.

>> If it has an *input form*. There might be data structures coming with property info that implement their own input form. Indeed we have that already in rules with 'text_formatted'.

Sorry, I don't understand. So the property info would contain a key 'input form' => 'my_custom_form_callback'?

text_formatted comes with its own UI class in the data info. The "Modify data" action for example uses the correct inputForm() without touching the form_alter callback. When I try to create a new text_formatted variable with this patch I don't get the correct input form, so what am I missing?

Status: Needs review » Needs work

The last submitted patch, 849464-data-create.patch, failed testing.

fago’s picture

Sorry, I don't understand. So the property info would contain a key 'input form' => 'my_custom_form_callback'?

No. Rules data types can have an UI class implementing the interface for direct input - as text_formatted have. In that case adding a single input parameter with the given type does it.

+++ rules/modules/data.eval.inc
@@ -111,6 +111,26 @@ function rules_action_data_list_remove($list, $item) {
+  if (isset($type_info['property info'])) {
+    $data = $values;
+  }
+  else {
+    $data = $values['value'];
+  }

If you don't name the value parameter 'param_value' but just value, you can just check for the existence of that paramater and don't to fetch the cache again.

+++ rules/modules/data.rules.inc
@@ -532,6 +558,59 @@ function rules_action_data_list_add_positions() {
+      if (isset($cache['data_info'][$property_info['type']])) {
+        $element_info['parameter']['param_' . $property] += $cache['data_info'][$property_info['type']];
+      }

What are these lines for?

Also you might want to clean the property_info array so only valid properties are copied over. array_intersect_key is fine for that as it also ignores not existing keys.

Powered by Dreditor.

klausi’s picture

Status: Needs work » Needs review
StatusFileSize
new7.42 KB

Sorry that it took me so long to follow up on this patch.

This approach basically works now. Some notes:
* the available options now have been reduced to data types either specifying an ui class implementing RulesDataDirectInputFormInterface or that specify a creation callback.
* fetching the cache in the action implementation is needed to retrieve the creation callback.
* array_intersect_key() is used to only copy type and label to the property parameters.
* 'creation callback' was added to the API documentation of hook_rules_data_info().
* the validation callback of entity create is used.

Open questions:
* I'm not sure I use the metadata wrappers right?
* How can we produce more than one variable in a rule? This would need a further parameter to carry the name of the new variable.

fago’s picture

Status: Needs review » Needs work

Code looks already good :)

Best use RulesPluginUI::getDataTypeClass(). (This should be static) + $class instanceof RulesDataDirectInputFormInterface for checking for DI. That should be shorter. Then make the if() to be a single line even if long, two lined ifs are really weird to read.

For checking for entities you could use:
+ $data_info = $cache['data_info'];
+ $entity_info = entity_get_info();
-> $data_info = array_intersect_key($cache['data_info'], entity_get_info());

Also you don't need the extra check for 'entity', it won't have a creation callback anyway or? :) Entities too, but let's keep that to make sure they can't be double-covered (entity_create + data_create).

+ *   - 'creation callback': Optionally, a callback that creates a new instance of
+ *     this data type. See entity_metadata_create_node() for an example.

Please clarify the relation to the entity creation callback in the comment. Also mention rules_action_data_create_array() there - maybe this is the better example? rules_action_data_create_array() is broken as it doesn't generate a wrapper though.

+    'callbacks' => array(
+      'form_alter' => 'rules_action_entity_type_form_alter',
+      'validate' => 'rules_action_entity_create_validate',
+    ),

Great that we can reuse those functions, however it's not obvious that they are reused. Best find a generic name for the function and specify it for both actions - that makes it more obvious. Also comment the functions to be used by both actions.

+    $data = entity_metadata_wrapper($args['settings']['type'], $args['settings']['value'])->value();
+    return array('data_created' => $data);
+  }

Just return the wrapper, so Rules can re-use the object instead of having to create another one.

Also we need to add a test for it - at least with text_formatted. A test case with property info would be good too, but I can't think of a use case for it in core. But the testing module could add a creation callback for watchdog entries.

Thinking about that I wonder how this relates to the "Add a variable" actions we had in 6.x. For sure we need something like that in 7.x too. Perhaps we should just split the action into two and use "add a variable" for *any type* - what gives us the DI use case too and concentrate on data structures with data_create() ?

klausi’s picture

Status: Needs work » Needs review
StatusFileSize
new12.44 KB

Yo, I tried to address all your remarks.
* Now with simpletests.
* uses array_diff_key() to remove entity info from the data type info
* instanceof does not work here, as we deal with class names (strings) and not objects.

Unsolved issues:
* creating more than one variable in a rule
* splitting up the action: I thought this would be the "Add a variable" action from D6? You think we should separate the types with input forms from the data types with creation callbacks? How would we name those actions then and what is the difference for end users?

fago’s picture

>instanceof does not work here, as we deal with class names (strings) and not objects.
oh, indeed. I remembered that somehow this is necessary, bot got distracted by a wrong use of instanceof in the ui. Fixed that :)

>* Now with simpletests.
Awesome!

>* creating more than one variable in a rule
You can just add multiple variables? Ah I think what's the problem right now is missing UI support for renaming the provided variable.

What data_create does different to "Add a variable" is that it creates *new* data. I cannot use the action to create a new user variable from node:author, or a new text variable 'foo'. The "Add a variable" would allow that and can support any data type. If there is no data source available + no direct input mode = bad luck .. ;)

But if we have that action, data_create does not deal with DI-capable data types any more as you can use "add var" for that. What remains is creating new data structures, what isn't properly possible with "add var" as you cannot allow data selection of a single var or the input of *multiple* parameters -> direct input for data structures is not possible such that you can use data selection for filling the single properties.

fago’s picture

+/**
+ * Creation callback for array structured data.
+ */
+function rules_action_data_create_array($values = array(), $type_info = array()) {
+ // $values is an array already, so we can just pass it to the wrapper.
+ return entity_metadata_wrapper('struct', $values, $type_info);
+}

Passing through $type_info is wrong here - it is not the same info. Best just use rules_wrap_data() to create the wrapper, as it incorporates property info from the data info hook. Also the wrapper should be created with the data type as registered in rules.

klausi’s picture

StatusFileSize
new12.4 KB

Now using rules_wrap_data().

>> Ah I think what's the problem right now is missing UI support for renaming the provided variable.

Are you planning to implement a generic ui interface to rename any provided variable of any action?

>> I cannot use the action to create a new user variable from node:author

You mean you want to clone node:author? If you want to create a new user, you should use the "create entity" action. Or do you want to have just a virtual user object that isn't saved automatically?

>> or a new text variable 'foo'.

Sure you can create a new text variable with this action. You cannot choose a variable name yet, that depends if we want to rename provided variables or add label and machine name parameters to let the user assign a variable name like in D6.

I think we can cover all data types with this action, simple ones with DI and data structures as well. Properties of data structures are listed and can be configured with the data selector. If the property type supports DI the input can be switched to DI. Otherwise the data for the property needs to be created beforehand and can then be selected via the data selector. So what am I missing?

fago’s picture

Patch looks good.

+ * - 'creation callback': Optionally, a callback that creates a new instance of
+ * this data type. See rules_action_data_create_array() for an example.

As noted, we should clarify that entities should use hook_entity_info() 'creation callback' instead, as introduced by entity metadata.

>Are you planning to implement a generic ui interface to rename any provided variable of any action?

Yes. #869398: UI for renaming/labelling provided variables

>You mean you want to clone node:author? If you want to create a new user, you should use the "create entity" action. Or do you want to have just a virtual user object that isn't saved automatically?

I create a new var $my_user and put node:author in there - for whatever reason. Maybe I change node:author later on. $my_user internally just points to the entity as node:author does.

>So what am I missing?

The action provides no way to put existing data into a new variable as it restricts the value to input. I just had a look at add_var in 6.x, and it behaves the same. But in 6.x we had no reason to support adding a new variable with the value of an existing one, as there were no data selectors like node:author - thus in 6.x we would have just duplicated the variables.

But for 7.x there is the use-case of changing a data property and keeping the original value in a variable. Are there other use cases? I don't know. Maybe
* shorten a long data selector node:referenced-node:author:node-profile-type:comment to comment? :D
* providing vars in components. To provide node:author as variable, you need to have a variable for it.

Supporting add_var too is as easy as removing the restriction to input and fixing the type options accordingly. However the problem is that the action name data_create doesn't fit any more :( Additionally one needs to be able to create a var for data-structure types by copying an existing value too. Thus we would have to split the action into two => variable_add + data_create (only for data structures).

klausi’s picture

StatusFileSize
new14.48 KB

Ok, now this patch splits up into two actions.

Currently it is not possible to do this fancy user re-assignment example, but I will look into it.

fago’s picture

Status: Needs review » Needs work

That's fast.. :)

+++ rules/modules/data.eval.inc
@@ -111,6 +111,44 @@ function rules_action_data_list_remove($list, $item) {
+  $data = entity_metadata_wrapper($type, $args['settings']['value']);
+  return array('variable_added' => $data);

Primitive data types aren't wrapped. Thus just return the data, rules is going to wrap it if necessary.

+++ rules/modules/data.rules.inc
@@ -232,6 +233,63 @@ function rules_data_action_info() {
+       // Further needed parameters depend on the type.

Why not just add the value parameter here?

+++ rules/modules/data.rules.inc
@@ -532,6 +590,87 @@ function rules_action_data_list_add_positions() {
+function rules_data_action_variable_add_options() {
+  return _rules_data_variable_options('input form');
+}

We do not need to filter for input forms here. Data selection is fine for adding a variable.

+++ rules/modules/data.rules.inc
@@ -532,6 +590,87 @@ function rules_action_data_list_add_positions() {
+  foreach ($data_info as $type => $properties) {
+    if ($filter == 'creation callback' && isset($properties['creation callback'])) {
+      // Add data types with creation callback.
+      $options[$type] = $properties['label'];
+    }

You might want to use rules_filter_array() + rules_extract_property() here.

+++ rules/modules/data.rules.inc
@@ -532,6 +590,87 @@ function rules_action_data_list_add_positions() {
+  if (in_array('RulesDataDirectInputFormInterface', class_implements($class))) {
+    $element_info['parameter']['value'] = $type_info;
+    $element_info['parameter']['value']['type'] = $type;
+  }

We need the value parameter with all possible input modes. This process callback just needs to set the right data type.

Powered by Dreditor.

klausi’s picture

Status: Needs work » Needs review
StatusFileSize
new13.8 KB

Updated patch. This time the "Add a variable" action allows to select any available data type and to assign via data selector.

>> You might want to use rules_filter_array() + rules_extract_property() here.

I looked at that, but they don't seem to fit quite for the creation callback filtering.

>> This process callback just needs to set the right data type.

And the label, which is also type dependent and makes the form more readable.

fago’s picture

Status: Needs work » Needs review

I think we are almost there now :)

+++ rules/modules/data.rules.inc
@@ -232,6 +233,66 @@ function rules_data_action_info() {
+      ),
+

Unnecessary line break.

+++ rules/modules/data.rules.inc
@@ -378,16 +439,17 @@ function rules_action_entity_query_process(RulesAbstractPlugin $element) {
+function rules_action_create_validate($element) {

Let's call that _type_validate like the form_alter, then the function naming is consistent.

+++ rules/rules.api.php
@@ -328,6 +328,10 @@ function hook_rules_event_info() {
+ *   - 'creation callback': Optionally, a callback that creates a new instance of
+ *     this data type. Entities should use hook_entity_info() to specify
+ *     'creation callback' instead, as introduced by the entity metadata
+ *     module. See rules_action_data_create_array() for an example.

Let's move this to the entry for 'property info' and explain it is only for data types with property info...

Perhaps:
If 'property info' is given, an optional callback that makes use of the property info to create ...

Powered by Dreditor.

fago’s picture

Status: Needs review » Needs work
klausi’s picture

StatusFileSize
new14.04 KB

Patch fixes your comments.

fago’s picture

Status: Needs review » Fixed

Awesome work! and thanks for your endurance ;)

Committed.

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.