depends_on
For one of my projects I had the need for a polymorphic association in which one object required the existence of the other. I had written a specific version of this, but decided it could easily be abstracted. I didn't go with the built in ActiveRecord polymorphic features as I wanted to strip it down to the basics to see if it would work. Why? I would love to use this in DataMapper at some point.
module Stonean
module DependsOn
def self.included(base)
base.extend Stonean::DependsOn::ClassMethods
end
module ClassMethods
def depends_on(model_sym, options = {})
has_one model_sym,
:foreign_key => "#{options[:as]}_id",
:conditions => "#{options[:as]}_type = '#{self.name}'"
validates_presence_of model_sym
validates_associated model_sym
define_save_method(model_sym, options[:as])
before_save "save_#{model_sym}".to_sym
options[:attrs].each{|attr| define_accessors(model_sym, attr)}
end
def define_save_method(model_sym, poly)
define_method "save_#{model_sym}" do
eval("self.#{model_sym}.#{poly}_type = self.class.name")
eval("self.#{model_sym}.#{poly}_id = self.id")
eval("self.#{model_sym}.save")
end
end
def define_accessors(model_sym, attr)
define_method attr do
eval("self.#{model_sym} ? self.#{model_sym}.#{attr} : nil")
end
define_method "#{attr}=" do |val|
model_defined = eval("self.#{model_sym}")
unless model_defined
eval("self.#{model_sym} = self.build_#{model_sym}")
end
eval("self.#{model_sym}.#{attr}= val")
end
end
end
end
end
ActiveRecord::Base.send :include, Stonean::DependsOn
Here's an example, but first a little information. Content is a model that all "presentable" objects in my cms depend on. It holds the name and url attributes so when you are calling message.name, you are really calling message.content.name.
class Message < ActiveRecord::Base
depends_on :content, :attrs => [:name, :url], :as => :presentable
end
This means your form and views can use these methods and not worry about the underlying association. for example:
text_field_tag "message[name]", value
Now you don't have to do anything special in your views or controller to build and save the depends_on object.
This example is very specific, but with a little modification, can be used to implement a class table inheritance architecture. I definitely plan on doing this soon.
So what are the drawbacks? The major one is the find method. As it's written now, you will make two calls to the database: one for the main object and one for the dependent object. That blows. I will be addressing this issue very soon.
I will be releasing this as a gem in the near future, but for now you can just copy the code above and add it into your config/initializers directory.
If you have any ideas or suggestions, I would love to hear them.
