Ruby gives you the ability to add methods to existing classes and in rails, this lets you add functionality to core classes. That way, you can use custom class methods in any Model, Controller, or View.
Let's say, I have an Address table in my DB. Addresses can belong to either a Person or Company.
I add 2 columns to my Address table:
owner_type
owner_id
So, my addresses will look something like this:
address owner_type owner_id 55 elm st. Person 45 22 main st. Company 99
I want to use functionality in other models too. So, I decide to put this functionality somewhere I can re-use it. First, I want to be able to add the columns to a table. I'm using rails migrations, so all I need to add some functionality to the base Table class.
First, I create a file
module ConnectionAdapters
class TableDefinition
def has_polymorphic_owner
column "owner_type", :string
column "owner_id", :int
end
end
end
end
this will add a method called 'has_polymorphic_owner' to the TableDefinition class. Once I do this, I can use this in my migration:
create_table :addresses do |t|
t.column :address, :string
t.has_polymorphic_owner
end
Ok, so I have a nice way of adding columns. Now, I want to add some functionality to my model. It'd be nice if I could just say ... address.parent and get the object for that owner. I don't want to look up the owner_type every time. So, lets write some meta-functions...
In the same file:
class Base
def self.has_polymorphic_owner
#Get the parent object
self.send(:define_method, :parent, Proc.new do
klass = Kernel.const_get(self.owner_type)
klass.find(self.owner_id)
end)
end
def self.polymorphic_parent_to(child_name)
self.send(:define_method, "first_#{child_name}".to_sym, Proc.new do
childKlass = Kernel.const_get(child_name.to_s.capitalize)
childKlass.find(:first, :conditions => "owner_type = '#{self.class.to_s}' and owner_id = #{self.id}")
end)
self.send(:define_method, "all_#{child_name}".to_sym, Proc.new do
childKlass = Kernel.const_get(child_name.to_s.capitalize)
childKlass.find(:all, :conditions => "owner_type = '#{self.class.to_s}' and owner_id = #{self.id}")
end)
#This will delete the children before deleting the parent.
destroy_method = "destroy_#{child_name}".to_sym
before_destroy destroy_method
self.send(:define_method, destroy_method, Proc.new do
child = instance_variables_get("all_#{child_name}")all_address
child.destroy if child_name
end)
end
end
end
This did a couple of interesting things, I defined 2 main methods: has_polymorphic_owner and has_polymorphic_child. These are both class files I can call inside my models. When these methods are called, they define other methods in that class. So, now my models can look like this:
class Address < ActiveRecord::Base
has_polymorphic_owner
end
class Person < ActiveRecord::Base
polymorphic_parent_to :Address
end
Then i can say things like:
Address.find(1).parent
Person.find(1).all_address
Person.find(1).destroy (This will delete all the associated Addresses too)
No comments:
Post a Comment