Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: typo

...

...

Code Block
ruleset my_movie_app {
  meta {
    use module org.themoviedb.sdk alias sdk
      with
        apiKey = meta:rid_configrulesetConfig{"api_key"}
        sessionID = meta:rid_configrulesetConfig{"session_id"}
    ...
  }
}

Notice that we understand that the API module we are using in my_movie_app needs these values, and so we supply them using the with clause of the use module statement. This code assumes that our ruleset will be configured with values named api_key and session_id when it is installed in a pico. Documentation we provide with our ruleset would include instructions for doing this.

...

Code Block
ruleset org.themoviedb.sdk {
  meta {
    ...
    provides getPopular, rateMovie
  }
  global {
    ...
    rateMovie = defaction(movieID, rating) {
      queryString = {"api_key":apiKey,"session_id":sessionID}
      body = {"value":rating}
      http:post(<<#{base_url}/movie/$#{movieID}/rating>>
        ,qs=queryString,json=body) setting(response)
      return response
    }
  }
}

...

Our rule, which selects on the movie:new_rating event, expects a non-empty attribute named movieID and a numeric attribute named rating. These are checked for in lines 10 and 11, respectively, and bound to internal (to the rule) names which happen to match the attribute names in line 12. We convert one of the attributes from the passed-in String to a Number in line 14, using the universal as operator. We check that the rating is in the expected range in line 15. If the rating is value valid (line 17) then we take the HTTP POST action in line 17, also capturing the HTTP response, which we then save in the pico in line 19 (along with a timestamp in line 20).

Finally, in line 21 we raise a movie:rated event for future use, if any. This follows the principle of avoiding dead-end rules.

Using an application

Our ruleset could be used as the back-end for a web-based single page application, or by some other ruleset. For now, you can just test it using the Testing tab:

...

An astute reader will have

Info

Note that the rate_movie rule uses an event expression to capture the values from the movieID and rating event attributes and assign those to variables. The regular expressions require that those be non-empty, so this rule won’t be selected if they are.

You can alternately use event:attr() in the pre block to save these values.

pre {

movieID = event:attr(”movieID”);

}

Using an application

Our ruleset could be used as the back-end for a web-based single page application, or by some other ruleset. For now, you can just test it using the Testing tab:

...

An astute reader will have noticed that there must be another shared function, named lastResponse, whose code we haven't yet included. Here it is:

...

  1. Sign up for a developer account at Twilio

  2. Study the Twilio API documentation for sending an SMS and making Twilio requests.

  3. Generate an account_sid and the corresponding auth_token

  4. Create a local file holding these keys. Do not check it in to any public repository.

  5. Create a Twilio module to wrap their API:

    1. Write a function to return a page of messages sent

    2. Write a user-defined action to send an SMS

  6. Write another ruleset to use and test your Twilio module by:

    1. calling the function and returning the messages

    2. defining a rule which actually sends an SMS

...

    1. SMS

...

Our first attempt will ignore some aspects of creating a useful module in order to just get something that works. Later we'll come back and clean those up. 

We'll start by defining an action called send_sms() that takes all of the information we need to use the Twilio API to send a message, including the Twilio API keys. 

A user-defined action is similar to a KRL function in that its body consists of a set of declarations, but instead of the last line being an expression representing the return value, the last line is an action. We used the defaction() keyword to define the function. In our case, the action will be the http:post() action that POSTs to the Twilio API. Here's our action definition:

Code Block
languagejs
send_sms = defaction(to, from, message, account_sid, auth_token){
  base_url = <<https://#{account_sid}:#{auth_token}@api.twilio.com/2010-04-01/Accounts/#{account_sid}/>>
  http:post(base_url + "Messages.json", form = 
                {"From":from,
                 "To":to,
                 "Body":message
                })
}

The action takes the following arguments:

  • to - the number to send the SMS to

  • from - the number the SMS is coming from (this must be a number from your Twilio account)

  • message - the message to send

  • account_sid - the Twilio account SID

  • auth_token - the Twilio authentication token

The action creates a base URL using the instructions in the Twilio API docs and then POSTs to that URL. 

We can test this action, by creating a rule that uses it as follows. 

Code Block
languagejs
rule test_send_sms {
  select when test new_message
  send_sms(event:attr("to"),
           event:attr("from"),
           event:attr("message"),
           event:attr("account_sid"),
           event:attr("auth_token"))
}

This simple rule selects on the test:new_message event and uses the action we just defined. All the needed arguments must be included as event attributes. 

After we install a ruleset containing this action definition and rule in our pico, we can use the Sky Event Console to publish an event to the pico. If you're using valid values for the account SID, authentication token, and from phone number, the to phone number should receive an SMS with the message you give. 

The user-defined action hides some of the details of the Twilio API and presents a simple action that any rule can use to send an SMS. 

A Module for Twilio

The previously defined action is not as useful as it could be because it requires that the account SID and authorization token be passed in each time. This is where KRL's parameterized modules come in handy. 

Modules are parameterized using a configure pragma in the meta block. The variables declared in the configure pragma take on the values that are set when the module is used. The configuration allows for default values. 

The following is a complete module for the send_sms() action using configuration. 

Code Block
languagejs
ruleset io.picolabs.twilio_v2 {
  meta {
    configure using account_sid = ""
                    auth_token = ""
    provides 
        send_sms
  }

  global {
    send_sms = defaction(to, from, message) {
       base_url = <<https://#{account_sid}:#{auth_token}@api.twilio.com/2010-04-01/Accounts/#{account_sid}/>>
       http:post(base_url + "Messages.json", form = {
                "From":from,
                "To":to,
                "Body":message
            })
    }
  }
}

The configure pragma's defaults are the empty string since there's no workable default for the Twilio keys. You can see that those variables are referenced in the send_sms() action to create the based URL for the Twilio call. 

We use a parameterized module by declaring its use in the meta block of the ruleset that wants to use the module. The use pragma allows parameters to be supplied for configuration variables. The following shows a ruleset that uses the Twilio module. 

Code Block
languagejs
ruleset io.picolabs.use_twilio_v2 {
  meta {
    key twilio {
          "account_sid": "<your SID goes here>",  
          "auth_token" : "<your auth token goes here>" 
    }
    use module io.picolabs.twilio_v2 alias twilio
        with account_sid = keys:twilio{"account_sid"}
             auth_token =  keys:twilio{"auth_token"}
  }

  rule test_send_sms {
    select when test new_message
    twilio:send_sms(event:attr("to"),
                    event:attr("from"),
                    event:attr("message")
                   )
  }
}

In this example, we've used the key pragma to declare the Twilio keys. Those keys are included in the use pragma that uses the Twilio module. By putting the keys in a key pragma, we tell the KRL parser and interpreter that they are keys so that it can take greater care. For example, they could be automatically redacted when shared. 

Warning

Security Warning

Don't put keys in rulesets that are publicly hosted, on GitHub for example. Any ruleset containing keys must be carefully stored so that only authorized parties can see them. See the Security Considerations section of the KRL documentation on keys for more information.

But, putting the keys in the ruleset means that it can't be publicly hosted and we have to take special care to ensure the keys aren't exposed. Consequently, best practice is to store keys in a module specifically designed for keys. That way, you only have to be careful with modules you are aware have keys. 

We can do this by defining a keys module that only contains keys. The provides keys pragma limits which keys are shared with which module for security. 

Code Block
languagejs
ruleset io.picolabs.lesson_keys {
  meta {
    key twilio {
          "account_sid": "<your account SID here>",  
          "auth_token" : "<your auth token here>" 
    }
    provides keys twilio to io.picolabs.use_twilio_v2
  }
}

We modify the preceding ruleset using the keys to load this ruleset as a module like so:

Code Block
languagejs
ruleset io.picolabs.use_twilio_v2 {
  meta {
  	use module io.picolabs.lesson_keys 
    use module io.picolabs.twilio_v2 alias twilio
        with account_sid = keys:twilio{"account_sid"}
             auth_token =  keys:twilio{"auth_token"}
  }

  rule test_send_sms {
    select when test new_message
    twilio:send_sms(event:attr("to"),
                    event:attr("from"),
                    event:attr("message")
                   )
  }
}

...