\chapter{Ruby API}
\label{chap:api:ruby}

\section{Client Library}
\label{sec:api:ruby:client}

HyperDex provides ruby bindings under the module \code{HyperDex::Client}.  This
library wraps the HyperDex C Client library and enables use of native Ruby data
types.

This library was brought up-to-date following the 1.0.rc5 release.

\subsection{Building the HyperDex Ruby Binding}
\label{sec:api:ruby:building}

The HyperDex Ruby Binding must be requested at configure time as it is not
automatically built.  You can ensure that the Ruby bindings are always built by
providing the \code{--enable-ruby-bindings} option to \code{./configure} like
so:

\begin{consolecode}
% ./configure --enable-client --enable-ruby-bindings
\end{consolecode}

\subsection{Using Ruby Within Your Application}
\label{sec:api:ruby:using}

All client operation are defined in the \code{HyperDex::Ruby} module.  You can
access this in your program with:

\begin{rubycode}
require 'hyperdex'
\end{rubycode}

\subsection{Hello World}
\label{sec:api:ruby:hello-world}

The following is a minimal application that stores the value "Hello World" and
then immediately retrieves the value:

\inputminted{ruby}{\topdir/ruby/client/hello-world.rb}

You can run this example with:

\begin{consolecode}
% ruby hello-world.rb
put "Hello World!"
got:
{:v=>"Hello World!"}
\end{consolecode}

Right away, there are several points worth noting in this example:

\begin{itemize}
\item Every operation is synchronous.  The PUT and GET operations run to
completion by default.

\item Ruby types are automatically converted to HyperDex types.  There's no need
to specify information such as the length of each string, as one would do with
the C API.

\item Ruby symbols are permitted wherever a string may be used.  By convention,
space names and attribute names are specified using Ruby symbols, e.g.
\code{:kv} and \code{:v}.  Of course, you may use strings for these parameters
too.
\end{itemize}

\subsection{Asynchronous Operations}
\label{sec:api:ruby:async-ops}

For convenience, the Ruby bindings treat every operation as synchronous.  This
enables you to write short scripts without concern for asynchronous operations.
Most operations come with an asynchronous form, denoted by the \code{async\_}
prefix.  For example, the above Hello World example could be rewritten in
asynchronous fashion as such:

\inputminted{ruby}{\topdir/ruby/client/hello-world-async-wait.rb}

This enables applications to issue multiple requests simultaneously and wait for
their completion in an application-specific order.  It's also possible to use
the \code{loop} method on the client object to wait for the next request to
complete:

\inputminted{ruby}{\topdir/ruby/client/hello-world-async-loop.rb}

\subsection{Data Structures}
\label{sec:api:ruby:data-structures}

The Ruby bindings automatically manage conversion of data types from Ruby to
HyperDex types, enabling applications to be written in idiomatic Ruby.

\subsubsection{Examples}
\label{sec:api:ruby:examples}

This section shows examples of Ruby data structures that are recognized by
HyperDex.  The examples here are for illustration purposes and are not
exhaustive.

\paragraph{Strings}

The HyperDex client recognizes Ruby's strings and symbols and automatically
converts them to HyperDex strings.  For example, the following two calls are
equivalent and have the same effect:

\begin{rubycode}
c.put("kv", "somekey", {"v" => "somevalue"})
c.put(:kv, :somekey, {:v => :somevalue})
\end{rubycode}

The recommended convention is to use symbols for space and attribute names, and
strings for keys and values like so:

\begin{rubycode}
c.put(:kv, "somekey", {:v => "somevalue"})
\end{rubycode}

\paragraph{Integers}

The HyperDex client recognizes Ruby's integers, longs, and fixnums and
automatically converts them to HyperDex integers.  For example:

\begin{rubycode}
c.put(:kv, "somekey", {:v => 42})
\end{rubycode}

\paragraph{Floats}

The HyperDex client recognizes Ruby's floating point numbers and automatically
converts them to HyperDex floats.  For example:

\begin{rubycode}
c.put(:kv, "somekey", {:v => 3.1415})
\end{rubycode}

\paragraph{Lists}

The HyperDex client recognizes Ruby lists and automatically converts them to
HyperDex lists.  For example:

\begin{rubycode}
c.put(:kv, "somekey", {:v1 => ["a", "b", "c"]})
c.put(:kv, "somekey", {:v2 => [1, 2, 3]})
c.put(:kv, "somekey", {:v3 => [1.0, 0.5, 0.25]})
\end{rubycode}

\paragraph{Sets}

The HyperDex client recognizes Ruby sets and automaticaly converts them to
HyperDex sets.  For example:

\begin{rubycode}
require 'set'
c.put(:kv, "somekey", {:v1 => (Set.new ["a", "b", "c"])})
c.put(:kv, "somekey", {:v2 => (Set.new [1, 2, 3])})
c.put(:kv, "somekey", {:v3 => (Set.new [1.0, 0.5, 0.25])})
\end{rubycode}

Note that you'll have to include the set module from the standard library.

\paragraph{Maps}

The HyperDex client recognizes Ruby hashes and automatically converts them to
HyperDex maps.  For example:

\begin{rubycode}
c.put(:kv, "somekey", {:v1 => {"k" => "v"}})
c.put(:kv, "somekey", {:v2 => {1 => 2}})
c.put(:kv, "somekey", {:v3 => {3.14 => 0.125}})
c.put(:kv, "somekey", {:v3 => {"a" => 1}})
\end{rubycode}

\subsection{Attributes}
\label{sec:api:ruby:attributes}

Attributes in Ruby are specified in the form of a hash from attribute names to
their values.  As you can see in the examples above, attributes are specified in
the form:

\begin{rubycode}
{:name => "value"}
\end{rubycode}

\subsection{Map Attributes}
\label{sec:api:ruby:map-attributes}

Map attributes in Ruby are specified in the form of a nested hash.  The outer
hash key specifies the name, while the inner hash key-value pair specifies the
key-value pair of the map.  For example:

\begin{rubycode}
{:name => {"key" => "value"}}
\end{rubycode}

\subsection{Predicates}
\label{sec:api:ruby:predicates}

Predicates in Ruby are specified in the form of a hash from attribute names to
their predicates.  In the simple case, the predicate is just a value to be
compared against:

\begin{rubycode}
{:v => "value"}
\end{rubycode}

This is the same as saying:

\begin{rubycode}
{:v => HyperDex::Client::Equals.new('value')}
\end{rubycode}

The Ruby bindings support the full range of predicates supported by HyperDex
itself.  For example:

\begin{rubycode}
{:v => HyperDex::Client::LessEqual.new(5)}
{:v => HyperDex::Client::GreaterEqual.new(5)}
{:v => HyperDex::Client::Range.new(5, 10)}
{:v => HyperDex::Client::Regex.new('^s.*')}
{:v => HyperDex::Client::LengthEquals.new(5)}
{:v => HyperDex::Client::LengthLessEqual.new(5)}
{:v => HyperDex::Client::LengthGreaterEqual.new(5)}
{:v => HyperDex::Client::Contains.new('value')}
\end{rubycode}

\subsection{Error Handling}
\label{sec:api:ruby:error-handling}

All error handling within the Ruby bindings is done via the
\code{begin}/\code{rescue} mechanism of Ruby.  Errors will be raised by the
library and should be handled by your application.  For example, if we were
trying to store an integer (5) as attribute \code{:v}, where \code{:v} is
actually a string, we'd generate an error.

\begin{rubycode}
begin
    puts c.put(:kv, :my_key, {:v => 5})
rescue HyperDex::Client::HyperDexClientException => e
    puts e.status
    puts e.symbol
    puts e
end
\end{rubycode}

Errors of type \code{HyperDexClientException} will contain both a message
indicating what went wrong, as well as the underlying \code{enum
hyperdex\_client\_returncode}.  The member \code{status} indicates the numeric
value of this enum, while \code{symbol} returns the enum as a string.  The above
code will fail with the following output:

\begin{verbatim}
8525
HYPERDEX_CLIENT_WRONGTYPE
invalid attribute "v": attribute has the wrong type
\end{verbatim}

\subsection{Operations}
\label{sec:api:ruby:ops}

\input{\topdir/ruby/client/ops}
\pagebreak

\subsection{Working with Signals}
\label{sec:api:ruby:signals}

The HyperDex client library is signal-safe.  Should a signal interrupt the
client during a blocking operation, it will raise a
\code{HyperDexClientException} with status \code{HYPERDEX\_CLIENT\_INTERRUPTED}.

\subsection{Working with Threads}
\label{sec:api:ruby:threads}

The Ruby module is fully reentrant.  Instances of
\code{HyperDex::Client::Client} and their associated state may be accessed from
multiple threads, provided that the application employs its own synchronization
that provides mutual exclusion.

Put simply, a multi-threaded application should protect each \code{Client}
instance with a mutex or lock to ensure correct operation.
