views:

47

answers:

3

I have a model with single-table inheritance on the type column:

class Pet < ActiveRecord::Base
  TYPES = [Dog, Cat, Hamster]
  validates_presence_of :name
end

I want to offer a <select> dropdown on the new and edit pages:

<% form_for @model do |f| %>
  <%= f.label :name %>
  <%= f.text_input :name %>

  <%= f.label :type %>
  <%= f.select :type, Pet::TYPES.map { |t| [t.human_name, t.to_s] } %>
<% end %>

That gives me the following error:

ActionView::TemplateError (wrong argument type String (expected Module))

I read a suggestion to use an alias for the field #type since Ruby considers that a reserved word that's the same as #class. I tried both

class Pet < ActiveRecord::Base
  ...
  alias_attribute :klass, :type
end

and

class Pet < ActiveRecord::Base
  ...
  def klass
    self.type
  end
  def klass=(k)
    self.type = k
  end
end

Neither worked. Any suggestions? Oddly, it works fine on my machine (MRI 1.8.6 on RVM), but fails on the staging server (MRI 1.8.7 not on RVM).

A: 

You could also try to change the column name used with STI for something different from type.

According to railsapi.com, it can be set in subclasses, so just add the following in your pet model:

self.inheritance_column = "type_id"

I'm just guessing... so I'm sorry this is totally wrong.

j.
+1  A: 

The key difference between ryan bates' "suggestion" (who knows more about rails than most) and your implementation is the suggestion used the direct access to the attributes via the brackets ("self[:type] = ") versus your implement that uses method calling ("self.type = ")

So try something like:

class Pet < ActiveRecord::Base
  ...
  def klass
    self[:type]
  end
  def klass=(k)
    self[:type] = k
  end
end
Ben Sharpe
Also, I would suggest using the same MRI as your production server to reduce issues like this.
Ben Sharpe
I concur with the use-the-same-MRI suggestion; I hadn't even noticed until I came across this issue that they differed.
James A. Rosen
A: 

If you absolutely have to do this, I'd do something along the lines of this in your controller:

@pet = Pet.new(params[:pet])
@pet[:type] = params[:pet][:type]

IMO, you're better off being both explicit and painful when trying to do this, as changing types on the fly like that feels like a really bad idea.

See my comment above about this being a *rendering* problem rather than an *updating* problem.
James A. Rosen
Ah, I understand now. So, I can pretty much guarantee it's not an issue with calling <%= f.label :type %> <%= f.select :type %>I've done that before in a handful of apps out of necessity and it doesn't seem to be an issue. If you replace the Pet::TYPES.map stuff with an array of strings, is an exception still raised?
Yeah, the problem is still there when using an `Array` of `String`s. I think the problem has more to do with `select` doing `form_object.send(method)`- in this case `@pet.send(:type)` - to get the currently-selected one, and then comparing it to the `String` in my `Array`.
James A. Rosen