DanSingerman
DanSingerman

Reputation: 36502

Rails Single Table Inheritance - What is the best way to explicitly set type?

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

Answers (7)

cisco
cisco

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

Adam
Adam

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

AndyV
AndyV

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

fringd
fringd

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

kt103099
kt103099

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

Bill
Bill

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

Codebeef
Codebeef

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

Related Questions