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
. - Local variables are just plain names that starts with a lower case, e.g.
local_var
.
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.
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 a 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!