Mixin for inheritance and a use case with ActiveRecord
back
Next article:
ActiveRecord class association for the same object
view
O-O purists do not like multiple inheritance because it can cause ambiguity if not used correctly. Unfortunately, we are attempting to model the real world, which sometimes exhibits behaviours that require multiple inheritance to solve. Ruby has its own way of solving the multiple inheritance problem, like all other languages. The way it does it is by using a concept called mixin. Mixin is a lump of code which gets included into the class at runtime to enable that class to exhibit the behaviour prescribed by that mixin. As with other languages, mixin has its usefulness, but also some caveat that need to be watched carefully. Imagine a typical use case. You have just created a whole bunch of tables, let's say topics, articles and surveys. In Rails, you would simply use the script/generate script to generate the models for these tables, namely Topic, Article, and Survey, which are one liners. in app/models/topic.rb class Topic < ActiveRecord::Base end in app/models/article.rb class Article < ActiveRecord::Base end in app/models/survey.rb class Survey < ActiveRecord::Base end So far so good. Now the problem starts when I want to be able to associate a common attribute with each of the tables, say an uploaded image. This will mean adding an image column to each of the tables and a class attribute image to each of the declarations above. No big deal, the code is still pretty simple to manage and maintain. However, if I want to introduce some other boilerplate code around the class attribute image to maybe do some kind of content management on the file system, like generating a series of thumbnails and other frequently used sizes, then it gets a bit more hairy. The most straight forward way would be to add this code into a mutator method for each of the classes. This approach is not good in that if I have a bug somewhere, I will have to fix it in all the classes each time. Why not get all three classes to inherit a common superclass ? I hear you say. Well, usually that should work well, but in the case of ActiveRecord, dynamic bindings between the object world and the relational world occur using inflection (or dynamic discovery of class names and attributes). This means that if you create a common superclass called ImageRecord for the above classes, ActiveRecord will attempt to bind to a table called image_records. Not really what we want.This is where mixins come in. You can declare a module called ImageRecord, which contains the image class attribute and all the boilerplate code. Module ImageRecord file_column :image def generate_thumbs ... end def clear_thumbs ... end end class Topic include ImageRecord end class Article include ImageRecord end class Survey include ImageRecord end Problem solved ? Not really. There are two problems with this approach: - methods added in this way are instance specific, not class specific. This means your class attribute called image is out of the question. If you attempt to declare this class attribute in the mixin, that is where it will stay. It will not be propagated to Topic, Article and Survey like you wanted.
- A mixin is not compiled in to your class as if it is part of your class. In fact it is invoked on a need to call basis. This means you can happily have a whole bunch of syntax errors in your mixin and they will never be discovered unless their services are required. This could work for you or against you really. I will leave that to your imagination (one definite drawback with a mixin is if you make changes to it, it will not be automatically hot swapped for you by the Ruby runtime like in the MVC (model-view-controller) classes. So each time you make changes to it, you will have to restart the WEBrick webserver or kill the fastcgi process on Apache to force unload the mixin).
The first problem can be overcome by making use of a special trick in Ruby for dynamically defining class methods in a mixin. There is a module method which gets invoked each time it is included into another class, namely append_features(base). This method is passed the class of the object performing the include (Topic, Article or Survey in our case). We can make use of this feature to inject our own class methods and also add the class attribute for the image column like so: Module ImageRecord def generate_thumbs ... end def clear_thumbs ... end def self.append_features(base) super base.file_column :image base.extend(ClassMethods) end module ClassMethods ... end end
back
Next article:
ActiveRecord class association for the same object
view
|