STI and change the type

Hey,

I’m trying to use STI in my models, while writing a little cms.
A have a model called Page and some subclasses like ContentPage,
Sysfolder etc. Some classes share similar attributes, so I use STI and
one table for all types.
Now I’m writing a form for edititing a page and I want to have the
opportunity to change the type of it at runtime. But that seems to be
a problem:

When I do:

page = Page.find(x) # page.type is Sysfolder
page.update_attributes(params[:record]) # with
params[:record][:type] = ContentPage

or additionally

page.type = ContentPage # or page[:type] = ContentPage
page.save!

everything is saved, but not the type. When I try to debug the class
of page I get Sysfolder (thanks to STI) and I think here lies the
problem, because I cannot change the class at runtime. I tried a lot
af things like creating a new page and try to copy it over, but none
worked.

Is there a workaround or option I do not see right now?

Regards,

Mike

Mike wrote:

When I do:
everything is saved, but not the type. When I try to debug the class

Try page[:type] = “ContentPage”

Hope this helps
Chris

http://www.autopendium.com
Stuff about old cars

As you can see in my first post (commented out), I already tried this
one out, but it’s not working. The problem seems to be that when I say
page = Page.find (x), the class of page isn’t “Page”, it’s Sysfolder
instead (if the “type” of page is Sysfolder) and I’am not able to
transform a Sysfolder into another subclass of page (why?).
The same problem seems to affect the migration too:
By defining a default value for the type like:

t.column :type, :string, :default => ‘Sysfolder’

it’s not possible to use the create-method on Page with another type.
As a result of the create-method, I always get a Sysfolder!
I don’t understand, why ActiveRecord doesn’t let me change the type in
a normal way!

Does anybody found a work around for this problem? I don’t need to use
the create-method, but a manual change of the type/class would help me
a lot.
Maybe I’ll give it a try to use pure SQL to change the type.

Thanks in advance.

Mike

Gabriel Gironda wrote:

class SummaryPage < Page
‘summary’ attribute that is nil in this case. Alternative solutions

By defining a default value for the type like:
a lot.

I believe you can change the type manually as I had it in my post
(providing it passes validation) – at last it seems to work in the
console. However, you will need to instantiate the item again as reload
won’t work, as it’s looking for the old “type”. However, I agree it is
bad practice.

Cheers
Chris

http://www.autopendium.com
Stuff about old cars

I think we’re defining “type” differently. You can change the type
attribute, using the hash accessors, but the object’s class is still
the same one it was instantiated with - when you save the object you
may have bypassed validations and callbacks and so forth that are
defined on the class you’re trying to change to.

Reload won’t work not because it’s looking for the old type, but
because you can’t change the value of “self” - and once you do
reinstantiate from the database, your object is potentially invalid
because it was saved as an instance of a different AR derived class
than the ‘type’ column reflects.

  • Gabriel

I finally did it the way you described in your first post.

page = Page.find(:first)
summary_page = SummaryPage.new(page.attributes)

The problem I got here was, that after saving summary_page I had a new
record in the database. So I extended ActiveRecord::Base with a
new_record= method and now I can set this flag manually. Maybe it’s
not the best way to do this, but for now it works.

After working for a month with Rails now I am still impressed by its
features, but I more and more find restrictions to deal with. Well…
I think that’s the price to pay for beeing “conventional”.

Thanks for your help anyway!

On May 31, 9:30 pm, “Gabriel Gironda” [email protected]

The issues you encounter are by design. Ruby will not let you change
the class of an object underneath it (unless using evil.rb:
http://rubyforge.org/projects/evil). Changing the type attribute of an
instantiated ActiveRecord object is also generally bad practice.
Consider this scenario:

class Page < ActiveRecord::Base
validates_presence_of :title
end

class SummaryPage < Page
validates_presence_of :summary
end

page = Page.find(:first)
page.summary = nil
page[:type] = ‘SummaryPage’
page.save!

This object will pass all validations and save just fine, even though
the intent was to change the type to ‘SummaryPage’, which requires a
‘summary’ attribute that is nil in this case. Alternative solutions
could be:

page = Page.find(:first)
summary_page = SummaryPage.new(page.attributes)

You could also write a method that looks up the right class based on a
:type attribute and instantiates a new object based on that, and so
forth. In ActiveRecord STI, your ‘default’ class should be the one
that maps to the table itself, not the one defined by the default
value of the ‘type’ column.

  • Gabriel

Gabriel Gironda wrote:

I think we’re defining “type” differently. You can change the type
attribute, using the hash accessors, but the object’s class is still
the same one it was instantiated with - when you save the object you
may have bypassed validations and callbacks and so forth that are
defined on the class you’re trying to change to.

Yes, we’re both saying the same thing here.

Gabriel Gironda wrote:

the intent was to change the type to ‘SummaryPage’, which requires a
value of the ‘type’ column.

transform a Sysfolder into another subclass of page (why?).
Does anybody found a work around for this problem? I don’t need to use

http://www.autopendium.co.uk
Stuff about old cars

Mike wrote:

I finally did it the way you described in your first post.

page = Page.find(:first)
summary_page = SummaryPage.new(page.attributes)

Be aware that protected attributes won’t be copied.

So if you are using attr_protected or attr_accessible,
you’ll have to write a variant of AR’s clone method
that is able to instantiate an object of a different class.


We develop, watch us RoR, in numbers too big to ignore.