Andrew
Andrew

Reputation: 43113

Rails polymorphic / single-table-inheritance models, select type in form

Update: After thinking about this some more I think this question includes both polymorphism and STI.

Let's say I have the following:

class Vehicle < ActiveRecord::Base
  belongs_to :dealership
  ...
end

class Car < Vehicle
  ...
end

class Truck < Vehicle
  ...
end

The Car and Truck models have the same attributes but different logic in the model, so they all just use the Vehicles table.

I'd like to make a generic form that allows someone to create a new vehicle record, and to select whether it is a "Car" or "Truck". On the backend this form should then create a "Car" or "Truck" record, not a generic "Vehicle" record.

Question: How do you present this option in a Rails form? Specifically, are there methods for listing the child types of an abstract base model, and/or is there a simple way to present these as (for instance) a Select element in a form?

The reason I am looking to create a form that can make a car or a truck opposed to just having two different forms for cars and trucks, is eventually I'd also like these models to be nested in a parent, "Dealership." I'd like to be able to not only create new vehicles and select "car" or "truck" as an option, but to be able to have nested forms where a user could load a "Dealership" and view/edit all the cars and trucks that belong to that dealership in a single form. I know how to set that up using accepts_nested_attributes_for on a simple association like Vehicle belongs_to :dealership, but I'm not sure how to set it up for child models that inherit from Vehicle.

Upvotes: 1

Views: 1299

Answers (3)

Anton Evangelatov
Anton Evangelatov

Reputation: 1417

I've had the same problem. Basically you are trying to build a form that accepts has_many association of a base class, but you want to create individual forms (add nested fields) for any of the Decorated / Concrete classes dynamically.

I solved this using the cocoon gem, which provided methods that dynamically add the correct fields to the form. The _form partial looks something like:

# _form.html.haml

= simple_form_for @dealership, :html => { :multipart => true } do |f|

  = f.simple_fields_for :vehicles do |vehicle|
    = render 'vehicle_fields', :f => vehicle
    = link_to_add_association 'Add a Car', f, :vehicles, :wrap_object => Proc.new { |vehicle| vehicle = Car.new }
    = link_to_add_association 'Add a Truck', f, :vehicles, :wrap_object => Proc.new { |vehicle| vehicle = Truck.new }

= f.button :submit, :disable_with => 'Please wait ...', :class => "btn btn-primary", :value => 'Save'

And then the _vehicle_fields

# _vehicle_fields.html.haml

- if f.object.type == 'Car'
  = render 'car_fields', :f => f
- elsif f.object.type == 'Truck'
  = render 'truck_fields', :f => f

car_fields and truck_fields would also feature a link link_to_remove_association provided by cocoon

Take a look at :wrap_object documentation at https://github.com/nathanvda/cocoon , or read my blog post at http://www.powpark.com/blog/programming/2014/05/07/rails_nested_forms_for_single_table_inheritance_associations

Upvotes: 1

Gerry
Gerry

Reputation: 5336

I don't think that a simple select element on the form is the solution to your problem. What you are trying to achieve here is called Multiple Table Inheritance and your problem should be the way you must structure your models in order be easily reflected on the database. Because it is a long discussion I will point you to this article where I explain the optimal way of doing this. I hope I helped you in a way :)

PS: To answer your question, no there is no built-in mechanism of doing that kind of thing in the Rails framework.

Upvotes: 0

gabrielhilal
gabrielhilal

Reputation: 10769

I might be wrong, but I understand polymorphic as the other way around. I mean, a model can belong to more than one other model...

Car and Truck is a kind of Vehicle. So, lets say that Vehicle has year and mark attributes.

You are able to create a new Car or Truck, and having the polymorphic defined, you will create your form as usual.

@car = Car.find(id)
@vehicle = @car.vehicles.build(year: "1999", mark: "Beetle") 
@vehicle.save

I hope it helps...

Upvotes: 0

Related Questions