Пару раз я был в ситуации, где я хотел осуществить рефакторинг дизайн некоторой модели и закончил тем, что поместил логику обновления в миграции. Однако насколько я понял, это не хорошая практика (тем более, что Вы поощряетесь использовать свой файл схемы для развертывания и не свои миграции). Как Вы имеете дело с подобными проблемами?
К clearify, что я имею в виду, скажите, что у меня есть модель User. Так как я думал, что только будет два вида пользователей, а именно, "нормального" пользователя и администратора, я принял решение использовать простое булево поле, говорящее, был ли пользователь adminstrator или нет.
Однако после того, как я, в то время как я полагал, что мне был нужен некоторый третий вид пользователя, возможно, модератор или что-то подобное. В этом случае я добавляю модель UserType (и соответствующая миграция) и вторая миграция для удаления "администраторского" флага от пользовательской таблицы. И здесь прибывает проблема. В "add_user_type_to_users" миграции я должен отобразить администраторское флаговое значение на пользовательский тип. Кроме того, чтобы сделать это, пользовательские типы должны существовать, означая, что я не могу использовать файл семян, а скорее создать пользовательские типы в миграции (также рассмотренный плохой практикой). Здесь прибывает некоторый вымышленный код, представляющий ситуацию:
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
Поскольку Вы видите, что существует две проблематичных части. Сначала часть создания строки в первой модели, которая необходима, если я хотел бы выполнить все миграции подряд, затем часть "обновления" во второй миграции, которая отображает "администраторский" столбец на "type_id" столбец.
Совет?
Я нахожу более "нетрадиционным" то, что вы используете fk's, чем то, что вы загружаете UserType со старым User.admin, что, я полагаю, происходит довольно часто.
Если вы используете fk's, вы получаете уродливые ошибки mysql, которые путают пользователя. Если же вы используете AR валидации и хуки для обеспечения ссылочной целостности, вы получаете красивые и хорошо интегрированные сообщения об ошибках, которые не нарушают пользовательский поток вашего приложения.
Не беспокойтесь о миграции, которая будет выполнена один раз, и подумайте о бизнес-логике, которую вы выносите за пределы своего кода.
Это все мнения/обсуждения, но я надеюсь, что мои мысли будут вам полезны.
Обычно для этой цели используется файл db / seed.rb - записи, помещенные в него, будут загружены как часть
rake db: setup
Тем не менее, я всегда обнаруживал, что рельсы не справляются с этой проблемой. Я думал о написании плагина, который дает вам папку db / seed, имеет исходные файлы с отметкой даты для добавления записей (возможно .yml) и отслеживает исходные данные в системной таблице, чтобы их можно было вернуть / обновить.