--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.
--Assuming the following use cases
--ʻUserand
Group 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
-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
--ʻ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 set
group_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.
--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.