#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!
 
          