【发布时间】:2020-04-15 12:51:53
【问题描述】:
我有一个带有 Person 模型和 Field 模型的 HR 系统。 Person 有一些属性存储在常规数据库列中,还有一些可以动态添加。
Field 表对 people 表上的每个数据库列都有一条记录。这些是系统必填字段。管理员可以在配置应用程序时添加任意数量的(非系统必需的)字段。他们还可以设置字段的属性,例如它们是否是强制性的。
对于非强制性字段,管理员可能希望在用户主页上添加一个小部件,以显示有多少人缺少此属性。例如,管理员可以添加一个 :personal_email 字段和一个显示有多少人没有输入该字段的小部件。
可以在运行时将字段添加到应用程序中,并且范围用于过滤人员表中的缺失记录。这一切都是使用 PersonField 模块完成的。添加新字段并请求小部件时,应用程序会产生错误NoMethodError: undefined method `missing_personal_email' for #<Person::ActiveRecord_Relation>。
重新启动 rails 服务器时不会出现错误。我认为这可能与 cache_classes 有关,但它发生在开发中它是错误的。如何重构 PersonField 模块以避免此问题?
class Person < ActiveRecord::Base
include PersonField
end
class Field < ActiveRecord::Base
enum field_type: {:boolean => 1, :integer => 2, :string => 3, :date => 4, :time => 5, :datetime => 6, :float => 7, :decimal => 8, :reference => 9, :any => 10, :email => 11, :phone => 12, :text => 13, :currency => 14, :postcode => 15}
enum widget: { :not_set => 0, :missing => 1, :not_missing => 2 }
scope :widget, -> { where.not(widget: 0) }
scope :system_required, -> {where(system_required: 1)}
scope :not_system_required, -> {where(system_required: 0)}
end
module PersonField
included do
typed_store :data do |s|
Field.active.each do |f|
case f.field_type.to_sym
when :integer, :reference
s.integer f.name.to_sym
when :string, :text, :email, :phone, :postcode
s.string f.name.to_sym
when :datetime
s.datetime f.name.to_sym
# etc for all field types
else
s.any f.name.to_sym
end
end
end
end
Field.active.system_required.widget.uniq.each do |f|
scope "#{f.widget}_#{f.name}", -> { where("people.#{f.name} IS NULL") }
end
Field.active.not_system_required.widget.pluck(:name).uniq.each do |f|
# EG for :personal_email field this gives the SQL condition: people.data NOT LIKE '%personal_email%' OR people.data LIKE '%personal_email: \n%'
scope "#{f.widget}_#{f.name}", -> { where("people.data NOT LIKE '%#{f.name}%' OR people.data LIKE '%#{f.name}: \n%'") }
end
end
【问题讨论】:
-
您所做的基本上是重新发明实体属性值(反)模式。由于类缓存,在运行时对类进行整个元编程的方法不太可能完全成功。感觉就像您试图将某些东西硬塞到一个不属于的关系数据库中,而像 mongodb 这样的文档存储会更合适。如果您决定要深入这个兔子洞,请使用单表继承来创建不同的字段类,而不是使用枚举和切换解决方案,这在单个类中发挥了很大作用。
-
感谢您的评论。你是对的,这是 EAV,看看这个项目将来是否可以转移到 MongoDB 肯定是有意义的。就目前而言,这是不可能的。我可以看到 STI 会简化 Field 类中的事情
-
我不完全确定我将如何处理这个问题,但是在实际设置内容或验证记录或查询时,您必须实际执行数据库查询和即时评估事物,而不是对类进行元编程。
标签: ruby-on-rails metaprogramming