Fluent Interfaces in Ruby
Fluent Interfaces became a standard in many programming languages. They make it easy to build DSLs that read like normal sentences. Take a look at this code example:
User.where(:username => 'Steven').all
Even if you never wrote a single line of code you can grasp what is going on. Someone wants to get all the users where the username is “Steve”. That seems legit. In the Ruby world we are spoiled by all the syntactic sugar the language provides. Blocks, procs, optional parantheses. It’s easy to write expressive code in such a beautiful language. In Java people have to fight the language to write beautiful code. All the getters, all the setters. The conventions make it hard to write meaningful code. But somehow a little rose blossomed in this wasteland. Fluent Interfaces. Instead of writing
Point p = new Point();
p.setX(1);
p.setY(2);
You can now write code that reads more like a specification.
Point p = new Point().x(1).y(2);
It is easy to write that in a language like Java. Different method signatures can have different return values. So it’s easy to write something like this:
public Point x(int xValue) {
// code
}
public int x() {
// code
}
The compiler can figure out which method to call and which return value to expect. Back to Ruby: You can’t do that. Ruby doesn’t really know the concept of overloading. But who needs overloading if you can have default arguments.
def x(x_value = nil)
if x_value.nil?
@x_value
else
@x_value = x_value
self
end
end
That is truely ugly. Default arguments, If statements and if you want to write more than one of those methods it gets tedious to copy and paste the same code over and over again. Essentially you are doing the work of the java compiler. Checking if an argument is given and then doing the right thing. We are in Ruby, we should’nt have to write so much boilerplate code. And we don’t have to.
We can write a module that does all the work for us.
module FluentInterface
def fluent_accessor(name)
define_method(name.to_sym) do |v=nil|
if(v.nil?)
self.instance_variable_get("@#{name}")
else
self.instance_variable_set("@#{name}", v)
self
end
end
end
end
So essentially what we are doing is this. If someone calls the fluent_accessor
method we define a new method on the fly that does exactly the same as the
code above. But that’s pretty unflexible. Sometimes the method name doesn’t
match the instance variable name. So we should also be capable of injecting
the instance variable name
# To keep it short I use the abbreviation va
# instead of variable
module FluentInterface
def fluent_accessor(name, va_name = nil)
define_method(name.to_sym) do |v=nil|
va = va_name.nil? ? name : va_name
if(v.nil?)
self.instance_variable_get("@#{va}")
else
self.instance_variable_set("@#{va}", v)
self
end
end
end
end
Now we can write fluent interfaces without the need to copy and paste all the code over and over again
class Line
extend FluentInterface
fluent_accessor :from, :start_point
fluent_accessor :to, :end_point
end
l = Line.new.from([0, 0]).to([1, 1])
l.from # => [0, 0]
l.to # => [1, 1]
So you see, fluent interfaces which are a natural fit for statically typed languages also enrich the world of dynamically typed languages like Ruby with their expressive syntax.