Absolutely stuck trying to create nested association in rails form with has_many through
Instead of calling:
@production = @miniature.productions.create
Try Rails' "build" method:
def new
@miniature = Miniature.new(miniature_params)
@miniature.productions.build
end
def create
@miniature = Miniature.new(miniature_params)
if @miniature.save
redirect_to @miniature
else
render 'new'
end
end
Using the build method uses ActiveRecord's Autosave Association functionality.
See http://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html
You also need to update your params method, e.g.
def miniature_params
params.require(:miniature).permit(:name, :release_date, :material, :scale, productions_attributes: [:manufacturer_id])
end
Also your fields_for should be plural (I think)...
<%= f.fields_for :productions do |production_fields| %>
Comments
-
Ossie almost 2 years
I posted an earlier question about this and was advised to read lots of relevant info. I have read it and tried implementing about 30 different solutions. None of which have worked for me.
Here's what I've got.
I have a Miniatures model. I have a Manufacturers model. Miniatures have many manufacturers THROUGH a Productions model.
The associations seem to be set up correctly as I can show them in my views and create them via the console. Where I have a problem is in letting the Miniatures NEW and EDIT views create and update to the Productions table.
In the console the command
@miniature.productions.create(manufacturer_id: 1)
works, which leads me to believe I should be able to do the same in a form.I THINK my problem is always in the Miniatures Controller and specifically the CREATE function. I have tried out a ton of other peoples solutions there and none have done the trick. It is also possible that my field_for stuff in my form is wrong but that seems less fiddly.
I've been stuck on this for days and while there are other things I could work on, if this association isn't possible then I'd need to rethink my entire application.
The form now creates a line in the Productions table but doesn't include the all important manufacturer_id.
Any help VERY much appreciated.
My New Miniature form
<% provide(:title, 'Add miniature') %> <h1>Add a miniature</h1> <div class="row"> <div class="span6 offset3"> <%= form_for(@miniature) do |f| %> <%= render 'shared/error_messages', object: f.object %> <%= f.label :name %> <%= f.text_field :name %> <%= f.fields_for :production do |production_fields| %> <%= production_fields.label :manufacturer_id, "Manufacturer" %> <%= production_fields.select :manufacturer_id, options_from_collection_for_select(Manufacturer.all, :id, :name) %> <% end %> <%= f.label :release_date %> <%= f.date_select :release_date, :start_year => Date.current.year, :end_year => 1970, :include_blank => true %> <%= f.submit "Add miniature", class: "btn btn-large btn-primary" %> <% end %> </div> </div>
Miniatures controller
class MiniaturesController < ApplicationController before_action :signed_in_user, only: [:new, :create, :edit, :update] before_action :admin_user, only: :destroy def productions @production = @miniature.productions end def show @miniature = Miniature.find(params[:id]) end def new @miniature = Miniature.new end def edit @miniature = Miniature.find(params[:id]) end def update @miniature = Miniature.find(params[:id]) if @miniature.update_attributes(miniature_params) flash[:success] = "Miniature updated" redirect_to @miniature else render 'edit' end end def index @miniatures = Miniature.paginate(page: params[:page]) end def create @miniature = Miniature.new(miniature_params) if @miniature.save @production = @miniature.productions.create redirect_to @miniature else render 'new' end end def destroy Miniature.find(params[:id]).destroy flash[:success] = "Miniature destroyed." redirect_to miniatures_url end private def miniature_params params.require(:miniature).permit(:name, :release_date, :material, :scale, :production, :production_attributes) end def admin_user redirect_to(root_url) unless current_user.admin? end def signed_in_user unless signed_in? store_location redirect_to signin_url, notice: "Please sign in." end end end
Miniature model
class Miniature < ActiveRecord::Base has_many :productions, dependent: :destroy has_many :manufacturers, :through => :productions accepts_nested_attributes_for :productions validates :name, presence: true, length: { maximum: 50 } validates :material, presence: true validates :scale, presence: true validates_date :release_date, :allow_blank => true def name=(s) super s.titleize end end
Production model
class Production < ActiveRecord::Base belongs_to :miniature belongs_to :manufacturer end
Manufacturer model
class Manufacturer < ActiveRecord::Base has_many :productions has_many :miniatures, :through => :productions validates :name, presence: true, length: { maximum: 50 } accepts_nested_attributes_for :productions end
-
Ossie over 10 yearsYeah that allows me to shift the line up above the "if @miniature save" line but it works just the same. I get a new line in the table but it doesn't contain the manufacturer_id. It must either be that I need to compose my form differently or I need to force the controller to use that particular parameter. Oof.
-
Ossie over 10 yearsI'd tried various combinations in the params bit. This looks great but still doesn't pass the manufacturer_id to productions. It is very possible right?
-
Helios de Guerra over 10 yearsCan you tell from your log if its passing the parameter, but it is being filtered out? Or is it not passing it at all? Sorry I haven't been too much help, I try not to use nested forms in favor of using form objects... Check out pattern #3 on Form Objects, here: blog.codeclimate.com/blog/2012/10/17/…
-
Ossie over 10 yearsInteresting. The log has manufacturer_id under Paramters but before 'begin transaction' it gives 'Unpermitted parameters: production'. So then only reads 'INSERT INTO "productions" ("created_at", "miniature_id", "updated_at") VALUES (?, ?, ?) [["created_at", Mon, 07 Oct 2013 17:44:55 UTC +00:00], ["miniature_id", 89], ["updated_at", Mon, 07 Oct 2013 17:44:55 UTC +00:00]]'. So it isn't passing it. I just can't understand why it plays nice in console but not in forms. Had no idea there was alternative to nested forms. Will investigate. This ought to be workable though right?
-
Ossie over 10 yearsAfter some toggling of production/productions in the form I can report that in plural it doesn't see the manufacturer_id param at all and thusly doesn't throw up the 'Unpermitted parameters: production' error. Probably not progress? Found this [link] (karolgalanciak.com/blog/2013/10/06/…) also reccomending Form Objects but bit daunted. No step by step guides. Thanks so much by the way.
-
Helios de Guerra over 10 yearsWhat do your request params look like in your log w/ the fields_for productions being plural?
-
Helios de Guerra over 10 yearsAlso, try
production_attributes
in the miniature params permit method instead of justproductions
... see latest edit -
Ossie over 10 yearsI switched to production_attributes: and that works just the same. If I try productions plural in the fields_for, my dropdown doesn't appear at all so can't check the log. So I'm back to having 'Unpermitted parameters: production' before 'begin transaction' in the log. Head against wall.
-
Helios de Guerra over 10 yearsI think you definitely want
productions
plural in the fields_for. I think its some small issue that we're missing... Maybe the select isn't being properly formed or something. You might try replacing the select :manufacturer_id with a number_field and just type in the id and see what you get. Also, have you reviewed the Rails guide, guides.rubyonrails.org/form_helpers.html#building-complex-forms. If all else fails, maybe post a Gist of your current controller and views and I'll work thru it. We're making this harder than it should be somewhere along the line. -
Ossie over 10 yearsWell productions plural loses my select. I'll have another look through the Rails Guide. The current branch is here github.com/WorldOfProper/mini_legions/tree/manufacturers but without that plural right now.
-
Helios de Guerra over 10 yearsAlright, I got it working for me... The reason you were losing your select, was that no production was initialized for a new Miniature instance...
@miniature = Miniature.new
needed to be followed by,@miniature.productions.build
... See latest edit -
Ossie over 10 yearsOHMIGOD that worked! You're a genius. The addition of the new productions meant that the preductions plural DID show the select and it worked. It works! This means I can go on with my whole project without starting again. You are my hero Helios de Guerra.
-
Helios de Guerra over 10 years:-) Glad to help. Those little things can be infuriating... Good luck w/ the rest of the project.