I am trying to create a model in a controller using strong params in Rails 5.1 (some things changed from previous for strong_params). However, when I inspect the params, the merged ones are NOT present and I am getting an ForbiddenAttributesError tracing back to the Model.new line below. The only thing in the Model is verify presence for all the attributes.
class ModelController < ApplicationController
before_action :application_controller_action
def create
@model = Model.new(strong_params)
if @model.valid?
result = @model.save
else
render html: 'MODEL NOT VALID'
end
render html: 'DONE'
end
private
def strong_params
# attr_1 and attr_2 are set in the application controller and are available here.
params.require(:model).permit(:name, :attribute_1, :attribute_2).merge(attribute_1: @attr_1, attribute_2: @attr_2)
# Inserting the following two lines causes a ForbiddenAttributesError
puts params.inspect # DOES NOT INCLUDE @attr_1 and/or @attr_2
return params
end
I may be doing something wrong though because I've even tried putting the strong params into a model with the attributes (which I can inspect just before) and it still fails because the validation for attr_1 and attr_2 fail in the Model.
def create
puts @user.inspect (not nil)
@model = Model.new(name: strong_params[:name], attribute_1: @attr_1, attribute_2: @attr_2)
UPDATE:
OK, I'm getting some weird errors from my troubleshooting. It seems the merge is not working correctly, though I'm sure it was at one point.
The first thing I checked was @attr_1 and @attr_2, they are definitely getting set.
For troubleshooting purposes, I've reduced the application before_action to this:
def application_before_action
@attr_1 = Model.first
@attr_2 = Model.last
With the code above, inspecting the params object and then returning it after the require().permit(), I am getting a ForbiddenAttributesError (no indication of what). If I remove those lines, I get a missing attributes error from the model indicating that @attr_1 and @attr_2 are missing.
UPDATE 2
Changed the title of the question, because I probably got confused during troubleshooting. I think the issue is just that the merge is assigning nil... but strangely so is the manual assignment suggested by (myself originally) and another answer here. The attributes keys are there, but they're getting assigned nil. Also, noticed my example was using a single Model, when there are actually two Models, Model1 and Model2. I am assigning the values from Model1 to Model2.
Here is a better demonstration of the error:
def create
puts '0:'
puts @model1.inspect
puts '1:'
puts strong_params.inspect
@model2 = Model2.new(strong_params) do |m|
m.user_id = @attr_1
m.account_number = @attr_2
end
puts '3:'
puts @model2.inspect
if @model2.valid?
result = @model2.save
render html: 'SUCCESS' and return
else
render html: @model2.errors.full_messages and return
end
end
Outputs in console:
0:
#<Model1 id: 29, attribute_1: 'test_value_1', attribute_2: 'test_value_2', created_at: "2018-08-15 03:55:08", updated_at: "2018-08-15 04:05:01">
1:
<ActionController::Parameters {"name"=>"test_name", "attribute_1"=>nil, "attribute_2"=>nil} permitted: true>
3:
#<Model2 id: nil, name: 'test_name', attribute_1: nil, attribute_2: nil, created_at: nil, updated_at: nil>
Obviously the nil id and timestamps are because the model has not been saved yet.
The html model2.errors.full_messages are: ["attribute_1 can't be blank", "attribute_2 can't be blank"]
SOLUTION
Coming from a pure ruby environment previously, I was mistaken about ActiveRecord default accessors for models. Removing the accessors seems to have resolved the problem.
Instead of mucking about with the params hash you can just assign the odd values one by one:
class ModelController < ApplicationController
before_action :application_controller_action
def create
@model = Model.new(strong_params) do |m|
m.attribute_1 = @attr_1
m.attribute_2 = @attr_2
end
if @model.valid?
result = @model.save
else
render html: 'MODEL NOT VALID'
end
# don't do this it will just give a double render error
render html: 'DONE'
end
private
private
def strong_params
params.require(:model).permit(:name, :attribute_1, :attribute_2)
end
end
In general this is a much more readable way to merge params with values from the session for example.
The reason your strong parameters method does not work is its just plain broken in every possible way. The main point is that you're not returning the whitelisted and merged params hash. You're returning the whole shebang.
You also seem under the faulty impression that .require
, .permit
and .merge
alter the orginal hash - they don't - they return a new hash (well actually an ActionContoller::Parameters instance to be specific).
def strong_params
# attr_1 and attr_2 are set in the application controller and are available here.
permitted = params.require(:model).permit(:name, :attribute_1, :attribute_2)
.merge(attribute_1: @attr_1, attribute_2: @attr_2)
puts permitted.inspect
permitted # return is implicit
end
Or just:
def strong_params
# attr_1 and attr_2 are set in the application controller and are available here.
params.require(:model).permit(:name, :attribute_1, :attribute_2)
.merge(attribute_1: @attr_1, attribute_2: @attr_2)
end