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.all
和 Product.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 })
我为名为 List
当我从 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.all
和 Product.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 })