Search code examples
ruby-on-railshas-manybelongs-to

Creating both has_many belongs_to associations child and parents model at same view


So I have two models:

#app/models/diy.rb
class Diy < Activerecord::Base
   #schema id | summary | created_at | updated_at 
   has_many :steps
end

#app/models/step.rb
class Step < ActiveRecord::Base
   # schema id | step_content | photo | created_at | updated_at
   belongs_to :diy
end

Is there any way to create a diy database row and associated with it step database rows in the same view?

Closest I've got is:

 <%= form_for(@diy) do |f| %>
   <%= f.label :summary  %><br>
   <%= f.text_field :summary %><br>
   <%= f.label :steps  %><br>
   <%= f.text_field :steps %><br>
   <%= f.submit %>
 <% end %>

but with this code I'm not accessing any columns in step table.

If it helps to solve the problem, with this code i get "Steps" text field which is already filled with "Step::ActiveRecord_Associations_CollectionProxy:0x9613ce0".


Solution

  • class Diy < ActiveRecord::Base
      has_many :steps
      accepts_nested_attributes_for :steps
    end
    
    class Step < ActiveRecord::Base
      belongs_to :diy
    end
    

    accepts_nested_attributes_for lets Diy take attributes for Steps:

    Diy.create( steps_attributes: [{ step_content: 'Stir it.' }] )
    

    To create the form inputs use fields_for:

    <%= form_for(@diy) do |f| %>
       <%= f.label :summary  %><br>
       <%= f.text_field :summary %><br>
       <%- # wrapping it in a fieldset element is optional -%>
       <fieldset>
         <legend>Steps</legend>
         <% f.fields_for(:steps) do |step_fields| %>
            <%= step_fields.label :step_content  %><br>
            <%= step_fields.text_field :step_content %><br>
         <% end %>
       </fieldset>
       <%= f.submit %>
     <% end %>
    

    What this does it iterate though @diy.steps and creates a <textarea name="diy[steps_attributes][][step_content]"> for each. step_fields is a form builder which is scoped to the particular nested record.

    Note that if @diy.steps is nil like on a new record then there will be no form inputs. To solve that you need to seed the record:

    class DiysController
    
      # ...
      def new
        @diy = Diy.new
        @diy.steps.new # creates a new step that the user can fill in.
      end
    
      def edit
        @diy = Diy.find(params[:id])
        @diy.steps.new # creates a new step that the user can fill in.
      end
    end
    

    To avoid getting a bunch of junk steps you would use the reject_if option:

    class Diy < ActiveRecord::Base
      has_many :steps
      accepts_nested_attributes_for :steps, reject_if: :all_blank
    end
    

    To whitelist the nested attributes in your controller use a array containing the allowed attributes:

    def diy_params
      params.require(:diy).permit(:summary, steps_attributes: [:step_content])
    end
    

    Please read: