Home > Enterprise >  Rails migration to update a model attribute for a collection of users
Rails migration to update a model attribute for a collection of users

Time:06-21

I have a huge database prod that includes a large number of user profiles; I need to do a backfill on all users and update an attribute passport_country with another attribute value country for each, so I did a migration, see the below:

Users Table:

create_table "users", id: :serial, force: :cascade do |t|
  t.string "country"
  t.string "passport_country"
end

New Migration:

class CopyUsersCountryToPassportCounty < ActiveRecord::Migration[6.0]
  disable_ddl_transaction!

  def up
    User.unscoped.where.not(country: nil).in_batches do |users|
      users.update_all passport_country: users.country
      sleep(0.01)
    end
  end
end

I get the below error each time I to migrate the new migration; any suggestions?

Error:

rails aborted!
StandardError: An error has occurred, all later migrations canceled

Caused by:
NoMethodError: undefined method `country' for #<User::ActiveRecord_Relation:0x00007f97fbcf4be0>
Did you mean?  count

CodePudding user response:

You can directly issue a single SQL query which copies the value currently in the country column to the passport_country column. With that, you don't need to fetch the individual records from the database for update which will make this MUCH faster.

Note that this will issue a single SQL statement. It will not create ActiveRecord instances for each user, will not run any callbacks or validations. It also assumes that both country and passport_country are regular database columns of compatible types.

class CopyUsersCountryToPassportCounty < ActiveRecord::Migration[6.0]
  disable_ddl_transaction!

  def up
    User.unscoped.where.not(country: nil).update_all('passport_country = country')
  end
end

See the documentation of ActiveRecord::Relation#update_all for details how this works.

  • Related