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
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
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
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.
Sorry, had a typo in the Context name that was causing it.
Thanks for letting me know T.M. I’ve only tested on 1.9.3p374 where it does WOMM
[…] Using Moby to do injectionless DCI in Ruby part I […]
[…] Using maroon to do injectionless DCI in Ruby part I […]