Help needed on STI syntax


#1

Struggling a little with getting the syntax right for a STI model. Prob
just being a bit dense.

Couple of questions I’m hoping you guys can answer:

Assume here I’ve got:
class Person
class Manager < Person
class Slave < Person

  1. How does person[:type] differ from person.type when type is the
    column used to specify the subclass. They seem to output the same
    (“Manager”) but the first works with an if statement (if person[:type] =
    “Manager”), the second doesn’t.

  2. How do I force the creation method that follows a form submission to
    put something into a particular subclass. At the moment I have one form
    that can handle all the subtypes and there’s a hidden field <%=
    hidden_field(‘person’, ‘type’, “value” => @person[:type]) %> that varies
    depending on how it’s called, e.g. @person = Manager.new or @person =
    Slave.new. However, although that seems to pass the correct variables
    along, the type is left as null.

There’s not much on the wiki (or anywhere else) on STI, so any help
would be greatly appreciated, and I’ll update the wiki.

Thanks
Chris T


#2
  1. you definitely want to use person[:type] instead of person.type as
    ‘type’
    is a built-in ruby method (although deprecated) and you could have some
    unexpected behavior if you try to use person.type.

  2. to make sure I understand correctly you have something like:

simplified

def new
case @params[:person_type]
when “Manager”
@person = Manager.new
when “Slave”
@person = Slave.new
end
end

in your view, do:

<%= hidden_field_tag “person_type”, @person[:type] %>

then in your create method:

#again, simplified
def create
case @params[:person_type]
when “Manager”
@person = Manager.new(@params[:person])
when “Slave”
@person = Slave.new(@params[:person])
end
if @person.save
redirect_to :action => :list
else
render_action :new
end
end

you could even go so far as to create a factory class method in your
Person
class

class Person < ActiveRecord::Base
def self.factory(type, params = nil)
case type
when “Manager”
return Manager.new(params)
when “Slave”
return Slave.new(params)
else
return nil
end
end
end

then in your new and create methods, you can do

def new
@person = Person.factory(@params[:person_type])
end

def create
@person = Person.factory(@params[:person_type], @params[:person])
if @person.save

end
end


#3
  1. How does person[:type] differ from person.type when type is the
    column used to specify the subclass. They seem to output the same
    (“Manager”) but the first works with an if statement (if person[:type] =
    “Manager”), the second doesn’t.

From my understanding

person.type returns the string object with the property name ‘type’

person[:type] points to the string value of the property with the key
‘type’ similar to if it were a hash table

So comparing an object to a string value returns false.

Ruby casts the string object to a string when you use it in the context
of

<%= person.type %>

Tony


#4

If you want to create a ‘manager’ or ‘slave’ but don’t know which, you
can do something like this…

def create
@person = Person.new
@person[:type] = “Manager” # or get it from a param[:person][:type]

In other words, if you manually set the ‘type’ column to the name of the
class you want it to have, when you pull that record from the database,
AR will re-create it as a ‘Manager’ instead of a ‘Person’.

_Kevin


#5

Chris

That (or something very similar) sorted it. Thanks. Still not 100%
understanding why type can’t be passed as a parameter with the rest of
the :person ones, but at least it’s working now.

Cheers
Chris T


#6

Anthony

OK. That sort of makes sense :slight_smile:

Cheers

Chris T.


#7

Kevin

Thanks for this. I ended up doing something very similar. Why does it
need to be set explicitly, though, i.e. separate from the other
parameters for person? [If you have the form return a hash something
like “person”=>{“name”=>“Fred”, “type”=>“Manager”, “age”=>“28”} I’m
missing something here, as it doesn’t seem to work.]

Cheers
Chris


#8

And for anyone who cares, if you want different validation rules in the
parent model use:
if (type.to_s == “Member”)

It took me a good couple of hours to figure that one out.

Dan


#9

If you know the type, you can also do something like this:

person_type = “Manager”
@person = eval(person_type + “.new”)

Or use constantize:

person_type = “Manager”
@person = person_type.constantize.new

(Thanks again to Ezra for that last bit.)

Sean

On 16 Mar 2006 15:13:07 -0000, Kevin O.


#10

Dan, Anthony, etc
Thanks for this. I’ll try to add a couple of notes to the wiki entry
tomorrow.
CHris T