0

So I discovered a weird issue while trying to use mass assignment on a model that also had an attr_accessor for an attribute that was being updated through a mass assigned strong params hash. I was curious as to why this happens - and is it supposed to happen?

Here is my controller methods for update and the strong params -

  def update
    @device = Device.find(params[:id])
    if @device.update(device_params)
      redirect_to device_path(@device)
    else
      render :edit
    end
  end

  private

  def device_params
    params.require(:device).permit(:department_id, :entity_id, :description, :device_model_id)
  end

And when I do this in the corresponding device model being updated, it will not throw any errors, but the fields - department, and entity will remain unchanged after the update method.

class Device < ActiveRecord::Base
  attr_accessor :device_event, :sensor_event, :department_id

  delegate :name, to: :department, prefix: true, allow_nil: true
  delegate :name, to: :entity, prefix: true, allow_nil: true
  delegate :id, to: :department, prefix: true, allow_nil: true
  delegate :id, to: :entity, prefix: true, allow_nil: true
  delegate :firmware, to: :device_configuration, prefix: true, allow_nil: true
  delegate :sleeptime, to: :device_configuration, prefix: true, allow_nil: true

  has_many :sensors
  has_many :events
  has_many :sensor_data, through: :events
  has_many :device_data, through: :events
  belongs_to :device_type
  belongs_to :entity
  belongs_to :department
  has_one :device_configuration

  paginates_per 10

  def self.filter(params_hash)
    filter_params = ActiveSupport::HashWithIndifferentAccess.new(params_hash)
    devices = Device.where(filter_params)
  end

  def recent_sensor_event
    self.events.where(event_type_id: 1).last
  end

  def recent_device_event
    self.events.where(event_type_id: 0).last
  end
end

Now when I remove the attr_accessor on department_id, the mass_assigned strong params hash will save properly when @device.update is called in the controller and all is well. It took me a while to figure out that the attr_accessors were what was tripping up the mass assignment.

Riggeot
  • 89
  • 2
  • 8
  • 1
    What are the fields of `Device` (the database fields), also, did you try using `update_attributes` instead? By checking [this answer](http://stackoverflow.com/a/27684300/312907) I feel like it might cause issues, using `update`. Also from my understanding, `update` is [deprecated](http://apidock.com/rails/ActiveRecord/Base/update/class) – Francesco Belladonna Jul 10 '15 at 21:04
  • I originally was using update_attributes, but then I found per [this place on the same docs](http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-update_attributes) that update_attributes is an alias for update. But per your mentioned SO answer, update_attributes is what I really want. Thanks! – Riggeot Jul 10 '15 at 21:57
  • You are probably confusing attr_accessor and attr_accessible: http://stackoverflow.com/questions/3136420/difference-between-attr-accessor-and-attr-accessible – Pierre Pretorius Jul 11 '15 at 12:44

1 Answers1

1

Your device model already has an implicit department_id by virtue of this line:

belongs_to :department

By additionally declaring attr_accessor :department_id you are overwriting this implicit attribute (and its ActiveRecord persistence magic) with a concrete getter and setter based on a @department_id instance variable (which has no persistence magic at all). That's probably not what you had in mind.

So when you perform the mass assignment, the @department_id value will get changed, but the underlying belongs_to association will not. Hence your observation that the department association is not updated in the database.

To summarize: you don't need attr_accessor :department_id because ActiveRecord generates something similar to it automatically when you declare belongs_to :department.

Matt Brictson
  • 10,904
  • 1
  • 38
  • 43
  • This makes sense. I found that with the accessors in place I could individually do `@device.entity = Entity.find(device_params["entity_id"])` and so on with each value and then use `if @device.save do yada yada`. That did work, and I'm guessing because using the direct association with the object goes straight to the ActiveRecord associations and doesn't use the getter/setter generated from attr_accessor. Thanks! – Riggeot Jul 10 '15 at 21:44