类方法可用的变量(在Concerns中)

时间:2016-01-18 01:07:31

标签: ruby-on-rails ruby-on-rails-4 instance-variables class-method

我为我的ActiveModel创建了一个名为List

的Rails Concern

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

  

NameError:未定义的局部变量或方法`parameters'   产品:分类

当参数更改为@parameters时,我收到此错误:

  

NoMethodError:未定义的方法`include?'为零:NilClass

可能的解决方案

哪个更好,使用常量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

3 个答案:

答案 0 :(得分:2)

要获得快速解决方案,要使Product.allProduct.visible能够对现有代码进行最少量的修改,您可以在parameters中定义module ClassMethods方法。例如:

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

如果您计划使用关注点之外的参数,或者子类可能想要定义自己的参数,则此方法解决方案也可以作为长期解决方案。

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

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

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

另请注意,self中的任何方法都隐含ClassMethods,因此您无需指定它。如果您定义了parameters方法,则会将其视为Product类方法,如果您在parameters方法中使用all,则会引用类方法,不是Rich建议的实例方法。

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

答案 1 :(得分:0)

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

使用实例变量,您正在设置变量的对象(您的模块)和尝试读取它的对象(包含您的模块的类)是不同的。实例变量根本不参与继承,因此不起作用。

列表模块中的常量是什么,即

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

因为你的类方法模块在List模块中,所以它里面的代码会找到List上设置的常量。常量查找首先查看词法范围(请参阅此搜索路径的Module.nesting),然后查看继承。

答案 2 :(得分:0)

将其设置为类var:

module List
  extend ActiveSupport::Concern

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

您现在遇到的问题是您从方法调用实例方法:

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

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

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