ActiveRecord / Single table inheritance edit (Category edit)
Contents |
It is sometimes desirable to be able to subclass your ActiveRecord objects. For example, if you want to have an Employee and a Customer, both of which share some common fields. You can do this without any problem:
class Person < ActiveRecord::Base class Employee < Person class Customer < Person
But once you save your object to the database, it forgets what type it is.
Solution: Single Table Inheritance. To use it, simply add a TEXT type column to your table (people in our case). Rails handles the rest.
If there are any columns that are only used by one subclass and not another, then you can just leave the values of those columns blank for the records/models that don't use them... (Somewhat wasteful and non-normalized, perhaps, but it works.)
Employee.create(:name => "Bob the underpaid", :some_column => "12345")
Customer.create(:name => "Fred the impulse spender", :another_column => "4567")
Person.find(:all).each { |person|
puts person.class
}
Outputs:
Employee Customer
Yes!
irb -> m = MenuDuJour::MenuItem.create
irb -> m.reload
=> #<MenuDuJour::MenuItem:0xb7513120 @attributes={"name"=>nil, "type"=>nil, "id"=>"5", "display_name"=>nil,} >
Yes!
irb -> m.type
(irb):36: warning: Object#type is deprecated; use Object#class
=> MenuDuJour::MenuItem
irb -> m.class
=> MenuDuJour::MenuItem
irb -> m.type = 'Label'
=> "Label"
irb -> m.save!
=> true
irb -> m.reload
=> #<MenuDuJour::MenuItem:0xb74fc164 @attributes={"name"=>nil, "type"=>"Label", "id"=>"5", "display_name"=>nil,} >
irb -> m.attributes['type']
=> "Label"
irb -> m.type = 'MenuItem' # Base class
=> "MenuItem"
irb -> m.save!; m.reload
=> #<MenuDuJour::MenuItem:0xb74fc164 @attributes={"name"=>nil, "type"=>"MenuItem", "id"=>"5", "display_name"=>nil,} >
irb -> m.attributes['type']
=> "MenuItem"
irb -> m.type = nil # Base class
=> nil
irb -> m.save!; m.reload
=> #<MenuDuJour::MenuItem:0xb74fc164 @attributes={"name"=>nil, "type"=>nil, "id"=>"5", "display_name"=>nil,} >
irb -> m.attributes['type']
=> nil
Gregory Seidman. http://lists.rubyonrails.org/pipermail/rails/2006-March/027914.html.
class Super < ActiveRecord::Base def self.alias_field(old_field, new_field) define_method(new_field) { self.send(old_field) } define_method("#{new_field}=") { |arg| self.send("#{old_field}=", arg) } define_method("#{new_field}?") { self.send("#{old_field}?") } end end class Sub < Super alias_field :text1, :address1 alias_field :text2, :address2 alias_field :text3, :zipcode endNow I can do all of the following:
fail "Where's the address?" unless foo.address1? foo.address1 = '1600 Pennsylvania Ave.' addr = foo.address1...and it will use text1?, text1=, and text1, respectively.
ryanb on http://railsforum.com/viewtopic.php?pid=22131#p22131
"type" is protected from mass assignment so you have to set it directly.
ryanb on http://railsforum.com/viewtopic.php?pid=22135#p22135
[Modified quotation (category)] I think you have to set it like this:
user[:type] = params[:type]rather than like this:
user.type = params[:type]because "type" is also a reserved method name in Ruby so you can't really set it directly.