Fork me on GitHub

ErlPort Ruby documentation

Contents

Overview and examples

In the first section of the manual you can find an overview of all related to Ruby features of ErlPort and a lot of examples mostly with using of Erlang shell. Make sure you read Downloads page and installed ErlPort or know how to start Erlang shell with ErlPort from the sources directory.

Ruby instances

If you read main Documentation page you should know that before any use of ErlPort you need to start a Ruby instance. A Ruby instance is basically an operating system process which represented in Erlang by Erlang process.

To start an instance of Ruby one of the variants of ruby:start or ruby:start_link functions should be used. The ruby:start/0 function will start Ruby instance with the default parameters.

To stop the running Ruby instance ruby:stop/1 function should be used with the instance id as the parameter.

And of course you can run more than one Ruby instance, but be careful to not create way too many because they can waste all the OS resources.

The following is an example of start/stop API:

1> {ok, RubyInstance1} = ruby:start().
{ok,<0.34.0>}
2> {ok, RubyInstance2} = ruby:start().
{ok,<0.36.0>}
3> ruby:stop(RubyInstance1)
ok
4> ruby:stop(RubyInstance2)
ok

Call Ruby functions from Erlang

To call a function in Ruby ruby:call/4 function should be used which accepts the instance id and File, Function and Arguments for Ruby function:

1> {ok, R} = ruby:start().
{ok,<0.34.0>}
2> ruby:call(R, '', 'Integer', [<<"2">>]).
2

Of course in Ruby you can also have hierarchies of files and modules so ruby:call/4 supports /-separated names for File and ::-separated for Function arguments:

3> ruby:call(R, 'rss/atom', 'RSS::Atom::Feed::new::version', []).
<<"1.0">>

In case of any error during the function call an exception of class error will be generated in the following form:

error:{ruby, ExceptionClass, ExceptionArgument, ReversedStackTrace}

For example:

4> try ruby:call(R, unknown, unknown, [])
4> catch error:{ruby, Class, Argument, StackTrace} -> error
4> end.
error
5> Class.
'LoadError'
6> Argument.
<<"no such file to load -- unknown">>
7> StackTrace.
[<<"-e:1">>,<<"-e:1:in `require'">>,
 <<"/../erlport/priv/ruby1.8/erlport/cli.rb:94">>,
 <<"/../erlport/priv/ruby1.8/erlport/cli.rb:41:in `main'">>,
 <<"/../erlport/priv/ruby1.8/erlport/erlang.rb:135:in `start'">>,
 <<"/../erlport/priv/ruby1.8/erlport/erlang.rb:191:in `_receive'">>,
 <<"/../erlport/priv/ruby1.8/erlport/erlang.rb:231:in `call_with_e"...>>,
 <<"/../erlport/priv/ruby1.8/erlport/erlang.rb:192:in `_receiv"...>>,
 <<"/../erlport/priv/ruby1.8/erlport/erlang.rb:215:in `inc"...>>,
 <<"/../erlport/priv/ruby1.8/erlport/erlang.rb:215:in "...>>]

And of course don't forget to stop the instance at the end:

8> ruby:stop(R).
ok

If you want to call a function from your own Ruby file in most cases you need to set the Ruby lib. You can do it with ruby:start/1 function or RUBYLIB environment variable. The ruby:start/1 also can be used to change the default Ruby interpreter. For example let's create a simple Ruby file /path/to/my/modules/version.rb:

def version
    "#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
end

Now we can set path to this module in ruby:start/1 like this:

1> {ok, R} = ruby:start([{ruby_lib, "/path/to/my/modules"},
1>                       {ruby, "ruby1.9.3"}]).
{ok,<0.34.0>}
2> ruby:call(R, version, version, []).
<<"1.9.3-p0">>
3> ruby:stop(R).
ok

Call Erlang functions from Ruby

ErlPort uses Ruby erlport/erlang.rb file with ErlPort::Erlang module as an interface to Erlang. Namely ErlPort::Erlang::call() function allows to call Erlang functions from Ruby. The function accepts Module and Function arguments as Symbol() (and ErlPort::ErlTerm::EmptySymbol() for Ruby 1.8.*) object and Arguments as an Array(). Currently each Erlang function will be called in a new Erlang process. Let's create the following Ruby module in pids.rb file in the current directory which will be added to Ruby lib path automatically by Ruby:

include ErlPort::Erlang

def pids
    pid1 = call(:erlang, :self, [])
    pid2 = call(:erlang, :self, [])
    [pid1, pid2]
end

Now we can call this function from Erlang:

1> {ok, R} = ruby:start().
{ok,<0.34.0>}
2> ruby:call(R, pids, pids, []).
[<0.36.0>,<0.37.0>]
3> ruby:stop(R).
ok

To simplify the demonstration the next example will use the call chaining so Ruby to Erlang calls will be initiated from Erlang shell. The following example also demonstrate the communication between two Ruby instances:

1> {ok, R1} = ruby:start().
{ok,<0.34.0>}
2> {ok, R2} = ruby:start().
{ok,<0.36.0>}
3> ruby:call(R1, '', 'Process::pid', []).
5196
4> ruby:call(R2, '', 'Process::pid', []).
5198
5> ruby:call(R1, 'erlport/erlang', call,
5>           [ruby, call, [R2, '', 'Process::pid', []]]).
5198
6> ruby:stop(R1).
ok
7> ruby:stop(R2).
ok

So the command #5 actually calls ErlPort::Erlang::call() function for instance R1, which calls Erlang function ruby:call/4, which in order calls Ruby function Process::pid() for instance R2.

Send messages from Erlang to Ruby

To send a message from Erlang to Ruby first a message handler function on Ruby side should be set. The message handler function can be set with ErlPort::Erlang::set_message_handler() function. The default message handler just ignore all the incoming messages. And if you don't need to handle incoming message anymore the default handler can be set again with ErlPort::Erlang::set_default_message_handler() function.

Be careful when you write a message handling function because the function can also get some unexpected messages which probably should be ignored and in case of any error in the message handler the whole instance will be shut down.

To demonstrate message sending from Erlang to Ruby we will first create the following module in the current directory in a file handler.rb:

include ErlPort::Erlang

def register_handler dest
    set_message_handler {|message|
        cast dest, message
    }
    :ok
end

This message handler just send all messages to the selected Erlang process.

To send a message to Ruby ruby:cast/2 function can be used and also all unknown to ErlPort messages will be redirected to the message handler.

1> {ok, R} = ruby:start().
{ok,<0.34.0>}
2> ruby:call(R, handler, register_handler, [self()]).
ok
3> ruby:cast(R, test_message).
ok
4> flush().
Shell got test_message
ok
5> R ! test_message2.
test_message2
6> flush().
Shell got test_message2
ok
7> ruby:stop(R).
ok

Send messages from Ruby to Erlang

It's very easy to send a message from Ruby to Erlang - you just need to know the pid() or registered name of the destination process. The function ErlPort::Erlang::cast() accepts two arguments - the id of the destination process and a message which can be any supported data type according to Data types mapping. And of course you can send messages to any other ErlPort process.

The following is a demonstration of message sending from Ruby:

1> {ok, R} = ruby:start().
{ok,<0.34.0>}
2> ruby:call(R, 'erlport/erlang', cast, [self(), test_message]).
undefined
3> flush().
Shell got test_message
ok
4> register(test_process, self()).
true
5> ruby:call(R, 'erlport/erlang', cast, [test_process, test_message2]).
undefined
6> flush().
Shell got test_message2
ok
7> ruby:stop(R).
ok

Custom data types

ErlPort only supports a minimal set of data types to make sure the types are orthogonal - can be created and meaningful in any language supported by ErlPort. In addition ErlPort also supports language specific opaque data type containers so for example Ruby instances can exchange any serializable data type. But sometimes it's better to use rich inter-language data types in which case custom data types can be used.

There are two functions to support custom data types:

Both of the functions can be reset to the default, which just pass the value unmodified, with ErlPort::Erlang::set_default_encoder() and ErlPort::Erlang::set_default_decoder() functions correspondingly. Note also that there's no support for automatic traversing of container data types so it should be implemented by encoder/decoder functions if needed.

To give you a feeling how it works the following file in the current directory with name date_type.rb will add the partial support to ErlPort for Time() objects:

include ErlPort::ErlTerm
include ErlPort::Erlang

def setup_date_type
    set_encoder {|v| date_encoder v}
    set_decoder {|v| date_decoder v}
    :ok
end

def date_encoder value
    if value.is_a? Time
        value = Tuple.new([:date,
            Tuple.new([value.year, value.month, value.day])])
    end
    value
end

def date_decoder value
    if value.is_a? Tuple and value.length == 2 and value[0] == :date
        year, month, day = value[1]
        value = Time.utc(year, month, day)
    end
    value
end

def add date, sec
    date + sec
end

The date_type module can be used in Erlang shell like this:

1> {ok, R} = ruby:start().
{ok,<0.34.0>}
2> ruby:call(R, date_type, setup_date_type, []).
ok
3> Date = ruby:call(R, '', 'Time::utc', [2012, 12, 31]).
{date,{2012,12,31}}
4> ruby:call(R, date_type, add, [Date, 60 * 60 * 24]).
{date,{2013,1,1}}
5> ruby:stop(R).
ok

Standard output redirection

As a convenient feature ErlPort also supports redirection of Ruby`s STDOUT to Erlang which can be used for example for debugging. For example:

1> {ok, R} = ruby:start().
{ok,<0.34.0>}
2> ruby:call(R, '', puts, [<<"Hello, World!">>]).
Hello, World!
undefined
3> ruby:stop(R).
ok

Reference manual

Here you can find complete description of data types mapping, Erlang functions, Ruby functions and environment variables supported by ErlPort.

Data types mapping

The following table defines mapping of Erlang data types to Ruby data types:

Erlang data type Ruby data type
integer() Integer()
float() Float()
atom() Symbol() and ErlPort::ErlTerm::EmptySymbol() in Ruby 1.8.*
true true
false false
undefined nil
binary() String()
tuple() ErlPort::ErlTerm::Tuple()
list() Array()
improper_list() ErlPort::ErlTerm::ImproperList()
Opaque Ruby data type container Ruby data type
Opaque data type container Opaque data type container

And here is the table of Ruby to Erlang data types mapping. The types mapping between Erlang and Ruby are practically orthogonal:

Ruby data type Erlang data type
Integer() integer()
Float() float()
Symbol() and ErlPort::ErlTerm::EmptySymbol() in Ruby 1.8.* atom()
true true
talse false
nil undefined
String() binary()
ErlPort::ErlTerm::Tuple() tuple()
Array() list()
ErlPort::ErlTerm::ImproperList() improper_list()
Other Ruby data type Opaque Ruby data type container
Opaque data type container Opaque data type container

The following classes can be found in erlport/erlterms.rb file.

ErlPort::ErlTerm::EmptySymbol()
Class to represent empty Erlang atoms in Ruby 1.8.*. Empty symbols support was added to Ruby in 1.9.1.
ErlPort::ErlTerm::Tuple(array)
Class to represent Erlang tuples in Ruby. Basically just a subclass of Array().
ErlPort::ErlTerm::ImproperList(array, tail)
Class to represent Erlang improper lists in Ruby. The tail argument can't be an array. Note that this class exists mostly to convert improper lists received from Erlang side and probably there are no reasons to create instances of this class in Ruby.

Erlang API

ruby:start() -> {ok, Pid} | {error, Reason}
Start Ruby instance with the default options
ruby:start(Options) -> {ok, Pid} | {error, Reason}

Start Ruby instance with options. The Options argument should be a list with the following options.

General options:

{buffer_size, Size::pos_integer()}
Size in bytes of the ErlPort receive buffer on Ruby side. The default is 65536 bytes.
{call_timeout, Timeout::pos_integer() | infinity}
Default timeout in milliseconds for function calls. Per call timeouts can be set with ruby:call/5 function.
{cd, Path::string()}
Change current directory to Path before starting.
{compressed, 0..9}
Set terms compression level. 0 means no compression and 9 will take the most time and may (or may not) produce a smaller result. Can be used as an optimisation if you know that your data can be easily compressed.
{env, [{Name::string(), Value::string() | false}]}
Set environment for Ruby instance. The Name variable is the name of environment variable to set and Value can be a string value of the environment variable or false if the variable should be removed.
nouse_stdio
Not use STDIN/STDOUT for communication. Not supported on Windows.
{packet, 1 | 2 | 4}
How many bytes to use for the packet size. The default is 4 which means that packets can be as big as 4GB but if you know that your data will be small you can set it for example to 1 which limits the packet size to 256 bytes but also saves 3 bytes for each packet. Note however that ErlPort adds some meta-information in each packet so the resulting packets always will be bigger than your expected size.
{start_timeout, Timeout::pos_integer() | infinity}
Time to wait for the instance to start.
use_stdio
Use STDIN/STDOUT for communication. The default.

Ruby related options:

{ruby, Ruby::string()}
Path to the Ruby interpreter executable
{ruby_lib, Path::string() | [Path::string()]}

The Ruby programs search path. The Path variable can be a string in RUBYLIB format or a list of paths. The priorities of different ways to set the modules search path is as follows:

  1. ruby_lib option
  2. RUBYLIB environment variable set through the env option
  3. RUBYLIB environment variable
ruby:start(Name, Options) -> {ok, Pid} | {error, Reason}
Start named Ruby instance. The instance will be registered with Name name. The Options variable is the same as for ruby:start/1.
ruby:stop(Instance) -> ok
Stop Ruby instance
ruby:call(Instance, File, Function, Arguments) -> Result

Call Ruby function. The Instance variable can be a pid() which returned by one of the ruby:start functions or an instance name (atom()) if the instance was registered with a name. The File and Function variables should be atoms and Arguments is a list.

In case of any error on Ruby side during the function call an exception of class error will be generated in the following form:

error:{ruby, ExceptionClass, ExceptionArgument, ReversedStackTrace}
ruby:call(Instance, File, Function, Arguments, Options) -> Result

The same as ruby:call/4 except the following options can be added:

{timeout, Timeout::pos_integer() | infinity}
Call timeout in milliseconds.
ruby:cast(Instance, Message) -> ok
Send a message to the Ruby instance.

Ruby API

All the following functions can be found in erlport/erlang.rb file.

ErlPort::Erlang::call(module, function, arguments) -> result
Call Erlang function as module:function(arguments). The function and module variables should be of type Symbol and arguments should be an Array.
ErlPort::Erlang::cast(pid, message)
Send a message to Erlang. The pid and message variables should be the same types as supported by Erlang ! (send) expression. Erlang pid() variables however can't be created in Ruby but can be passed as parameters from Erlang.
ErlPort::Erlang::self() -> pid
Get the Erlang pid of the Ruby instance
ErlPort::Erlang::set_encoder(&encoder)
Set encoder for custom data types. Encoder is a code block with a single value argument which is can be any Ruby data type and should return an Erlang representation of this type using supported Data types mapping.
ErlPort::Erlang::set_decoder(&decoder)
Set decoder for custom data types. Decoder is a code block with a single value argument which is one of the supported Erlang data types according to Data types mapping. The function should decode and return Erlang representation of the rich Ruby data type.
ErlPort::Erlang::set_message_handler(&handler)
Set message handler. Message handler is a code block with a single message argument which receive all the incoming messages.
ErlPort::Erlang::set_default_encoder()
Reset custom data types encoder to the default which is just pass the term through without any modifications
ErlPort::Erlang::set_default_decoder()
Reset custom data types decoder to the default which is just pass the term through without any modifications
ErlPort::Erlang::set_default_message_handler()
Reset message handler to the default which is just ignore all the incoming messages

Environment variables

The following environment variables can change the default behavior of ErlPort:

ERLPORT_RUBY
Path to Ruby interpreter executable which will be used by default.
RUBYLIB

The default search patch for Ruby programs. The same as RUBYLIB environment variable supported by Ruby. The priorities of different ways to set the programs search path is as follows:

  1. ruby_lib option
  2. RUBYLIB environment variable set through the env option
  3. RUBYLIB environment variable