简体   繁体   中英

Rails 5 HABTM Unpermitted parameters

I'm throwing in the towel after a day of getting nowhere (and the answer will likely be annoyingly simple, but that's code).

Ok, I have 2 models, User (with Devise) and Chapter.

Models:

class User < ApplicationRecord
  has_many :projects
  has_and_belongs_to_many :chapters
end

class Chapter < ApplicationRecord
  belongs_to :country
  has_and_belongs_to_many :users
  accepts_nested_attributes_for :users
end

_form.html.erb

[...]
<% @chapter.users.each_with_index do |chap_user, i| %>
  <div class="row">
    <div class="col-md-4 col-md-offset-4  col-xs-8 col-xs-offset-2">
      <small>User <%= i + 1 %></small>
    </div>
  </div>

  <div class="row">
    <div class="col-md-2 col-md-offset-4  col-xs-8 col-xs-offset-2">

      <div class="field form-group">
        <%= form.label 'First Name' %>
        <%= form.text_field :first_name, value: chap_user.first_name,class:"form-control"%>
      </div>
    </div>
    <div class="col-md-2 col-md-offset-0 col-xs-8 col-xs-offset-2">
      <div class="field form-group">
        <%= form.label 'Last Name' %>
        <%= form.text_field :last_name, value: chap_user.last_name, class:"form-control"%>
      </div>

    </div>
  </div>
  <div class="row">
    <div class="col-md-4 col-md-offset-4 col-xs-8 col-xs-offset-2">
      <div class="field form-group">
        <%= form.label :email %>
        <%= form.text_field :email, value: chap_user.email, class:"form-control"%>
      </div>

    </div>
  </div>
<% end %>
[...]

ChaptersController (params)

private # Use callbacks to share common setup or constraints between actions. def set_chapter @chapter = Chapter.find(params[:id]) end

# Never trust parameters from the scary internet, only allow the white list through.
def chapter_params
  params.fetch(:chapter, {})
  params.require(:chapter).permit(:city, :country_id, user_attributes: [:first_name,:last_name,:email])
end
# or...
params.require(:chapter).permit(:city, :country_id, user_ids: [])
# or...
params.require(:chapter).permit(:city, :country_id, user_attributes: [:id, :first_name])
# or...
params.require(:chapter).permit(:city, :country_id, users_attributes: [:id, :first_name])

binding.pry in controller

  # PATCH/PUT /chapters/1
  # PATCH/PUT /chapters/1.json
  def update
    binding.pry
    respond_to do |format|
      if @chapter.update(chapter_params)
        format.html { redirect_to @chapter, notice: 'Chapter was successfully updated.' }
        format.json { render :show, status: :ok, location: @chapter }
      else
        format.html { render :edit }
        format.json { render json: @chapter.errors, status: :unprocessable_entity }
      end
    end
  end

binding.pry response

I know this one is a bit ugly but basically the params are being submitted but users[:first_name,:last_name,:email] aren't being permitted

[1] pry(#)> chapter_params Unpermitted parameters: :first_name, :last_name, :email => "London", "country_id"=>"1"} permitted: true> [2] pry(#)> params => "✓", "_method"=>"patch", "authenticity_token"=>"9IdAqcAsVG5vvakSf7jHRk+WplN/GyVZUxmAdefUbTX/uI6IajmZPr1YSol21Zhzdl7P/lrl+SVnWr5mBMiljw==", "chapter"=>"London", "country_id"=>"1", "first_name"=>"Tim2", "last_name"=>"Heard", "email"=>"jmcgregorx@spartaglobal.com"} permitted: false>, "commit"=>"Update", "controller"=>"chapters", "action"=>"update", "id"=>"1"} permitted: false>

In a nutshell, I want to connect certain users to certain chapters through a form, but I can't figure out how to get the nested params through the chapter update/create methods. My head is a red, pulpy mess. Please help.

Since the fields you are trying to modify are for a child model, you have to tell the form that because it assumes that you're editing fields on parent model by default. The way to to that is by using #fields_for (specifically the the "One-to-Many" section).

So you'll do something along these lines (explanation below code):

<% @chapter.users.each_with_index do |chap_user, i| %>
  <%= form.fields_for :users, chap_user do |user_fields| %>
    <div class="row">
      <div class="col-md-4 col-md-offset-4  col-xs-8 col-xs-offset-2">
        <small>User <%= i+ 1 %></small>
      </div>
    </div>

    <div class="row">
      <div class="col-md-2 col-md-offset-4  col-xs-8 col-xs-offset-2">
        <div class="field form-group">
          <%= user_fields.label 'First Name' %>
          <%= user_fields.text_field :first_name, value: chap_user.first_name,class:"form-control"%>
        </div>
      </div>
      <div class="col-md-2 col-md-offset-0 col-xs-8 col-xs-offset-2">
        <div class="field form-group">
          <%= user_fields.label 'Last Name' %>
          <%= user_fields.text_field :last_name, value: chap_user.last_name, class:"form-control"%>
        </div>
      </div>
    </div>

    <div class="row">
      <div class="col-md-4 col-md-offset-4 col-xs-8 col-xs-offset-2">
        <div class="field form-group">
          <%= user_fields.label :email %>
          <%= user_fields.text_field :email, value: chap_user.email, class:"form-control"%>
        </div>
      </div>
    </div>
  <% end %>
<% end %>

What changed is I wrapped all the form inputs in a block using the #fields_for method to specify that now we're modifying child attributes. When we do so, we also specify a variable name that will be used to generate fields for the child model's attributes. I named it user_fields and then inside that block replaced all references of form with user_fields .

In your ChaptersController, you want the line that accepts the params to be:

params.require(:chapter).permit(:city, :country_id, users_attributes: [:id, :first_name, :last_name, :email])

You'd want users_attributes and not user_attribute because the plural "users" tells rails to expect attributes for more than one child (more than one user). Then inside that array you want to tell it all the fields to expect, including id . Even though you're not explicitly defining a field for the ID, Rails puts one to allow you to edit attributes for children that already exist (and not only be able to create new ones). Snippet from the documentation:

Note that #fields_for will automatically generate a hidden field to store the ID of the record. There are circumstances where this hidden field is not needed and you can pass include_id: false to prevent #fields_for from rendering it automatically.

If something about this doesn't work, or if you have more questions, feel free to let me know!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM