Пару раз я был в ситуации, когда я хотел реорганизовать дизайн какой-либо модели и в конечном итоге применил логику обновления в миграциях. Однако, насколько я понял, это не очень хорошая практика (тем более что вам рекомендуется использовать файл схемы для развертывания, а не ваши миграции). Как вы справляетесь с такими проблемами?
Чтобы прояснить, что я имею в виду, скажем, у меня есть модель пользователя. Поскольку я думал, что будет только два типа пользователей, а именно «обычный» пользователь и администратор, я решил использовать простое логическое поле, указывающее, был ли пользователь администратором или нет.
Однако после того, как я решил, что мне нужен какой-то третий тип пользователей, возможно, модератор или что-то подобное. В этом случае я добавляю модель UserType (и соответствующую миграцию) и вторую миграцию для удаления флага «admin» из пользовательской таблицы. И тут возникает проблема. В миграции «add_user_type_to_users» я должен сопоставить значение флага администратора с типом пользователя. Кроме того, для этого должны существовать пользовательские типы, что означает, что я не могу использовать файл seed, а скорее создавать пользовательские типы в процессе миграции (также считается плохой практикой). Вот несколько вымышленных кодов, представляющих ситуацию:
class CreateUserTypes < ActiveRecord::Migration
def self.up
create_table :user_types do |t|
t.string :name, :nil => false, :unique => true
end
#Create basic types (can not put in seed, because of future migration dependency)
UserType.create!(:name => "BASIC")
UserType.create!(:name => "MODERATOR")
UserType.create!(:name => "ADMINISTRATOR")
end
def self.down
drop_table :user_types
end
end
class AddTypeIdToUsers < ActiveRecord::Migration
def self.up
add_column :users, :type_id, :integer
#Determine type via the admin flag
basic = UserType.find_by_name("BASIC")
admin = UserType.find_by_name("ADMINISTRATOR")
User.all.each {|u| u.update_attribute(:type_id, (u.admin?) ? admin.id : basic.id)}
#Remove the admin flag
remove_column :users, :admin
#Add foreign key
execute "alter table users add constraint fk_user_type_id
foreign key (type_id) references user_types (id)"
end
def self.down
#Re-add the admin flag
add_column :users, :admin, :boolean, :default => false
#Reset the admin flag (this is the problematic update code)
admin = UserType.find_by_name("ADMINISTRATOR")
execute "update users set admin=true where type_id=#{admin.id}"
#Remove foreign key constraint
execute "alter table users drop foreign key fk_user_type_id"
#Drop the type_id column
remove_column :users, :type_id
end
end
Как видите, есть две проблемные части. Сначала часть создания строки в первой модели, которая необходима, если я хочу выполнить все миграции подряд, затем часть обновления во второй миграции, которая отображает столбец «admin» в столбец «type_id».
Любой совет?