Intermediate table is saved by assign_attributes

--When you want to receive a value from an input form, validate it, and then save it, you can use ʻassign_attributes to update the variable of the instance once, and the result is unexpected. --In the rails intermediate table, DB insert runs and even commits when ʻassign_attributes is executed. --Therefore, when saving failed due to a validation error of model, the intermediate table was left in the saved state. ――I understood as follows, but this does not seem to be accurate. --ʻUpdate_attributes updates the instance variables and even updates the DB --ʻAssign_attributes updates instance variables, but not DB ――In other words, if you want to validate after ʻassign_attributes`, or if you do not want to save for some reason, you need to explicitly roll back or do not insert first.

Use Case

--Assuming the following use cases --ʻUserandGroup are many-to-many, and GroupUsers exists as a model of the intermediate table. --When saving ʻUser, you want to save GroupUsers at the same time. --Set the parameters received from view and save, but if a validation error occurs, do not save and return an error

groups.rb


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

users.rb


class User < ApplicationRecord
  has_many :group_users
  has_many :groups, through: :group_users
  
  validates :name, presence: true
end

group_users.rb


class GroupUsers < ApplicationRecord
  belongs_to :group
  belongs_to :user
end

--The following field grows in User, but if you set a value in that field, the insert will run. - groups - group_ids - group_users - group_user_ids

Proposal 1 Make a transaction

-Enclose ʻassign_attributes to save` in transaction and roll back if it cannot be saved.

users_controller.rb


class UsersController < ApplicationController
  def update
    @user = Users.new

    begin
      ActiveRecord::Base.transaction do
        @user.assign_attributes(user_params)     #Here it is temporarily saved in the intermediate table, but save!If there is an error in, it will be rolled back
        @user.save!
      end
      p "Successful update"
    rescue
      p "Update failed"
    end
  end
end

Proposal 2 Use after_save

--ʻAfter_save is executed just before COMMIT to the database --In other words, it calls the method after executing model validation etc. --By sending a parameter from view with an attribute name different from the field for relations that grows automatically, setting the value of that parameter to the attribute of the intermediate table you want to save after validating the model, and saving it, the intermediate table after passing validation To be saved --When executing ʻassign_attributes, do not set anything in the field that means relation, but set the value in the method executed by ʻafter_save`.

users.rb


class User < ApplicationRecord
  has_many :group_users
  has_many :groups, through: :group_users
  
  validates :name, presence: true
  
  after_save :save_group_user
  
  attr_accessor :g_ids
  
  #Called after user validation and just before committing
  def save_group_user
    self.group_ids = @g_ids
  end
end

--Define ʻafter_savein model and setgroup_ids for intermediate table in the method to be executed. --In addition, define ʻattr_accessor: g_ids as an attribute for setting the parameters from view. --From view, send a parameter with an appropriate field name such as g_ids and save the intermediate table by setting it to group_ids with the method of ʻafter_save`.

users_controller.rb


class UsersController < ApplicationController
    def update
      @user = Users.new
      @user.assign_attributes(user_params)
      @user.save ? (redirect_to root_path notice: 'Successful update') : (render :edit)
    end
end

--On the controller, just execute save normally to save.

By the way

--If User is in the id field of nil, such as when creating a new one, the insert will not run automatically even if you set a value in the field for the intermediate table, so you should be careful when updating.

Recommended Posts

Intermediate table is saved by assign_attributes
Display pages by category after implementing intermediate table