[RUBY] Prevent operations! How to securely update Rails manually using transactions

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.

Common manual data update method

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.

Method introduced here: Update data within a transaction

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

Reference: Summary of update methods for Rails applications

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.

Summary

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!

reference

-Summary of how to update attribute of ActiveRecord

Recommended Posts

Prevent operations! How to securely update Rails manually using transactions
[Rails] How to prevent screen transition
[Rails] How to upload images using Carrierwave
How to implement image posting using rails
[Rails] How to handle data using enum
How to prevent direct URL typing in Rails
[Rails] How to upload multiple images using Carrierwave
How to write Rails
How to uninstall Rails
How to manually generate a JWT with Rails Knock
Rails learning How to implement search function using ActiveModel
[Rails] How to install a decorator using gem draper
How to set environment variables when using Payjp with Rails
[Rails] How to use enum
How to read rails routes
How to use rails join
How to set and describe environment variables using Rails zsh
How to update with activerecord-import
How to deploy jQuery in your Rails app using Webpacker
[Rails] How to delete images uploaded by carrierwave (using devise)
[Ruby On Rails] How to update the calculated result to an integer type column using update_column
How to terminate rails server
How to write Rails validation
How to write Rails seed
[Rails] How to use validation
[Rails] How to disable turbolinks
[Rails] How to use authenticate_user!
[Rails] How to use "kaminari"
[Rails] How to implement scraping
[Rails] How to make seed
How to write Rails routing
[Rails] How to install simple_calendar
[Rails] How to install reCAPTCHA
[Rails] How to use Scope
How to authorize using graphql-ruby
How to control transactions in Spring Boot without using @Transactional
How to prevent past dates from being entered in Rails forms
[Rails 6] How to create a dynamic form input screen using cocoon
[Rails] How to upload images to AWS S3 using Carrierwave and fog-aws
[Rails] How to upload images to AWS S3 using refile and refile-s3
[Rails 5] How to display the password change screen when using devise
[rails6.0.0] How to save images using Active Storage in wizard format
[Rails] How to use gem "devise"
How to deploy jQuery on Rails
[Rails] How to install Font Awesome
[Rails] How to use devise (Note)
[Rails] How to use flash messages
[rails] How to display db information
How to use Ruby on Rails
How to deploy Bootstrap on Rails
[Rails] How to speed up docker-compose
[Rails] How to add new pages
Rails on Tiles (how to write)
[Rails] How to write exception handling?
[Rails] How to install ImageMagick (RMajick)
[Rails] How to install Font Awesome
[Rails] How to use Active Storage
How to introduce jQuery in Rails 6
[Rails] How to implement star rating
How to return Rails API mode to Rails
How to get along with Rails