Search code examples
ruby-on-rails-3nested-formssti

Rails 3 STI model inside nested form


I am trying to build a CRUD controller and form in Rails 3.

I have

class Publication < ActiveRecord::Base

  has_many :posts

end

where Posts is a STI model :

class Post < ActiveRecord::Based
  attr_accessible :title, :description
end

and I have a few inherited models:

class Image < Post
end

class Video < Post
end

class Status < Post
end

etc.

I want to create a CRUD for Publication, where the user is able to add as many Posts as they want, dynamically adding nested form for any type of Post.

Is there a gem I could use that supports such nested forms with STI ?

I tried to build a form, but I need to amend the Publication class and introduce nested attributes for each additional inherited model. Is there a way to avoid doing this?

class Publication < ActiveRecord::Base

  has_many :videos, :dependent => :destroy
  accepts_nested_attributes_for :videos, allow_destroy: true
  attr_accessible :videos_attributes

  has_many :posts

end

Solution

  • I wrote a short blog post about this problem at: http://www.powpark.com/blog/programming/2014/05/07/rails_nested_forms_for_single_table_inheritance_associations

    I basically decided to use the cocoon gem, which provides two helper methods - link_to_add_association and link_to_remove_association, which dynamically add the respective decorated class fields to the form (such as Post, Image or Video

    # _form.html.haml
    
    = simple_form_for @publication, :html => { :multipart => true } do |f|
    
      = f.simple_fields_for :items do |item|
        = render 'item_fields', :f => item
      = link_to_add_association 'Add a Post', f, :items, :wrap_object => Proc.new { |item| item = Item.new }
      = link_to_add_association 'Add an Image', f, :items, :wrap_object => Proc.new { |item| item = Image.new }
      = link_to_add_association 'Add a Video', f, :items, :wrap_object => Proc.new { |item| item = Video.new }
    
      = f.button :submit, :disable_with => 'Please wait ...', :class => "btn btn-primary", :value => 'Save'
    

    The :wrap_object proc generates the correct object, to be rendered inside _item_fields partial, like:

    # _item_fields.html.haml
    
    - if f.object.type == 'Video'
      = render 'video_fields', :f => f
    - elsif f.object.type == 'Image'
      = render 'image_fields', :f => f
    - elsif f.object.type == 'Post'
      = render 'post_fields', :f => f