#Ruby Basics
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.
#Data Types
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
#Variables
- 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
.
#Examples
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
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.
#Control Expressions
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
#Loops
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.
#Examples
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
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
#Blocks
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
#Block arguments
Blocks can receive arguments which are passed by its caller. We will often encounter this in run and triggered blocks.
rule 'name' do
changed Switches.members
run do |event|
# do something based on the event argument
end
end
#Ruby's Safe Navigation Operator
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
#Some Gotchas
#Exiting early
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
.
#Parentheses
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 { }
#Zero is "truthy" in Ruby
if 0
logger.info "This will always be executed"
else
logger.info "This will never be executed"
end
#Source Code Formatting
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!