什么是在Rails中填充数据库的最佳方法?

89

我有一个rake任务在我的Rails应用程序中填充一些初始数据,例如国家、州、移动运营商等。

现在的设置方式是,在/db/fixtures目录下有一堆创建语句的文件和一个处理它们的rake任务。例如,我有一个模型是themes。我在/db/fixtures目录下有一个theme.rb文件,内容如下:

Theme.delete_all
Theme.create(:id => 1, :name=>'Lite', :background_color=>'0xC7FFD5', :title_text_color=>'0x222222',
                      :component_theme_color=>'0x001277', :carrier_select_color=>'0x7683FF', :label_text_color=>'0x000000',
                      :join_upper_gradient=>'0x6FAEFF', :join_lower_gradient=>'0x000000', :join_text_color=>'0xFFFFFF',
                      :cancel_link_color=>'0x001277', :border_color=>'0x888888', :carrier_text_color=>'0x000000', :public => true)

Theme.create(:id => 2, :name=>'Metallic', :background_color=>'0x000000', :title_text_color=>'0x7299FF',
                      :component_theme_color=>'0xDBF2FF', :carrier_select_color=>'0x000000', :label_text_color=>'0xDBF2FF',
                      :join_upper_gradient=>'0x2B25FF', :join_lower_gradient=>'0xBEFFAC', :join_text_color=>'0x000000',
                      :cancel_link_color=>'0xFF7C12', :border_color=>'0x000000', :carrier_text_color=>'0x000000', :public => true)

Theme.create(:id => 3, :name=>'Blues', :background_color=>'0x0060EC', :title_text_color=>'0x000374',
                      :component_theme_color=>'0x000374', :carrier_select_color=>'0x4357FF', :label_text_color=>'0x000000',
                      :join_upper_gradient=>'0x4357FF', :join_lower_gradient=>'0xffffff', :join_text_color=>'0x000000',
                      :cancel_link_color=>'0xffffff', :border_color=>'0x666666', :carrier_text_color=>'0x000000', :public => true)
puts "Success: Theme data loaded"
这里的想法是我想为用户安装一些默认主题。但我使用此方法时遇到了问题。
设置ID无效。这意味着,如果我决定添加一个名为“红色”的主题,我只需将主题语句添加到此fixture文件中并调用rake任务以重新生成数据库。如果我这样做,由于主题属于其他对象并且它们的ID在重新初始化后发生更改,所有链接都将失效。
我的问题首先是,这是否是处理种子数据库的好方法?在以前的帖子中,有人向我推荐了这种方法。
如果是,请问如何硬编码ID,并且是否存在任何缺点?
如果不是,那么最佳的种子数据库方法是什么?
我真的很感激融合最佳实践的长而深思熟虑的答案。
7个回答

118

既然这些答案有些过时了,因此更新一下。Rails 2.3.4中添加了一个简单的功能,即db/seeds.rb。

提供了一个新的rake任务。

rake db:seed

适用于填充常见的静态记录,例如州、国家等...

http://railscasts.com/episodes/179-seed-data

*请注意,如果您已经创建了fixture,则可以使用它们通过在seeds.rb文件中添加以下内容来填充db:seed任务(来自railscast剧集):

require 'active_record/fixtures'
Fixtures.create_fixtures("#{Rails.root}/test/fixtures", "operating_systems")

在Rails 3.x中,使用'ActiveRecord :: Fixtures'代替'Fixtures'常量。

require 'active_record/fixtures'
ActiveRecord::Fixtures.create_fixtures("#{Rails.root}/test/fixtures", "fixtures_file_name")

29

通常需要两种类型的种子数据:

  • 基础数据,构成你的应用程序核心所必需的。我称之为常见种子。
  • 环境数据,例如,为了开发应用程序,在已知状态下拥有一堆数据对于在本地上工作于该应用程序是很有用的(上面的 Factory Girl 回答涵盖了这种数据)。

根据我的经验,我总是需要这两种类型的数据。因此,我编写了一个小型 gem,它扩展了 Rails 的 seeds,并允许您在 db/seeds/ 下添加多个常见的种子文件和任何环境种子数据,例如 db/seeds/development。

我发现这种方法足以为我的种子数据提供一些结构,并且使我能够通过运行以下命令来设置我的开发或预备环境处于已知状态:

rake db:setup

测试数据和常规 SQL 转储都很脆弱且难以维护。


我喜欢使用术语“系统数据”和“运行时数据”来描述代码所依赖的事物,与用户数据不同。有时它们之间的界限很模糊。 - Tim Abell

28

使用 seeds.rb 文件或 FactoryBot 很棒,但是它们分别适用于固定的数据结构和测试。

seedbank gem 可能会为您的种子数据提供更多控制和可组合性。它插入 rake 任务,您还可以定义种子之间的依赖关系。您的 rake 任务列表将包括这些添加内容(例如):

rake db:seed                    # Load the seed data from db/seeds.rb, db/seeds/*.seeds.rb and db/seeds/ENVIRONMENT/*.seeds.rb. ENVIRONMENT is the current environment in Rails.env.
rake db:seed:bar                # Load the seed data from db/seeds/bar.seeds.rb
rake db:seed:common             # Load the seed data from db/seeds.rb and db/seeds/*.seeds.rb.
rake db:seed:development        # Load the seed data from db/seeds.rb, db/seeds/*.seeds.rb and db/seeds/development/*.seeds.rb.
rake db:seed:development:users  # Load the seed data from db/seeds/development/users.seeds.rb
rake db:seed:foo                # Load the seed data from db/seeds/foo.seeds.rb
rake db:seed:original           # Load the seed data from db/seeds.rb

27
factory_bot 看起来可以实现你想要的功能。你可以在默认定义中定义所有常见属性,然后在创建时覆盖它们。你也可以向工厂传递一个id:
Factory.define :theme do |t|
  t.background_color '0x000000'
  t.title_text_color '0x000000',
  t.component_theme_color '0x000000'
  t.carrier_select_color '0x000000'
  t.label_text_color '0x000000',
  t.join_upper_gradient '0x000000'
  t.join_lower_gradient '0x000000'
  t.join_text_color '0x000000',
  t.cancel_link_color '0x000000'
  t.border_color '0x000000'
  t.carrier_text_color '0x000000'
  t.public true
end

Factory(:theme, :id => 1, :name => "Lite", :background_color => '0xC7FFD5')
Factory(:theme, :id => 2, :name => "Metallic", :background_color => '0xC7FFD5')
Factory(:theme, :id => 3, :name => "Blues", :background_color => '0x0060EC')

使用faker时,可以快速填充数据库的关联数据,而不必使用Fixture(呸)来进行繁琐的操作。
我在rake任务中有类似以下的代码。
100.times do
    Factory(:company, :address => Factory(:address), :employees => [Factory(:employee)])
end

12
FactoryGirl实际上是用于测试而非fixture,但也可以用于生产环境中的数据加载。使用一个具有db:migrate前提条件的rake任务来加载所有默认数据。您可能需要使此rake任务足够智能,以便不会创建现有数据的副本。 - Bob Aman
2
不建议使用FactoryGirl进行种子数据生成,可查看此文章:https://robots.thoughtbot.com/factory_girl-for-seed-data。 - blackbiron

1
Rails内置了一种种子数据的方法,可以在这里中找到说明。
另一种方法是使用一个更高级或更易于种植的gem,例如:seedbank
这个gem的主要优点和我使用它的原因是它具有高级功能,如数据加载依赖和每个环境种子数据。 添加最新答案,因为这个答案是谷歌上的第一个。

-3

最好的方法是使用固定装置。

注意:请记住,固定装置直接插入数据,不使用您的模型,因此如果您有填充数据的回调函数,则需要找到解决方法。


-5
将其添加到数据库迁移中,这样每个人在更新时都可以得到它。在 Ruby/Rails 代码中处理所有逻辑,这样您就永远不必处理显式 ID 设置。

如果我需要更改初始数据,使用迁移时可能会变得混乱。你的第二条评论并没有太多意义。通过外键链接的关联将被销毁。 - Tony
c = Category.create( stuff ) p = Post.create( stuff ) p.category = c无需显式设置ID。如果您更改了初始数据,则只需创建新迁移即可。非常简单。 - Matt Rogish
假设在创建对象的时候可以进行关联,这里有一个例子,我认为你的逻辑是有问题的……如果我错了,请纠正我。我使用模板主题对数据库进行初始化。用户ID = 1创建模板ID = 2,并使用主题ID = 4。此时,数据库中的记录如下:模板:ID = 2,用户ID = 1,主题ID = 4。现在,如果我重新初始化数据库,主题ID = 4变成主题ID = 10...然后用户的模板就会被错误地应用主题。 - Tony
好的,这取决于你所说的“重新初始化”的含义——如果你从零开始,Rails会自动处理所有关联。如果你在硬编码ID值(不好!!!),那么它会崩溃。 - Matt Rogish
好的,我开始明白你的观点了,但是我需要向你展示一个场景。我用国家查找表填充了数据库。美国=>国家ID=1。然后用户创建了一家位于美国的餐厅。餐厅数据库行的country_id = 1。这很常见,对吧?我之后决定添加更多的国家……如果我清空数据库并重新填充国家查找表,那么除非ID相同,否则餐厅所在的国家将不再准确。我该如何处理这个问题? - Tony
是的,这将是一个问题。然而,这是一个很好的例子,说明为什么在查找中不要“使用”主键。例如,总是使用Country.find(:conditions => ['country_name' = ?], 'USA',然后使用找到的ID。关联表具有“旧”ID的问题?是的,这是数据完整性问题,如果您决定删除其他地方的外键现有记录,则确实需要处理它。在实时生产系统中,这些类型的更改通常包括“数据迁移”来处理此类和其他问题。 - Michael Durrant

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接