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.
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:
# 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.
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.
