Forum: Ruby on Rails Help needed on STI syntax

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
92dafea96df3ff949b426b235a19ae0e?d=identicon&s=25 ChrisT (Guest)
on 2006-03-16 13:25
(Received via mailing list)
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
A2c85dc5ee81b12e3cc0a6522e8d079d?d=identicon&s=25 Chris Hall (Guest)
on 2006-03-16 14:39
(Received via mailing list)
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
F59329dc91cba06600ff65c85fd3e93c?d=identicon&s=25 Anthony Green (acgreen)
on 2006-03-16 15:24
> 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
5d15c6821f3c3054c04b85471824ba7c?d=identicon&s=25 Kevin Olbrich (Guest)
on 2006-03-16 16:15
(Received via mailing list)
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
92dafea96df3ff949b426b235a19ae0e?d=identicon&s=25 ChrisT (Guest)
on 2006-03-16 17:38
(Received via mailing list)
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
92dafea96df3ff949b426b235a19ae0e?d=identicon&s=25 ChrisT (Guest)
on 2006-03-16 17:50
(Received via mailing list)
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
92dafea96df3ff949b426b235a19ae0e?d=identicon&s=25 ChrisT (Guest)
on 2006-03-16 17:50
(Received via mailing list)
Anthony

OK. That sort of makes sense :-)

Cheers

Chris T.
2b2c2a705ed12f8fb327c7b4c56456c6?d=identicon&s=25 Sean Hussey (seanhussey)
on 2006-03-16 19:35
(Received via mailing list)
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 Olbrich
54a6fc785e9fcf73ead8e387432b32f5?d=identicon&s=25 Dan Harper (Guest)
on 2006-03-16 23:32
(Received via mailing list)
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
92dafea96df3ff949b426b235a19ae0e?d=identicon&s=25 ChrisT (Guest)
on 2006-03-17 00:21
(Received via mailing list)
Dan, Anthony, etc
Thanks for this. I'll try to add a couple of notes to the wiki entry
tomorrow.
CHris T
This topic is locked and can not be replied to.