ruby-on-railsactiverecordruby-on-rails-5activemodel

Using `assign_attributes` saves `has_many through:` association immediately


As far as I know, assign_attributes (unlike update_attributes) is not supposed to save the record or for that matter, any record.

So it quite startled me when I discovered that this is not true when supplying _ids for a has_many through: relation.

Consider the following example:

class GroupUser < ApplicationRecord
  belongs_to :group
  belongs_to :user
end

class Group < ApplicationRecord
  has_many :group_users
  has_many :users, through: :group_users
end

class User < ApplicationRecord
  has_many :group_users
  has_many :groups, through: :group_users

  validates :username, presence: true
end

So we have users and groups in an m-to-m relationship.

Group.create # Create group with ID 1
Group.create # Create group with ID 2

u = User.create(username: 'Johny')

# The following line inserts two `GroupUser` join objects, despite the fact 
# that we have called `assign_attributes` instead of `update_attributes` 
# and, equally disturbing, the user object is not even valid as we've 
# supplied an empty `username` attribute.
u.assign_attributes(username: '', group_ids: [1, 26])

The log as requested by a commenter:

irb(main):013:0> u.assign_attributes(username: '', group_ids: [1, 2])
  Group Load (0.2ms)  SELECT "groups".* FROM "groups" WHERE "groups"."id" IN (1, 2)
  Group Load (0.1ms)  SELECT "groups".* FROM "groups" INNER JOIN "group_users" ON "groups"."id" = "group_users"."group_id" WHERE "group_users"."user_id" = ?  [["user_id", 1]]
   (0.0ms)  begin transaction
  SQL (0.3ms)  INSERT INTO "group_users" ("group_id", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["group_id", 1], ["user_id", 1], ["created_at", "2017-06-29 08:15:11.691941"], ["updated_at", "2017-06-29 08:15:11.691941"]]
  SQL (0.1ms)  INSERT INTO "group_users" ("group_id", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["group_id", 2], ["user_id", 1], ["created_at", "2017-06-29 08:15:11.693984"], ["updated_at", "2017-06-29 08:15:11.693984"]]
   (2.5ms)  commit transaction
=> nil

I daresay that update_attributes and the _ids construct are mostly used for processing web forms - in this case a form that updates the user itself as well as its group association. So I think it is quite safe to say that the general assumption here is all or nothing, and not a partial save.

Am I using it wrong in some way?


Solution

  • @gokul-m suggests reading about the issue at https://github.com/rails/rails/issues/17368. One of the comments in there points to a temporary workaround: https://gist.github.com/sudoremo/4204e399e547ff7e3afdd0d89a5aaf3e