Archive for July, 2006

Extending ActiveRecord with a Mixin

So in the last post we got to the point where a model class has been modified to be able to access associated classes in a simple way. Now we want to add this functionality to all model classes, that is, those which are a sub class of ActiveRecord::Base.

Ruby Mixins

If you have done any digging into Ruby (get a Pickaxe!), you should know the OO basics like class extensions, inheritance, class methods etc. I’m assuming this much anyway – there are thousands of Ruby OO examples and tutorials out there, you can’t avoid it.

A Mixin is a way of modifying the functionality of a class by ‘applying’ a module to it. This is how I understand the technology anyway:

  • You write a module with the methods you want ‘bolted on’ to your base class.
  • You extend your base class with that module (there are multiple ways to do this).

It is a handy work around for multiple inheritence (which Ruby does not support), as you can mixin any number of modules with a class. Again, there are hundreds of Mixin introductions available on the almighty Google, so I’m not going to explain the path to my understanding of Mixins, I’m just going to show you how I have used them.

So, on with the module writing.

module ModelExtensions

  public

  def get_associated_model_classes(type = :belongs_to)

    # no need for self here, the method is being added to the class

    # (eventually)

    aggregate_objs = self.reflect_on_all_associations(type)

    aggregate_objs.map {|m| m.klass}

    # let’s use this elegant array builder eh?

  end

end

I’ve put this code in /lib/model_extensions.rb – Rails will be able to pick this up automatically for use elsewhere.

If I have understood my Pickaxe correctly, all method calls in Ruby are ultimately messages being sent to the receiver object. Because of this, it is quite simple to extend AR::Base without declaring the class or editing any code other than the functionality you want to add. To apply our Mixin, we have to tell AR:Bass to extend itself with the contents of our module, ModelExtensions. I’ve added the following to the end of /config/environment.rb:

require ‘model_extensions’

# Lib is available to Rails as it’s in the /lib directory!

ActiveRecord::Base.send(:extend, ModelExtensions)

# Send the message to AR::Base to extend using our module

 

Ok let’s fire up the console and test the experiment.

C:\vhost\cdb>ruby script/console

Loading development environment.

>> Currency.get_associated_model_classes :has_many

=> [Spend]

This shows that the method works for all model classes (declared as an extension to AR::Base) and that it also works for different associations. Job done!

Bringing it all together

There’s a final part to this solution, involving some new class functionality and a new helper. With any luck I’ll get it online before the weekend.

Comments (9)

Rails Scaffolding - Automatic Navigation Generation

I’ve been working quite a while on this prototype project now and I’m quite happy to admit that the majority of the work has been an exercise in wrapping up a scaffolded application into something more presentable and usable for the target audience. This post is going to try and describe what I think could work for future applications in the style of a Rails Recipe.

Problem Description

As I’ve gone through the process of making tweaks and alterations to the main scaffolded files to achieve what I’ve wanted, I’ve realised that while hooking together sets of scaffolds can get you so far – presenting the user with an intuitive navigation for accessing these scaffolds could be the main stepping stone in moving from scaffolds to a first demonstrable prototype.

For most applications the user will be aware of some top level CRUD feature (or set of features) which will guide their use through the rest of the application. Even DHH himself is suggesting we embrace the ‘CRUDness’ of our applications, so I think our navigation should be able to automatically represent the model structure and hierarchy of our system.

Essentially what I would like is a helper that can take the current controller or action and, based on our model structure, suggest appropriate related controllers to access and provide a way of navigating the hierarchy of our system. If everything is CRUD-based this should result in natural learning and use of the system, especially if the user is already familiar with the hierarchy of a domain, e.g. the application is to supplement or replace an existing set of processes.

The Solution

My suggestion is that we extend ActiveRecord::Base to provide the extra functionality I would like. If we modify the class by way of a Mixin, we can add functionality to the class and assume that any future derived classes (Models) will share this functionality.

Let’s set up an example to follow – below is a set of models which make up part of a system.

class Spend < ActiveRecord::Base

  belongs_to :currency

  belongs_to :service

end



class Service < ActiveRecord::Base

  has_many :spends

end



class Currency < ActiveRecord::Base

  has_many :spends

end

Quite a simple set of relationships to begin with. As far as foreign records are concerned a Spend record is composed of the Service we are spending on and the Currency of the spend.

Using Ruby Reflection to access associated models

ActiveRecord has a method of querying the class itself to return all associations – this is how the rails code can determine what tables to join etc when performing queries. The ActiveRecord API for reflection tells us that the method reflect_on_all_associations can return an array of AggregateReflection objects. The base class of the AggregrateReflection (MacroReflection) is where we can find the information we want – the class of the associated models.

The code below shows how you can wrap up these method calls into something more friendly. I don’t really want to be handling these kind of objects regularly in my code anyway!

class Spend < ActiveRecord::Base

  belongs_to :currency

  belongs_to :service



  def self.get_associated_model_classes(type = :belongs_to)

    # defined as self.get.. as we want to call the method on the class

    aggregate_objs = self.reflect_on_all_associations(type)

    out = Array.new

    aggregate_objs.each do |mr|

      out << mr.klass

    end

    # all this ‘out’ stuff can be replaced with:

    # aggregate_objs.map {|m| m.klass}

    out

  end

end

I’m sure there is a much cleaner way to handle the array processing and output, probably in the form of aggregate_objs.map {|m| m.klass}, but when I wrote the code I was still struggling with getting my blocks working properly, so I keep track of my returns with a variable called ‘out’ just in case.

So now, if you put all that code into IRB (after requiring active_record.rb and active_support.rb), you should be able to run the following:

irb(main):037:0> Spend.get_associated_model_classes
=> [Currency, Service]
irb(main):038:0>

Air Guitar! Now we have a workable array of classes to play with. Unfortunately this is limited to working with the Spend class, which is pretty useless. Now we’re going to bolt this functionality onto the AR::Base class so that all of our models can be queried in the same way. We will do this with a Mixin.

Extending ActiveRecord with a Mixin

Coming soon…I’ve decided to split this into 2 or 3 parts otherwise It’s going to take me til the end of the week before I get anything online. Release early!

Comments (3)

Always save a draft!

So I was up quite late last night. After learning some cool new stuff about RoR I decided I should document my journey and write a post about it. Spent about 2 hours putting something together in the format of a Rails Recipe and left it to be finished in the morning.

Windows Update decided it wanted to install some new version of .NET for me last night however, so rebooted the machine at 3:07AM, closing all open windows, including the Firefox instance I was editing my post in.

Wordpress needs auto-save draft (ala gmail) asap.

Comments (3)

Where have I been?

Everything came to a stop last week, but this time it’s for a very good reason!

I got my first Rails contract!

There has been much rejoicing and a lot of really enjoyable coding going on since I had the go-ahead for the project, so hopefully this is the start of a trend now that I can put Rails on the Beanlogic skillset officially.

I’d say more about the project, but unfortunately I’m tied up in NDAs for it. All I can say is that I’m developing a prototype for an application for a rather large bank. The best part is that Rails has managed to perform very well with a very large dataset, so the whole thing might not end it’s life as a prototype…I might get the contract to develop it completely into a production app…fingers crossed eh!

YEAH!

Comments