Reputation: 36502
I am using single table inheritance in my rails application, and want to explicitly set the type of an instance.
I have the following;
class Event < ActiveRecord::Base
class SpecialEvent < Event
which is implemented through single table inheritance.
SpecialEvent.new
works as expected, but I want to be able to do things like
Event.new(:type => 'SpecialEvent')
So I can create different sub_types easily in the application.
However this doesn't work and seems to set :type
to nil
, not the value I set it to; I suspect this is because by calling Event.new
it is overwriting the :type
argument.
Has anyone got a good way of doing this?
Upvotes: 10
Views: 10561
Reputation: 141
from "Pragmatic - Agile Web Development with rails 3rd edition", page 380
There’s also a less obvious constraint (with STI). The attribute type is also the name of a built-in Ruby method, so accessing it directly to set or change the type of a row may result in strange Ruby messages. Instead, access it implicitly by creating objects of the appropriate class, or access it via the model object’s indexing interface, using something such as this:
person[:type] = 'Manager'
man, this book really rocks
Upvotes: 14
Reputation: 101
You can use the Rails safe_constantize method, which will ensure the object/class actually exists.
For example:
def typeify(string)
string.classify.safe_constantize
end
new_special_event = typeify('special_event').new
Upvotes: 0
Reputation: 3741
Up front I'll agree that STI is often NOT the best way to deal with things. Polymorphism, yes, but it's often better to use a polymorphic association than STI.
That said, I had a system in which STI was important. It was a judicial system and things like court cases were remarkably similar across their types and generally shared all their essential attributes. However, a civil case and a criminal case differed in the elements they managed. This happened at several levels in the system so abstracted my solution.
https://github.com/arvanasse/sti_factory
Long story short, it uses a factory method to leverage the common approach described above. As a result, the controller can remain neutral/ignorant of the particular type of STI class that you're creating.
Upvotes: 0
Reputation: 2528
To me it sounds like you'll need some mojo in the event#create
action:
type = params[:event].delete(:type)
# check that it is an expected value!!!
die unless ['Event', 'SpecialEvent'].include(type)
type.constantize.new(params[:event])
Upvotes: 2
Reputation: 21
Apparently, Rails does not allow you to set Type directly. Here's what I do...
klass_name = 'Foo'
...
klass = Class.const_get(klass_name)
klass.new # Foo.new
I believe .constantize is a Rails inflector shortcut. const_get is a Ruby method on Class and Module.
Upvotes: 0
Reputation: 1563
No, I want to create instances of sub-types, where I want to programmatically determine which sub_type they are – HermanD
You could use a factory pattern, although I have heard recently that people frown on the overuse of this pattern. Basically, use the factory to create the actual types you want to get
class EventFactory
def EventFactory.create_event(event_type)
event_type.constantize.new()
end
end
Upvotes: 5
Reputation: 43996
If you're trying to dynamically instantiate a subtype, and you have the type as a string, you can do this:
'SpecialEvent'.constantize.new()
Upvotes: 22