如何编写一个能够动态生成作用域的方法,该方法可用于RubyonRails中的多个模型

mwg9r5ms  于 2021-09-29  发布在  Java
关注(0)|答案(1)|浏览(276)

目前,现有的作用域是这样的。

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

我有更多类似作用域的模型,我可以用更通用的方式编写,这样我就可以避免这些重复的过程。

sirbozc5

sirbozc51#

我强烈建议您不要为每个属性添加范围。 where(...) 只有5个字符,为读者提供了额外的上下文。 Person.where(name: 'John Doe') 说:在 Person 执行查询( where )并返回符合条件的集合 name: 'John Doe' .
如果添加“建议”属性范围,则该行变为 Person.name('John Doe') . 通过删除这是一个查询的上下文,读者必须“了解”每个属性名也可以作为作用域访问。
上面立即显示了另一个问题,即名称冲突。 Person.name 已获取,并返回类名。所以加上 scope :name, ->(name) { where(name: name) } 将引发一个错误。
作用域可能很有用,但如果使用太多,则会使模型的类方法命名空间变得混乱。
有了以上这些,下面是一些实际的解决方案。
您可以编写一个帮助器,使您能够轻松地为属性创建作用域。然后循环遍历传递的属性,并为它们动态创建作用域。

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) } 是可选的,但防止创建与当前公共/私有类方法名称冲突的作用域。这将跳过这些属性。您可以放心地省略这一行,但是 scope 方法在传递危险的作用域名称时可能引发argumenterror。
现在唯一要做的就是打电话 enable_attribute_scopes 在要启用属性作用域的模型中。
上面的内容应该让你了解如何处理事情,你甚至可以添加如下选项 :except:only . 还可以选择将上述代码提取到模块中,然后 extend AttributeScopeHelpers 在内部 ApplicationRecord 如果类变得杂乱无章。
然而,就像我开始回答这个问题一样,我建议不要为每个属性添加作用域。

相关问题