我想为 DB 初始化的种子记录和 Ruby-on-Rails 测试的固定装置分配相同的数据,而不违反 DRY 原则。
例如,让我们考虑这样的情况:模型
Genre
具有用户可编辑的属性description
以及半永久固定的属性mname
和priority
,这些属性与模型和一些算法。至于后两者,它们可以(并且应该?)被定义为模型中的常量,并且种子和固定装置(很容易)都引用它,如下所示。
# Model /app/models/genre.rb
PRIORITY = {novel: 3, poem: 5}.with_indifferent_access
# Seeds /db/seeds.rb
s="novel"; Genre.create!(mname: s, priority: Genre::PRIORITY[s],
description: "Various novels")
s="poem"; Genre.create!(mname: s, priority: Genre::PRIORITY[s],
description: "Favourite poems")
# Fixtures /test/fixtures/genres.yml
novel:
mname: novel
priority: <%= Genre::PRIORITY[:novel] %>
description: "Various novels"
poem:
mname: poem
priority: <%= Genre::PRIORITY[:poem] %>
description: "Favourite poems"
在此示例中,
description
的值在种子和夹具中单独定义,手动设置为相同的值。这违背了 DRY 原则。我宁愿将它们定义在一个地方,以便种子和赛程都可以毫无歧义地引用它们。
我应该把它们放在哪里以及如何参考它们?
与硬编码
priority
不同,恐怕模型中的常量(或 lib/
或 config/
中的代码)不是定义用户可编辑参数 description
初始值的正确位置,因为 (1) 它可以随时更改,并且 (2) 与运行应用程序无关。这些值仅与播种和测试相关(作为初始值),以便开发和测试环境中的输出看起来相似。
OP 希望test数据库(DB)中的部分初始(夹具)数据尽可能接近真实(development,product)DB,这将最大限度地减少仅在测试环境,或者相反,测试环境中出现的错误仅在实际情况下的其他环境中永远不会出现。另外,如果系统测试时在test环境中看到的截图与在development环境中看到的非常相似,那就让人放心了。
正如OP所说,数据库中与应用程序算法紧密相连的表数据应该在
app/
(或conf/
)目录中定义,并在数据库初始化中读取,无论是用于测试 或 开发 环境。但是对于应用程序来说不是必需的数据,包括数据库初始化的一些或大部分种子,不应该是必需的。我认为 db/
下的目录,也许(用户创建的)db/seeds/
是放置此类数据的合适位置。其他可能性可能包括 lib/
下的目录,例如 lib/assets/
.
使用带有模型Genre的OP案例,以下是实现此类案例场景的方法之一。 假设模型假设有一个 Genre 记录,其唯一属性
mname: "novel"
与固定的 priority
3 相关联,依此类推(无论是永久的还是在某些场合或条件下)。您首先创建一个目录db/seeds/
。
型号
## Model /app/models/genre.rb
class Genre < ApplicationRecord
# defines algorithm-related constants.
PRIORITY = {novel: 3, poem: 5}.with_indifferent_access
end
db/中的种子数据(在模块中定义)
其中读取上述模型数据。
## Seeds /db/seeds/genre.rb
module Seeds
module Genre
# Data to seed (descriptions are defined here)
SEED_DATA = [["novel", "Various novels"],
["poem", "Favourite poems"]].map{ |ea|
[ea[0], {mname: ea[0],
priority: Genre::PRIORITY[ea[0]],
description: ea[1], }.with_indifferent_access ]
}.to_h.with_indifferent_access
end
end
种子脚本(默认用于开发),
其中读取上述种子数据。
## Seeds /db/seeds.rb
require_relative "seeds/genre.rb"
Seeds::Genre::SEED_DATA.each_pair do |ek, ev|
record = Genre.find_or_initialize_by(mname: ev[:mname]}
next if record.new_record?
# If a record with the mname exists, do nothing, so that
# this seeding script can be run multiple times without reverting
# deliberate changes in production back to the original.
record.update!(ev)
end
测试环境设置
## /test/test_helper.rb
ENV['RAILS_ENV'] ||= 'test'
require_relative '../config/environment'
require 'rails/test_help'
Dir[Rails.root+"db/seeds/*.rb"].uniq.each do |seed_file|
require seed_file
end
ActiveRecord::FixtureSet.context_class.include Seeds
用于测试的夹具数据,
其中读取部分包含模型常量的上述种子数据。
## Fixtures /test/fixtures/genres.yml
<% Seeds::Genre::SEED_DATA.each_pair do |ename, ehash| %>
<% ehash.each_pair do |ek, edata| %>
genre_<%= ename %>:
<%= ek %>: <%= edata %>
<% end %>
<% end %>
这里,钥匙是
db/seeds/
中定义种子数据,其中一些数据是从模型中读取的,/test/test_helper.rb
和注意:上面的示例将 Ruby 字符串的直接输入放入 YAML。一般来说,该值应该被处理为 YAML 安全的,尤其是可能包含换行符的长字符串。
有了这些,您可以保证种子和测试夹具之间的数据集(可能部分)相同,基本上消除了数据不一致的风险,同时保持将附加数据注册到夹具的灵活性,而且您还可以(有时很大)减少灯具中的线路数量。
请参阅 Rails Reference 了解有关夹具设置的详细信息。