我如何编写一个可以动态生成范围的方法,该方法可用于 ruby 中的多个模型 rails
How can i write a method that can generate scope dynamically which can be used for multiple models in ruby on rails
目前已有的作用域是这样的
module TransactionScopes
extend ActiveSupport::Concern
included do
scope :status, ->(status) { where status: status }
scope :portfolio_id, ->(portfolio_id) { where portfolio_id: portfolio_id }
scope :investor_external_reference_id, ->(investor_external_reference_id) { where investor_external_reference_id: investor_external_reference_id }
scope :portfolio_external_reference_id, ->(portfolio_external_reference_id) { where portfolio_external_reference_id: portfolio_external_reference_id }
scope :file_id, ->(file_id) { where back_office_file_id: file_id }
scope :oms_status, ->(status) { where oms_status: status }
scope :order_id, ->(order_id) { where order_id: order_id }
scope :order_status, ->(order_status) { where order_status: order_status }
scope :transaction_id, ->(transaction_id) { where transaction_id: transaction_id }
end
我还有一些范围相似的模型,我可以写更通用的方式来避免这些重复的过程吗?
我强烈建议您不要为每个属性添加范围。 where(...)
只有 5 个字符,为 reader 提供了额外的上下文。 Person.where(name: 'John Doe')
说:在 Person
上执行查询 (where
) 和 return 符合条件 name: 'John Doe'
.
的集合
如果添加建议属性范围,该行将变为 Person.name('John Doe')
。通过删除这是一个查询的上下文,reader 必须“学习”每个属性名称也可以作为范围访问。
上面直接说明了另一个问题,就是名称冲突。 Person.name
已被占用,return 是 class 名称。所以添加 scope :name, ->(name) { where(name: name) }
会引发 ArgumentError.
作用域可能很有用,但如果使用过多,它们会使模型的 class 方法命名空间混乱。
完成上述内容后,这里有一些实际的解决方案。
您可以编写一个帮助程序,使您可以轻松地为属性创建范围。然后遍历传递的属性并为它们动态创建范围。
class ApplicationRecord < ActiveRecord::Base
class << self
private
def attribute_scope_for(*attributes)
attributes.each { |attr| scope attr, ->(value) { where(attr => value) } }
end
end
end
然后在你的模型中调用这个助手。
class YourModel < ApplicationRecord
attribute_scopes_for :status, :portfolio_id # ....
end
或者,如果您想为每个属性创建一个范围,您可以使用 attribute_names
动态收集它们。然后遍历它们并根据名称创建范围。
class ApplicationRecord < ActiveRecord::Base
class << self
private
def enable_attribute_scopes
attribute_names
.reject { |attr| respond_to?(attr, true) }
.each { |attr| scope attr, ->(value) { where(attr => value) } }
end
end
end
在上面的代码片段中,.reject { |attr| respond_to?(attr, true) }
是可选的,但可以防止创建与当前 public/private class 方法名称冲突的范围。这将跳过那些属性。您可以安全地省略此行,但是 scope
方法在传递危险范围名称时可能会引发 ArgumentError。
现在唯一要做的就是在要启用属性范围的模型中调用 enable_attribute_scopes
。
以上应该让您了解如何处理事情,您甚至可以添加 :except
或 :only
等选项。如果 class 变得混乱,还可以选择将上面的代码提取到模块和 extend AttributeScopeHelpers
中 ApplicationRecord
。
但是,就像我开始回答这个问题一样,我建议不要为每个属性添加范围。
目前已有的作用域是这样的
module TransactionScopes
extend ActiveSupport::Concern
included do
scope :status, ->(status) { where status: status }
scope :portfolio_id, ->(portfolio_id) { where portfolio_id: portfolio_id }
scope :investor_external_reference_id, ->(investor_external_reference_id) { where investor_external_reference_id: investor_external_reference_id }
scope :portfolio_external_reference_id, ->(portfolio_external_reference_id) { where portfolio_external_reference_id: portfolio_external_reference_id }
scope :file_id, ->(file_id) { where back_office_file_id: file_id }
scope :oms_status, ->(status) { where oms_status: status }
scope :order_id, ->(order_id) { where order_id: order_id }
scope :order_status, ->(order_status) { where order_status: order_status }
scope :transaction_id, ->(transaction_id) { where transaction_id: transaction_id }
end
我还有一些范围相似的模型,我可以写更通用的方式来避免这些重复的过程吗?
我强烈建议您不要为每个属性添加范围。 where(...)
只有 5 个字符,为 reader 提供了额外的上下文。 Person.where(name: 'John Doe')
说:在 Person
上执行查询 (where
) 和 return 符合条件 name: 'John Doe'
.
如果添加建议属性范围,该行将变为 Person.name('John Doe')
。通过删除这是一个查询的上下文,reader 必须“学习”每个属性名称也可以作为范围访问。
上面直接说明了另一个问题,就是名称冲突。 Person.name
已被占用,return 是 class 名称。所以添加 scope :name, ->(name) { where(name: name) }
会引发 ArgumentError.
作用域可能很有用,但如果使用过多,它们会使模型的 class 方法命名空间混乱。
完成上述内容后,这里有一些实际的解决方案。
您可以编写一个帮助程序,使您可以轻松地为属性创建范围。然后遍历传递的属性并为它们动态创建范围。
class ApplicationRecord < ActiveRecord::Base
class << self
private
def attribute_scope_for(*attributes)
attributes.each { |attr| scope attr, ->(value) { where(attr => value) } }
end
end
end
然后在你的模型中调用这个助手。
class YourModel < ApplicationRecord
attribute_scopes_for :status, :portfolio_id # ....
end
或者,如果您想为每个属性创建一个范围,您可以使用 attribute_names
动态收集它们。然后遍历它们并根据名称创建范围。
class ApplicationRecord < ActiveRecord::Base
class << self
private
def enable_attribute_scopes
attribute_names
.reject { |attr| respond_to?(attr, true) }
.each { |attr| scope attr, ->(value) { where(attr => value) } }
end
end
end
在上面的代码片段中,.reject { |attr| respond_to?(attr, true) }
是可选的,但可以防止创建与当前 public/private class 方法名称冲突的范围。这将跳过那些属性。您可以安全地省略此行,但是 scope
方法在传递危险范围名称时可能会引发 ArgumentError。
现在唯一要做的就是在要启用属性范围的模型中调用 enable_attribute_scopes
。
以上应该让您了解如何处理事情,您甚至可以添加 :except
或 :only
等选项。如果 class 变得混乱,还可以选择将上面的代码提取到模块和 extend AttributeScopeHelpers
中 ApplicationRecord
。
但是,就像我开始回答这个问题一样,我建议不要为每个属性添加范围。