class 方法可用的变量(在关注范围内)

Variable available to class methods (within Concerns)

我为名为 List

的 ActiveModel 创建了一个 Rails 关注点

当我从 Rails 控制台 运行 Product.all 时,我得到:

NameError: undefined local variable or method `parameters' for Product:Class

当参数更改为@parameters 时出现此错误:

NoMethodError: undefined method `include?' for nil:NilClass

可能的解决方案

使用常量 PARAMETERS 或 @@Parameters 哪个更好?优点和缺点?

代码

module List
  extend ActiveSupport::Concern

  require 'csv'

    parameters =  [ 
                  :visible,
                  :desc,
                  :value,
                  ]

    attr_accessor(*parameters)

  def initialize(attributes = {})
    attributes.each do |name, value|
      send("#{name}=", value)
    end
  end

  def persisted?
    false
  end

  module ClassMethods 
    def all   
      list = []
      filename = File.join(Rails.root,"app/models/data/#{self.name.downcase}.csv")
      CSV.foreach(filename, headers: true) do |row|
        list << self.new(row.select{|key,_| parameters.include? key.to_sym })
      end
      return list
    end

    def visible
      list = []
      filename = File.join(Rails.root,"app/models/data/#{self.name.downcase}.csv")
      CSV.foreach(filename, headers: true) do |row|
          list << self.new(row.select{|key,_| parameters.include? key.to_sym })  if row['visible']=='1'
      end
      return list
    end
  end
end

为了快速解决问题,要使 Product.allProduct.visible 对现有代码进行最少的修改,您可以在 [=17= 中定义一个 parameters 方法].例如:

def parameters
  @parameters ||= [:visible, :desc, :value]
end

如果您打算使用关注点之外的参数,或者如果子class 可能想要定义自己的参数,则此方法解决方案也可以用作 long-term 解决方案。

但是,如果参数仅用于此关注点内,并且此数据永远不会更改,至少不会通过任何应用程序逻辑更改,那么常量将是最佳解决方案,因为它传达了正确的含义reader。我也会冻结它以防止修改:

PARAMETERS = [:visible, :desc, :value].freeze

如 Rich 所述,另一种选择是定义一个 class 变量。请注意,无论您是在 List 模块内还是在 ClassMethods 模块内定义常量,该常量都将起作用。但是,如果您希望 Product 能够将其称为 parameters,则 class 变量将仅在 ClassMethods 模块内起作用。

此外,请注意 self 隐含在 ClassMethods 中的任何方法中,因此您无需指定它。如果你定义了一个 parameters 方法,它会被认为是一个 Product class 方法,如果你在 all 方法中使用了 parameters,它会引用class 方法,而不是 Rich 建议的实例方法。

Class 变量通常不鼓励在 Ruby 中使用,因为它们的副作用经常被误解。 Ruby 风格指南建议避免使用它们:https://github.com/bbatsov/ruby-style-guide#no-class-vars

关于速度,我对比了方法和常量解法,感觉常量解更快:

require "benchmark/ips"

PARAMETERS = [:visible, :desc, :value].freeze

def parameters
  @parameters ||= [:visible, :desc, :value]
end

def uses_constant
  puts PARAMETERS
end

def uses_method
  puts parameters
end

Benchmark.ips do |x|
  x.report("constant") { uses_constant }
  x.report("method") { uses_method }
  x.compare!
end

结果:

Comparison:
  constant:  45256.8 i/s
  method:    44799.6 i/s - 1.01x slower

使用 def 创建的方法在定义该方法时看不到局部变量,因此您的第一次尝试没有成功。

对于实例变量,您设置变量的对象(您的模块)和试图读取它的对象(包含您的模块的 class)是不同的。实例变量根本不参与继承,所以那是行不通的。

你的 List 模块中的常量会起作用,即

PARAMETERS = [:visible, :desc, :value]

因为您的 class 方法模块位于 List 模块内,所以其中的代码会找到 List 上设置的常量。常量查找首先查看词法范围(此搜索路径参见 Module.nesting),然后是继承。

将其设置为 class 变量:

module List
  extend ActiveSupport::Concern

    @@parameters =  [:visible, :desc, :value]
    cattr_Accessor :parameters #-> List.parameters && List.new.parameters

您现在遇到的问题是您正在从 class 方法调用 instance 方法:

module ClassMethods 
    self.new(row.select{|key,_| parameters.include? key.to_sym })

使用 class 可变代码,您可以 运行:

self.new(row.select{|key,_| self.parameters.include? key.to_sym })