Tom Prats
Tom Prats

Reputation: 7911

Rails 4 update Type when migrating to Single Table Inheritance

Rails 4.0.4, Ruby 2.1.2

I want to use STI like so:

User < ActiveRecord::Base
Admin < User

But currently I have:

User < ActiveRecord::Base
Info < ActiveRecord::Base

So I changed my models, and then start writing my migration. In my migration, I first add a column to allow STI:

add_column :users, :type, :string

Then I want to update the Users currently in the database to be Admin

# Place I'm currently stuck

Then I move all my Info records into the Users table

Info.all.each { |info| User.create(name: info.name, email: info.email) }

Everything seems to work except turning the previous Users into Admins. Here are some things I've tried:

# Seems to work, but doesn't actually save type value
User.each do |user|
  user.becomes!(Admin)
  user.save! # evaluates to true, doesn't have any errors
end

# Seems to work, but doesn't actually save type value
# I've also tried a combo of this one and the above one
User.each do |user|
  user.type = "Admin"
  user.save! # evaluates to true, doesn't have any errors
end

User.each do |user|
  user = user.becomes!(Admin)
  user.save! # evaluates to true, doesn't have any errors
end

# Seems to work, but doesn't actually save type value
User.each do |user|
  user.update_attributes(type: "Admin")
end

Each time the local user variables seems to have the correct type ("Admin"), along with save evaluating to true, but when I check Admin.count or check Users type value, it is always nil. I know you're not supposed to change them, but this is just to migrate the data over to STI and then I'll be able to start creating Users or Admin with the proper class.

At the very least I think Rails should raise an error, set an error or somehow let the developer know it's failing the save calls.

Upvotes: 3

Views: 3089

Answers (2)

James EJ
James EJ

Reputation: 2134

If you had more rows in the database User.each would become quite slow as it makes an SQL call for each user.

Generally you could use User.update_all(field: value) to do this in one SQL call but there is another reason to avoid this: if the User model is later removed the migration will no longer run.

One way to update all rows at once without referencing the model is to use raw SQL in the migration:

def up
  execute "UPDATE users SET type = 'Admin' WHERE type IS NULL"
end

Upvotes: 2

Tom Prats
Tom Prats

Reputation: 7911

It turns out that while update_attributes doesn't work for type (I haven't researched why yet), update_column does work.

So the migration simply becomes:

User.each do |user|
  user.update_columns(type: "Admin")
end

The reason this works and other updates don't can probably be traced back to either callbacks or validations not being run. I have no callbacks that would prevent it, but maybe there are default Rails ones for type

http://apidock.com/rails/ActiveRecord/Persistence/update_columns

Upvotes: 4

Related Questions