ruby-on-railsstrong-parametersruby-on-rails-5.1rails-models

Rails merge (and manual assignment) assigning nil


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.


Solution

  • 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