November 28, 2012 by Rob Nichols
I spent much of my time at university tapping on rocks with a small hammer. A geologist I, with the beard to prove it. Yet now the nearest I get to geology is working with Ruby gems.
With my current project, I needed a tool to compare sets of arrays to user defined rules. In other words, a tool that would take a text rule, compare it to an array of objects, and return true where there were matches. The result was array_logic.
My first step was to work out how I wanted my rules to work. For this, I took inspiration from Justin French’s story of how he created formtastic. That is, I sat down and wrote out a set of rules and how I wanted them to work:
a1 = Answer.find(1)
a2 = Answer.find(2)
an = Answer.find(n)
rule_set(1) = '(a1 and a2) or (a3 and a4)'
rule_set(2) = 'a1 and not a2'
rule_set(3) = '2 in a1 a2 a3'
rule_set(4) = '(2 in a1 a2 a3) or (1 in a4 a5)'
Once I’d done this, I realised that the functionality could be achieved by working with object ids only. So my objects didn’t need to be ActiveRecord objects or any other complex object. They just needed to have an id method that returned an integer. To manipulate such objects, I could use bare ruby which would allow me to keep my code very simple and self-contained.
I also realised that it was likely that I would want to use this functionality elsewhere. As it would be easy to make a solution as a standalone ruby app, it would be very easy to make it into a gem.
One of the first things I did was create the Thing object. This is just a class whose instances have an id method. I added to this a class method make that made it easy to generate sequences of things for testing. I now had an object I could play with to get the functionality I wanted.
Next I needed a set of tests that described how the new tool needed to behave. However, before creating these I needed to create an ruby app framework.
The framework for a ruby app can be a lot simpler than that required for rails. All I needed was a lib folder to hold my code, a test folder for my tests and a few files in the root. They were:
- licence file: Not required, but a good idea to have one if you want to allow others to use your code.
- README: To start with I put my rules text here. I made it a .rdoc file, as this allowed me to use rdoc markdown formatting to make the text a little prettier.
- A rake file: This allowed me to run a couple of useful rake tasks. Specifically rake test
As I wanted to package up my code in a gem that could be used in many different projects, I needed to think about my object naming carefully. I did not want the array_logic classes interfering with objects within the parent applications (or vice versa). The easiest way to avoid this is to use a namespace.
There are two steps to using a namespace in ruby. First to create a module that acts as the name space container. Once this is created, any new class needs to be defined within this module. Secondly ruby expects named spaced classes to be stored within a folder with the underscore version of the namespace name. So I created the module ArrayLogic and put the files containing classes within this name space, in a folder called ‘array_logic’
To put each class definition within the new namespace, I used this pattern:
I could also have used this style of name spacing:
I also added to my ArrayLogic declaration file, require_relative declarations to the classes within the name space. This meant that simply requiring ‘array_logic’ from outside the namespace, would automatically provide the required classes within the namespace.
Notice that I also name spaced my test files. So they went into /test/array_logic and were created within the module ArrayLogic. This meant that I could refer to the class Rule rather than having to use ArrayLogic::Rule within each test.
With that in place, I could start building my application. I started by creating a test for a very simple rule: ‘t1 and t2’, which translated as, “the array must contain an object with an id of 1, and an object with id of 2”. I then built my first version of ArrayLogic::Rule, that would pass this test.
It was then a case of progressively increasing the complexity of test and/or functionality described by the tests and updating my Rule class to suit. In this way I was able to start simple, and gradually develop a solution that did what was needed.
Once I had a version of array_logic that was ready to publish, it was then time to create a gem from it. To do this there were four steps.
- Set up a RubyGems account: You can set up a RubyGems account here.
- Create a gemspec file
- Build the gem
- Push the gem to RubyGems
Notice that I have defined version within the ArrayLogic name space. That makes it easier to manage changing version numbers. You cannot push a new revision of a gem into the same version number. So as your gem develops you need to increment the version number.
Also notice that I have set the home as the github location where the code is stored. That creates a nice link between the gem and the code in the repository.
The resultant gem is held here.
To use the new gem, I added this to my rails app’s gemfile:
gem 'array_logic' # Used in rule_sets
Note that I added a comment to the gem declaration. I believe this is good practice (see my blog for an explanation).
I was then able to use array_logic in my RuleSet model.
Creating gems makes it much easier to reuse your code. I find it also helps me focus on how to abstract my code into modular blocks, and as a result I believe I am becoming a better programmer. As you can see above, creating a gem is very straightforward and something I think every ruby developer should be aiming to do.