Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: added a couple more sections

...

  • Explain what entity variables are.
  • Use rules to mutate entity variables.
  • Use functions to simplify usage of entity variables.
  • Use entity variables to create persistent picos.
  • Understand how rules can use explicit events to chain rules

Prerequisites

You should have completed the following lessons:

...


Code Block
  rule hello_world {
    select when echo hello
    pre{
      name = event:attr("name").defaultsTo(ent:name,"use stored name")
    }
    send_directive("say") with
      something = "Hello " + name
  }

Test your persistence by raising an event to store_name without providing a value. This will cause the "defaultsTo" operator to provide the persisted name, which will be mentioned in the directive.

You have now created and used a simple entity variable to give your Pico persistent state.

Complex Entity Variables

We used an entity variable to store a simple string, but you can also store complex objects in your entity variables. To search deep branches of your object you provide hash paths.

Let's change the hello_world ruleset to know multiple users.

To do this we will need to do the following:

  1. Change our entity variable to be a map of users.
  2. Modify store_name to store multiple users (one at a time).
  3. Modify hello_world to greet from a user id.

Modify the entity variable in store_name to be a hash of user id's and names.

Start by adding a rule to initialize a structure to contain the map of user names. We'll bind a name "clear_name" to a structure with one, default, name. Be sure to put this declaration in the globals section of the ruleset.

Code Block
  clear_name = { "id": "_0", "name": { "first": "GlaDOS", "last": "" } }


  rule clear_names {
    select when hello clear
    always {
      ent:name := clear_name
    }
  }

If you add the following to the "events" array in the "__testing" structure, then you'll have a UI in the "Testing" tab of your Pico to initialize the entity variable "name".

Code Block
{ "domain": "hello", "type" : "clear" }

Validate your code changes, and refresh the "Testing" tab.

Image Added

Refresh the "Rulesets" tab to verify that the entity variable has been prepared.

Image Added

Now, let's modify the "hello_world" rule to obtain the name for the greeting from the structure. All we need to change is the code in the rule prelude.

Code Block
      id = event:attr("id").defaultsTo("_0")
      first = ent:name{[id,"name","first"]}
      last = ent:name{[id,"name","last"]}
      name = first + " " + last

You'll notice that this rule used to expect the name as an event attribute, but now it expects the id. So, adjust the entry accordingly in the "__testing" object.

Code Block
{ "domain": "echo", "type": "hello", "attrs": [ "id" ] }

Validate and install the modified ruleset. Refresh the "Testing" tab and click on the "echo/hello" button. Notice the directive now suggests a greeting to the default name.

 Image Added

Now, we need a rule to add another name to our entity variable structure.

Code Block
  rule store_name {
    select when hello name
    pre{
      passed_id = event:attr("id").klog("our passed in id: ")
      passed_first_name = event:attr("first_name").klog("our passed in first_name: ")
      passed_last_name = event:attr("last_name").klog("our passed in last_name: ")
    }
    send_directive("store_name") with
      id = passed_id
      first_name = passed_first_name
      last_name = passed_last_name
    always{
      ent:name_s := ent:name_s.defaultsTo(clear_name,"initialization was needed");
      ent:name_s := ent:name_s.set([passed_id,"name","first"],passed_first_name);
      ent:name_s := ent:name_s.set([passed_id,"name","last"],passed_last_name)
    }
  }

Update the entry for this rule in the "__testing" structure, to reflect the fact that it now expects three event attributes.

Code Block
{ "domain": "hello", "type": "name", "attrs": [ "id", "first_name", "last_name" ] }

Refresh the "Testing" tab and add a new name, filling in the three fields and clicking on the "hello/name" button.

Image Added

Now use the new "id" value and click the "echo/hello" button. Note the suggested greeting in the directive sent by this rule.

Image Added

Splendid! You have learned to use complex entity variables.

Querying Persistent Variables

We could use our entity variable how it is but the code will quickly become un-readable.

Let's create some functions to access our entity variable.

To do this we will need to:

  1. Add a users function to retrieve all users
  2. Add a name function to retrieve a user from id.
  3. Modify hello_world to use users and name function

Create two functions in the global block that, returns all users and return user name given an id. You can read more about functions here. A function returns the value of its last expression.

Code Block
    name = function(id){
      all_users = users();
      nameObj = id => all_users{[id,"name"]}
                    | { "first": "HAL", "last": "9000" };
      first = nameObj{"first"};
      last = nameObj{"last"};
      first + " " + last
    }

    users = function(){
      ent:name
    }

With the functions defined in the global block, we can simplify the prelude of the "hello_world" rule:

Code Block
  pre{
    id = event:attr("id").defaultsTo("_0").klog("the id")
    name = name(id)
  }


After saving your changes to the ruleset, refresh the "Testing" tab and verify that all is working as before.

Sweet, you now can add as many users as you want and have your ruleset say hello to each user.

Next Level

We have multiple users our ruleset can recognize with an id, but it would be so much cooler if we knew how many times our ruleset says hello to a user.

Let's make our ruleset smarter.

To do this we will need to use the structure in our entity variable to count visits.

Code Block
  rule hello_world {
    select when echo hello
    pre{
      id = event:attr("id").defaultsTo("_0").klog("the id")
      name = name(id)
      visits = ent:name{[id,"visits"]}.defaultsTo(0) + 1
    }
    send_directive("say") with
      something = "Hello " + name
    fired {
      ent:name := ent:name.set([id,"visits"],visits)
    }
  }