ActiveRecord / Single table inheritance

From WhyNotWiki
Jump to: navigation, search


ActiveRecord / Single table inheritance  edit   (Category  edit)


Contents

Introduction

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

Can you have records of the base class type?

Yes!

irb -> m = MenuDuJour::MenuItem.create

irb -> m.reload
    => #<MenuDuJour::MenuItem:0xb7513120 @attributes={"name"=>nil, "type"=>nil, "id"=>"5", "display_name"=>nil,} >

Can I change the type of a record after it's been created?

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


Reusing a shared column in two different subclasses but using a different column name

Gregory Seidman. http://lists.rubyonrails.org/pipermail/rails/2006-March/027914.html. Retrieved on 2007-05-11 11:18.


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
end

Now 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.

The "type" column

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.

Ads
Personal tools