(Classic) Lesson: Pico State
Learning Objectives
After completing this lesson, you will be able to do the following:
- Explain what entity variables are.
- Use rules and functions to interact with 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:
Picos Represent State
Picos (Persistent Compute Objects) contain state that can be changed in response to events. Picos persist their state using entity variables.
hello_world
ruleset from (Classic) Quickstart.Add Persistence to hello_world
Let's change the hello_world
ruleset to know your name by default.
To do this we will need:
- An entity variable to store your name in.
- A new rule that stores your name in that entity variable.
Create a rule and name it store_name.
Have it select on event domain hello
and event type name
.
Entity variables can only be set in the postlude. We use the always
form of the postlude so that this postlude is always evaluated.
We will declare a variable for the name and log it in the prelude.
The rule store_name
will send a directive named store_name
with option name assigned
giving the passed parameter name we are going to set. The directive will allow us to know that the rule is working without having to look at the logs.
rule store_name { select when hello name pre{ passed_name = event:attr("name").klog("our passed in Name: "); } { send_directive("store_name") with name = passed_name; } always{ set ent:name passed_name; } }
Now change hello_world
rule to default to our persistent name.
rule hello_world { select when echo hello pre{ name = event:attr("name").defaultsTo(ent:name,"use stored name"); } { send_directive("say") with greeting = "Hello #{name}"; } always { log ("LOG says Hello " + name); } }
Test your persistence by raising an event to store_name
with a name
attribute. (Remember to flush your ruleset after updating the source code)
Test echo hello
event without a name
attribute.
Cool Beans!
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:
- Change our entity variable to be a map of users.
- Modify
store_name
to store multiple users. - 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.
rule store_name { select when hello name pre{ id = event:attr("id").klog("our pass in id: "); first = event:attr("first").klog("our passed in first: "); last = event:attr("last").klog("our passed in last: "); init = {"_0":{ "name":{ "first":"GLaDOS", "last":""}} } } { send_directive("store_name") with passed_id = id and passed_first = first and passed_last = last; } always{ set ent:name init if not ent:name{["_0"]}; // initialize if not created. Table in data base must exist for sets of hash path to work. set ent:name{[id,"name","first"]} first; set ent:name{[id, "name", "last"]} last; } }
Modify hello_world
to greet from id.
rule hello_world { select when echo hello pre{ id = event:attr("id").defaultsTo("_0","no id passed."); first = ent:name{[id,"name","first"]}; last = ent:name{[id,"name","last"]}; } { send_directive("say") with greeting = "Hello #{first} #{last}"; } always { log "LOG says Hello " + first + " " + last ; } }
Test your persistence by raising an event to store_name with an id
, first and
last
attributes. (Remember to flush your ruleset after updating the source code)
Test your greeting by raising an echo
hello
event with an id
attribute.
Splendid!
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:
- Add a
users
function to retrieve all users - Add a
name
function to retrieve a user from id. - Modify
hello_world
to useusers
andname
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.
users = function(){ users = ent:name; users }; name = function(id){ all_users = users(); first = all_users{[id, "name", "first"]}.defaultsTo("HAL", "could not find user. "); last = all_users{[id, "name" , "last"]}.defaultsTo("9000", "could not find user. "); name = first + " " + last; name; };
Change hello_world rule to use the name
function.
rule hello_world { select when echo hello pre{ id = event:attr("id"); default_name = name(id); } { send_directive("say") with greeting = "Hello #{default_name}"; } always { log "LOG says Hello " + default_name ; } }
Test your greeting again by raising an echo
hello
event with an id
attribute.
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:
- Create a rule to make a new user.
- Add a function that returns a single user.
- Add a variable to store visits.
- Update hello_world rule to store visits of known user and then if there is a new user, create him.
Create a rule called new_user that selects on explicit new_user event, takes an id, first and last name and sets the new user into the ent:name variable. This rule will also create a visits variable to store number of greetings.
rule new_user { select when explicit new_user pre{ id = event:attr("id").klog("our pass in Id: "); first = event:attr("first").klog("our passed in first: "); last = event:attr("last").klog("our passed in last: "); new_user = { "name":{ "first":first, "last":last }, "visits": 1 }; } { send_directive("say") with something = "Hello #{first_name} #{last_name}"; send_directive("new_user") with passed_id = id and passed_first = first and passed_last = last; } always{ set ent:name{[id]} new_user; } }
We will need to check if a user exists, to do this create a function that returns a single user from a full name.
user_by_name = function(full_name){ all_users = users(); filtered_users = all_users.filter( function(user_id, val){ constructed_name = val{["name","first"]} + " " + val{["name","last"]}; (constructed_name eq full_name); }); user = filtered_users.head().klog("matching user: "); // default to default user from previous steps. user };
We need hello_world rule to create new users and add greetings to current user's visits variables.
When we see a new user create a random key of three digits. This way our rule receives an event with a name, creates a new user if the user function does not find them, otherwise increments visits of the user.
rule hello_world { select when echo hello pre{ name = event:attr("name").defaultsTo("HAL 9000","no name passed."); full_name = name.split(re/\s/); first_name = full_name[0].klog("first : "); last_name = full_name[1].klog("last : "); // note we don't check name format its assumed. matching_user = user_by_name(name).klog("user result: "); //has id user_id = matching_user.keys().head().klog("id: "); new_user = { "id" : last_name.lc() + "_" + first_name.lc(), "first" : first_name, "last" : last_name }; } if(not user_id.isnull() ) then { send_directive("say") with something = "Hello #{name}"; } fired { log "LOG says hello to " + name ; set ent:name{[user_id,"visits"]} ent:name{[user_id,"visits"]} + 1; } else { raise explicit event 'new_user' // common bug to not put in ''. attributes new_user; log "LOG asking to create " + name ; } }
In the meta block add sharing on and provides users so you can view your users and see their visits change.
sharing on provides users
Query users function after raising a few echo hello events with user's names to see your users and their visits. Your query should look something like this.
Awesome Sauce, you did it!
Copyright Picolabs | Licensed under Creative Commons.