ActiveModel::ForbiddenAttributesError using update

2020-07-14 12:11发布

I’m trying to edit/update a model record using simple_form, but the form is not going to directly change a model field. Instead, I offer a couple of check_box_tag fields that tell update what fields need changed. As a result, update is not receiving a params[:device] hash that I can use to update the attributes. I am attempting to create this hash, but am getting ForbiddenAttributesError when I issue the @device.update_attributes(params[:device]).

I believe my strong parameters list is correct. If I allow one model field (name) to be processed in the edit view, I receive the expected params[:device] hash and everything works. If I disable that field, because I don’t want it to be changed, then I need to create that hash myself and I receive the error. When I look at the hash I created, it looks to me as equivalent to the one passed by the view. I don’t understand why it is failing.

Environment is Ruby 2.0.0, Rails 4.1 on Windows 8.1 with RubyMine 6.3.

The form is: <... needs correct formatting once working ...>

<%= simple_form_for @device do |f| %>
      <legend><%= controller.action_name.capitalize %> Device:</legend>
      <%= f.input :name, disabled: true %>
      <%= check_box_tag(:is_admin, 0, @device.admin?) %>
      <%= label_tag(:is_admin, "Make admin?") %>
      <%= check_box_tag(:chg_pwd) %>
      <%= label_tag(:chg_pwd, "Change password?") %>
      <%= f.button :submit %>
<% end %>

The params[:device] that I receive when I sent f.input :name, disabled: false and allow the view to generate params[:device] is:

ActionController::Parameters (3 element(s))
"{"name"=>"D105", "password"=>"D105Dvgr", "password_confirmation"=>"D105Dvgr"}"

And, everything works.

The params[:device] that I create is:

ActionController::Parameters (3 element(s))
"{"name"=>"D106", "password"=>"D106VdAd", "password_confirmation"=>"D106VdAd"}"

And, I receive Forbidden Attribute Error, even though I see no difference between the two.

The update is: <... Code needs refactored, once it is working...>

class DevicesController < ApplicationController
  before_filter :authenticate_device!

... other methods removed here ...

  def edit
    @device = Device.find(params[:id])
    # my_page = render_to_string controller: 'devices', action: 'edit', layout: "application"
  end

  def update
    authorize! :update, @device, :message => 'Not authorized as an administrator.'
    @device = Device.find(params[:id])
    pwd_msg = ""
    if params[:chg_pwd]
      pwd_gen = @device.device + SecureRandom.urlsafe_base64(15).tr('lIO0=_\-', 'sxyzEUM').first(4)
      params[:device] = {name: @device.name} if params[:device].nil?
      params[:device][:password] = pwd_gen
      params[:device][:password_confirmation] = pwd_gen
      pwd_msg = ", new password is #{pwd_gen}"
    end
    if @device.update_attributes(params[:device])
      params[:is_admin] ? @device.add_role(:admin) : @device.remove_role(:admin)
      flash[:notice] = ["Device updated" + pwd_msg]
      redirect_to devices_path
    else
      @device.errors.messages.each do |key, value|
        flash[:alert] = ["Unable to update device"]
        @device.errors.messages.each do |key, value|
          flash[:alert] << key.to_s.capitalize + " " + value[0]
        end
      end
      redirect_to devices_path
    end
  end

  private

  def device_params
    params.require(:device).permit(:device, :name, :email, :password, :password_confirmation, :encrypted_password, :salt, :role_ids, :is_admin, :chg_pwd)  # TODO minimize when update is working
  end

end

The model is:

class Device < ActiveRecord::Base

  rolify
  devise :database_authenticatable, :rememberable, :trackable, :validatable

  validates :device,
            presence: true,
            length: {minimum: 4 },
            uniqueness: {case_sensitive: false }
  validates :name,
            presence: true

  def remember_me
    true unless self.admin?
  end

  def admin
    self.add_role :admin
  end

  def not_admin
    self.remove_role :admin
  end

  def admin?
    self.has_role? :admin
  end
  def device?
    self.has_role? :device
  end
  def vip?
    self.has_role? :vip
  end

  def login=(login)
    @login = login
  end
  def login
    @login || self.device || self.email
  end

  def self.find_first_by_auth_conditions(warden_conditions)
    conditions = warden_conditions.dup
    if login = conditions.delete(:login)  # Note one equal sign.  Strange but true.
      where(conditions).where(["lower(device) = :value OR lower(email) = :value", { :value => login.downcase }]).first
    else
      where(conditions).first
    end
  end

end

NEW INFORMATION: I neglected to provide information I have in the ApplicationController. This fix from Anton Trapp handles strong parameters for gems that aren't yet fully Rails 4 compatible:

  before_filter do
    resource = controller_name.singularize.to_sym
    method = "#{resource}_params"
    params[resource] &&= send(method) if respond_to?(method, true)
  end

I have found that using the proposed solution of:

@device.update_attributes(device_params)

does not work if a model field is updated. The result is "param not found: device". It does work if no model field is update. So, the whole issue begs the question of what is truly wrong.

2条回答
Melony?
2楼-- · 2020-07-14 12:54

The fix was to add the fields as attr_accessor to the model, but not the database, so that it could be used correctly within the form.

  attr_accessor :is_admin, :chg_pwd

And then modify the view to:

<%= simple_form_for @device do |f| %>
    <legend><%= controller.action_name.capitalize %> Device:</legend>
    <%= f.input :name, disabled: true %>
    <%= f.input :is_admin, as: :boolean, checked_value: true, unchecked_value: false %>
    <%= f.input :chg_pwd, as: :boolean, checked_value: true, unchecked_value: false %>
    <%= f.button :submit %>
<% end %>

Then, due to the Application Controller code from Anton Trapp:

  before_filter do
    resource = controller_name.singularize.to_sym
    method = "#{resource}_params"
    params[resource] &&= send(method) if respond_to?(method, true)
  end

I was able to update the fields in Device Controller as follows:

@device.update_attributes(params[:device])
查看更多
叛逆
3楼-- · 2020-07-14 12:58

In DevicesController#update action, change

@device.update_attributes(params[:device])

To

@device.update_attributes(device_params)

As you are using Rails 4.1, you need to whitelist the attributes which you would like to be inserted/updated in database. As you passed the attributes directly to update_attributes method without permitting them you received ActiveModel::ForbiddenAttributesError

UPDATE

To resolve param not found: device:

  def device_params
    if params[:device]
      params.require(:device).permit(:device, :name, :email, :password, :password_confirmation, :encrypted_password, :salt, :role_ids, :is_admin, :chg_pwd)  # TODO minimize when update is working 
    end
  end
查看更多
登录 后发表回答