Forum: Ruby on Rails STI and change the type

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.
B2721cc23cfb835d957b886c7885abac?d=identicon&s=25 Mike (Guest)
on 2007-05-29 20:42
(Received via mailing list)
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
58c6efb8466b9f85155fe6aa9fc37fce?d=identicon&s=25 Chris Taggart (christ)
on 2007-05-31 12:44
(Received via mailing list)
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
B2721cc23cfb835d957b886c7885abac?d=identicon&s=25 Mike (Guest)
on 2007-05-31 18:28
(Received via mailing list)
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
Fc49ff6d82732b73a94be2974b4dd3df?d=identicon&s=25 Gabriel Gironda (Guest)
on 2007-05-31 18:59
(Received via mailing list)
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
58c6efb8466b9f85155fe6aa9fc37fce?d=identicon&s=25 Chris Taggart (christ)
on 2007-05-31 21:19
(Received via mailing list)
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
Fc49ff6d82732b73a94be2974b4dd3df?d=identicon&s=25 Gabriel Gironda (Guest)
on 2007-05-31 21:31
(Received via mailing list)
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
B2721cc23cfb835d957b886c7885abac?d=identicon&s=25 Mike (Guest)
on 2007-05-31 22:09
(Received via mailing list)
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" <gabriel.giro...@gmail.com>
58c6efb8466b9f85155fe6aa9fc37fce?d=identicon&s=25 Chris Taggart (christ)
on 2007-06-01 00:01
(Received via mailing list)
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
C64e63b70be7dfed8b0742540b8b27e5?d=identicon&s=25 Mark Reginald James (Guest)
on 2007-06-01 08:07
(Received via mailing list)
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.
This topic is locked and can not be replied to.