The openHAB JRuby scripting automation is based on the JRuby implementation of the Ruby language. This page offers a quick overview of Ruby to help you get started writing rules. However, it is by no means comprehensive. A wealth of information can be found on Ruby's web site.
In Ruby, everything is an object, even primitive types such as numbers and strings. For example, 1
as a number is an object
and has all the methods for the Integer class.
It is useful to get to know the basic data types that we will often encounter:
- Integer - e.g.
1
,-3
, etc. - Floating Point - e.g.
3.5
- String -
String literals in Ruby can be enclosed with double quotes, or single quotes. Strings enclosed by double quotes can
contain variables and expressions that are enclosed with
#{}
. For example:"Hi my name is #{name_variable}"
. The String class offers a plethora of useful methods to operate on and manipulate strings. - Array - example:
[1, 2, 'foo', AnotherObject]
- Hash - example:
{ 'key1' => 'value', 'key2' => 'value' }
- Symbol - example:
:iamasymbol
- Range - example:
1..5
- In Ruby, variables start with a lower case and by convention use
snake_case
. - Uppercase identifiers are constants, e.g.
NAMES
- Variable whose names start with
$
are global variables, e.g.$i_am_global
. - Variable whose names start with
@
are instance variables, e.g.@instance_variable
. Instance variables are similar to member variables or fields in other languages. - Local variables are just plain names that starts with a lower case, e.g.
local_var
.
Instance variables are created as soon as they are referenced.
They are persisted on whatever self
is.
In most simple file-based rules, and in UI based rules, self
is simply a top level Object
named main
, and instance variables will be persisted between multiple executions of the same rule:
rule "light turned on" do
changed Light_Switch, to: ON
run do
@turned_on_count ||= 0
@turned_on_count += 1
logger.info("The light has been turned on #{@turned_on_count} times")
end
end
String interpolation is done by enclosing a variable or an expression with #{
and }
inside a double quoted string.
The variable doesn't have to be a string. Ruby will automatically call #to_s
for you.
Although string concatenation with +
is possible, using string interpolation is preferred.
An example of string interpolation is included above.
Ruby supports various control expressions such as if/else
, ternary operator, case
, etc.
Example:
if a
# do something here
elsif b
# something else
else
# something here
end
# modifier if form
a = b if c == 5
# ternary operator
a = b == 5 ? 'five' : 'other'
# case/when similar to the switch() { case... } in c / java.
rule 'x' do
received_command DimmerItem1
run do |event|
case event.command
when OFF
Light1.off
Light2.off
when 0...50
Light1.on
Light2.off
when 50..100, ON
Light1.on
Light2.on
end
end
end
While Ruby supports the traditional for
and while
loop, they are rarely used.
Ruby objects such as Array, Hash, Set, etc. provide a plethora of methods to
achieve the same thing in a more "Ruby" way.
array = [1, 2, 3]
array.each do |elem|
logger.info("Element: #{elem}")
end
array.each_with_index do |elem, index|
logger.info("Element #{index}: #{elem}")
end
SWITCH_TO_LIGHT_HASH = { Switch1 => Light1, Switch2 => Light2 }
SWITCH_TO_LIGHT_HASH.each do |switch, light|
logger.info "#{switch.name} => #{light.name}"
end
rule 'turn light on' do
changed Switches.members
triggered do |item|
SWITCH_TO_LIGHT_HASH[item]&.command item.state
end
end
Note: next
is similar to continue
in C/Java. break
in Ruby is the same as in C/Java.
Methods are defined like this:
def one_plus_one # It can also be defined as def one_plus_one()
1 + 1
end
With parameters:
def one_plus(arg1)
1 + arg1
end
With a keyword argument:
def one_minus(another:)
1 - another
end
Note the last value in a method execution becomes its return value, so a return
keyword is optional.
To call a method:
# Calling a method without passing any arguments, no parentheses needed.
one_plus_one
# This works too:
one_plus_one()
# Calling a method with an argument:
one_plus(2)
# The parentheses can also be omitted here:
one_plus 2
# Calling a method with a keyword argument:
one_minus(another: 1)
# Guess what, the parentheses can also be omitted here:
one_minus another: 1
Multi-line blocks in Ruby are enclosed in a do
.. end
pair and single line blocks are enclosed with braces {
.. }
. You have encountered blocks in the examples above.
Rules are implemented in a block:
rule 'rulename' do
...
end
The execution part is also in a block for the run method, nested inside the rule block:
rule 'rulename' do
changed Item1
run do
...
end
end
Blocks can receive arguments which are passed by its caller. We will often encounter this in {OpenHAB::DSL::Rules::BuilderDSL#run run} and {OpenHAB::DSL::Rules::BuilderDSL#triggered triggered} blocks.
rule 'name' do
changed Switches.members
run do |event|
# do something based on the event argument
end
end
Ruby has a safe navigation operator
&.
which is similar to ?.
in C#, Groovy, Kotlin, etc.
# Instead of:
if items['My_Item']
items['My_Item'].on
end
# We can write it as:
items['My_Item']&.on
To exit early from a block, use next
instead of return
.
rule 'rule name' do
changed Item1
run do
next if Item1.off? # exit early
Item2.on # Turn on Item2 if Item1 turned on
# Do other things
end
end
Note: To exit early from a UI rule, use return
.
In Ruby, parentheses are optional when calling a method. However, when calling a method with arguments and a single-line block, the parentheses must be used. Example:
after(5.seconds) { }
after(5.seconds) do
# ...
end
after 5.seconds do
# parentheses aren't a must before a do..end block
end
# the following example will cause an error
after 5.seconds { }
if 0
logger.info "This will always be executed"
else
logger.info "This will never be executed"
end
The ruby style guide offers the generally accepted standards for Ruby source code formatting.
When working with file based rules in a source code editor (e.g. VSCode), it is highly recommended to integrate Rubocop (or rubocop-daemon) as the source code formatter and linter for Ruby.
Happy coding!