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.