You have to manually update the data in case of data inconsistency or when you want to change the value of an existing column. Manual updates should be done with caution, as you will be working directly with the production data.
This time, I will show you how to safely and manually update the data in your Ruby on Rails application.
For example, suppose a data inconsistency occurs between Event and Entry linked one-to-many, and it becomes necessary to manually change the Entry linked to ʻevent_id = 10 to ʻevent_id = 11
. I will.
A common workaround is to manually update with the rails console
like this:
#Confirmation before update
Entry.where(event_id: 10).count
=> 5
Entry.where(event_id: 11).count
=> 0
#Update work
Entry.where(event_id: 10).update_all(event_id: 11)
#Confirmation after update
Entry.where(event_id: 10).count
=> 0
Entry.where(event_id: 11).count
=> 5
When updating data with rails console
, I think that the process of" executing commands in the development environment and confirming that there are no errors before performing the actual work "is often adopted.
If the number of data is small or the data update is not complicated, manual update with rails console
is fine.
However, ** When updating complicated data, I want to be able to return to the state before the update if there is any problem **.
In such a case, it is recommended to update the data using transactions.
Enclose the data update in modelname.transaction
so that rollback can be performed in case of problems.
Rollback is performed when an exception occurs within the transaction.
You can use raise ActiveRecord :: Rollback
to intentionally raise an exception, which results in a rollback.
The script that executes the manual data update introduced earlier in a transaction is as follows.
# app/script/maintenance/20201103_fix_envent_id.rb
ActiveRecord::Base.transaction do
# update_Prevent at time stamps from being updated
Entry.record_timestamps = false
puts "Before event_id = 10: #{Entry.where(event_id: 10).count}"
puts "Before event_id = 11: #{Entry.where(event_id: 11).count}"
Entry.where(event_id: 10).each do |entry|
begin
entry.update!(event_id: 11)
rescue => e
puts 'entry update failure'
puts e
puts "entry.id: #{entry.id}"
end
end
puts "After entry_id = 10: #{Entry.where(event_id: 10).count}"
puts "After entry_id = 11: #{Entry.where(event_id: 11).count}"
print "Are you sure?(yes/no) > "
answer = gets.strip
if answer == "yes"
puts "committed"
else
puts "rollback"
raise ActiveRecord::Rollback
end
#Since I set it to false at the beginning, return it to the original (true)
Entry.record_timestamps = true
end
The point of the script is that after the data update is completed, gets
is used to wait for input from the command.
If you enter a character other than yes
, raise ActiveRecord :: Rollback
will be called and rollback will be performed.
By outputting the record status as appropriate, such as how the number of records changed before and after the data update, you can safely complete the data update after confirming that there are no problems.
Run the script with rails runner
.
This time I'm running rails runner
on my development environment. Add the -e production
option if you want to run it in a production environment.
$ rails runner app/script/maintenance/20201103_fix_envent_id.rb
Before Entry.where(event_id: 10).count: 5
Before Entry.where(event_id: 11).count: 0
After Entry.where(event_id: 10).count: 0
After Entry.where(event_id: 11).count: 5
Are you sure?(yes/no) > yes
committed
Rails applications have multiple methods for updating data. The differences between typical methods are as follows.
Method | Validation | Callback | updated_update of at | Whether to update multiple columns |
---|---|---|---|---|
update | ○ | ○ | ○ | ○ |
update_attributes | ○ | ○ | ○ | ○ |
update_attribute | × | ○ | ○ | × |
update_all | × | × | × | ○ |
update_columns | × | × | × | ○ |
update_column | × | × | × | × |
** If you just want to change the value of the record, use ʻupdate_columns, and if you want to update the data while ensuring consistency, use ʻupdate
**.
Also, when updating data with ʻupdate, if you are in trouble if the ʻupdated_at
of the record to be updated changes, it is better to setrecord_timestamps
to false
before updating. Don't forget to change record_timestamps
back to true
after the update.
This concludes the introduction of data update methods using transactions.
--If you want to update data safely, it is recommended to execute the script enclosed in transaction with rails runner. --Intentionally rollback is possible with "raise ActiveRecord :: Rollback" --Update the data with "update_columns" if you just want to change the record value, or "update" if you want to ensure consistency.
I'm on Twitter (@ nishina555). I hope you will follow me!
-Summary of how to update attribute of ActiveRecord
Recommended Posts