Sometimes we write plain SQL queries in migrations so we don't have to mock ActiveRecord classes. These two migrations do the same:
class Migration1 < ActiveRecord::Migration[5.2]
class User < ActiveRecord::Base; end
def up
add_column :users, :trashed, :boolean
User.update_all(trashed: false)
end
end
class Migration2 < ActiveRecord::Migration[5.2]
def up
add_column :users, :trashed, :boolean
update("UPDATE users SET trashed = #{quoted_false}")
end
end
The plain SQL migration is less code, but has a pitfall when combined with a condition. Let's say, we want to trash all users that don't have blue hair, and the database contains users with the hair_color
'blue'
, 'green
, ''
and NULL
. Then the following migration will be wrong:
class Migration < ActiveRecord::Migration[5.2]
def up
add_column :users, :trashed, :boolean
update("UPDATE users SET trashed = #{quoted_false} WHERE hair_color = 'blue'")
update("UPDATE users SET trashed = #{quoted_true} WHERE NOT hair_color = 'blue'")
end
end
Looks right, but it is not! All users where hair_color
is NULL
will end up with trashed
being NULL
, too. So we have to add OR hair_color IS NULL
to the query:
class Migration < ActiveRecord::Migration[5.2]
def up
add_column :users, :trashed, :boolean
update("UPDATE users SET trashed = #{quoted_false} WHERE hair_color = 'blue'")
update("UPDATE users SET trashed = #{quoted_true} WHERE NOT hair_color = 'blue' OR hair_color IS NULL")
end
end
Conclusion:
Is might be more elegant to simply use the Migration1
solution from the first example.
Note
If need to copy data, the plain SQL solution can still be the right choice, e. g. if you want to copy columns:
update("UPDATE users SET birth_name = last_name WHERE NOT hair_color = '' OR hair_color IS NULL")