Using maroon to do injectionless DCI in Ruby part I

Posted: February 14, 2013 in DCI
Tags: , , ,

Yesterday I published my first gem ever. It’s called maroon and makes injections less DCI possible in Ruby. I hadn’t coded any Ruby prior to this but done similar work in C#. It took quite some time to reach a working compiler when I was creating injectionless DCI for Marvin in C#, a language I’ve been using for more than a decade. So being unfamiliar with Ruby I was surprised how fast I could actually reach a working solution and impressed at how little code was needed.

There was three separate factors that led me to work on this gem. The first two were different discussions. One on stackoverflow.com where a member of the SO community claimed that DCI was always going to be slow compared to regular method invocations and the other on the object-composition google group where something a long the line of “it can’t be done” was postulated. Doing something that can’t be done and doing it faster than possible seemed like fun challenges. The final push was an invitation to wroc_love.rb as a speaker. If I was going to present at a Ruby conference I thought I better learn Ruby.

The rest of this post is going to be a short introduction to maroon.

To get started install the gem

gem install maroon

This might install a few dependencies as well depending on whether they are already installed on your machine (just ordinary package management in work for you)

To follow traditions we could start with a hello world example and then disect that

require 'maroon'

Context::define :Greeter do
  role :who do
    say do
      self
    end
  end
  role :greeting do end
  greet do
    p "#{greeting} #{who.say}!"
  end
end

class Greeter
  def initialize(greeting,who)
    @who = who
    @greeting = greeting
  end
end

Greeter.new('Hello','world').greet #Will print "Hello world!"

The first line just requires the gem. Nothing fancy here.
The second line

Context::define :Greeter do

defines a new context called Greeter. As we will see later a context is simply a class. The difference is in how the methods are handled
Within a context you can define zero or more roles. Though if there’s no roles there’s no need for a context.

The next snippet defines a role called who

role :who do
  say do
     self
  end
end

There’s a single role method in this role which is called ‘say’. The method simply returns the role player. Notice that self in a role method is the role player and not an instance of the type we are defining. This might seem counter intuitive at first but when writing role methods this will feel very natural.

following the role ‘who’ is another role

role :greeting do end

this role does not have any role methods and could be a field (and in reality will be) however following the DCI line of thinking this is a role and we define it as such

The last part of the context is an interaction. Interactions are defined in the same manner as a role method. The only difference is that they are defined directly in the context and not inside a role.

greet do
   p "#{greeting} #{who.say}!"
end

The above snippet defines an interaction called ‘greet’. Interactions can be seen as public instance methods. That is what they will end up being but of course from a DCI perspective they are more than just that. From a DCI perspective interactions represent a graph of communicating objects. It is a core goal of DCI to be able to not only model the objects of a domain but also to model the interactions between these objects as one coherent structure.
In this interaction there’s two interacting objects, represented by the two roles of the context: ‘greeting’ and ‘who’

You can extend the context just like you can extend any other class in Ruby and it will often be advantageous to extend with an initializer that can bind role players to roles since there’s no way of doing this from outside the context.

class Greeter
  def initialize(greeting,who)
    @who = who
    @greeting = greeting
  end
end

We’re binding a role player to each of the roles we have. As you can see role players are kept in a field named after the role.You should only use this field in two cases. When binding as we are doing here and when calling an instance method on the role player where a role method with the same name is defined as well. You can’t call a role method using the field but will have to use the getter with the same name as the role.

The last line of code in the hello world example is where we instantiate the context object and execute the interaction
Greeter.new(‘Hello’,‘world’).greet #Will print “Hello world!”

So that’s our very first DCI program using maroon.

If you are interested in the inner workings of maroon, all you will have to do is execute the above program using irb or print the out put of define to the console. define returns two objects. The first being the newly defined class and the second being the actual source of the class. In the next post I will explain the source code

Advertisement
Comments
  1. Marc Grue says:

    Awesome, Rune! Intuitive and clean.
    A small typo: the interaction method is called “greet” in the code overview but “greeting” in the comments/code snippets later. It should be “greet” all the way, right?
    Cheers,
    Marc Grue

    • runefs says:

      Thanks Marc. Yes it should be greet everywhere for the interaction and greeting for the role. I’ve updated the post accordingly thanks for spotting the potentially rather confusing mix up

  2. T.M. says:

    Your example doesn’t work with ruby 1.9.3p327:

    hello_world.rb:24:in `’: undefined method `greeting’ for # (NoMethodError)

    I thought maybe it was a typo and it should have been:

    Greeter.new(‘Hello’, ‘world’).greet

    but that gives a NoMethodError too.

  3. […] Using Moby to do injectionless DCI in Ruby part I […]

  4. […] Using maroon to do injectionless DCI in Ruby part I […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s