rails: Updating associations with assign_attributes should not persist

assign_attributes should not persist the attributes. But it does for associations.

class Firm
  has_many :clients
  validates :clients, presence: true
end
class Client
  belongs_to :firm
end
firm = Firm.new
firm.clients.build
firm.save
firm.assign_attributes({clients: []})
saved = firm.save

saved becomes false, but it has already been persisted in the call to assign_attributes.

About this issue

  • Original URL
  • State: closed
  • Created 10 years ago
  • Reactions: 9
  • Comments: 17 (9 by maintainers)

Most upvoted comments

I’d also be very interested in a fix. I think the default behavior of Rails is incredibly dangerous and completely counter-intuitive.

Example

Consider the following scenario:

class User
  has_many :groups
  validates :name, presence: true
end

class UsersController
  def update
    User.transaction do
      @user = User.find(params[:id])

      if @user.update_attributes(params.require(:user).permit!)
        redirect_to @user
      else
        render action: :edit
      end
    end
  end
end
-# Example with simple_form to simplify example
= simple_form_for user do |f|
    = f.association :groups, as: :check_boxes

For this example, we submit the following params:

{ user: { name: '', group_ids: [1, 2, 3] } }

Expected behavior

As we send an empty name, the validation is failing. Therefore, update_attributes returns false, nothing is persisted and the user is presented with the form again, showing the validation errors.

Actual behavior

As expected, the validation fails. The very dangerous thing though is that the group associations are saved anyway, as the Rails-generated _ids-setter (and thus assign_attributes and update_attributes) automatically save the association.

Temporary workaround

I’ve written a mini-library to work around this issue. If you use it, make sure you read through it and understand it fully, as it does not handle everything properly so it’s important you know exactly what it does and what it doesn’t do.

Conclusion & plea

I’d be very happy if this could be fixed by someone with AR development experience - I think I don’t know the code base well enough to implement such a fundamental change.

Thank you folks for having a look at this 👍

Actually, it would be nice it this could be changed in rails 5. I would like the assignment of a has_one, has_many, has_and_belongs_to_many to not save automatically be default, but instead do the same as the collection.build method. This would be way more intuitive.

This hit me as well. And it’s true that it’s dangerous, since it might change data when you rely on validations that should prevent that! I have a form that shows an error upon submit due to a validation but still silently persists the changes in that very moment. The workaround by remofritzsche works well in my case.

The workaround by remofritzsche gist linked multiple times in this thread has been removed. What is the current recommended method of dealing with this possibly dangerous situation?

The Gist has been moved to https://gist.github.com/sudoremo/4204e399e547ff7e3afdd0d89a5aaf3e.

The proposal posted by Jason Barnabe five years ago has been migrated and is collecting dust here now: https://discuss.rubyonrails.org/t/using-accepts-nested-attributes-for-with-assign-attributes-means-immediate-saving-of-associated-model/70525