2014-11-14 43 views
1
class Numeric 
@@currencies = {'yen' => 0.013, 'euro' => 1.292, 'rupee' => 0.019} 

    def method_missing(method_id, *args, &block) 
    singular_currency = method_id.to_s.gsub(/s$/, '') 
    if @@currencies.has_key?(singular_currency) 
     self * @@currencies[singular_currency] 
    else 
     super 
    end 
    end 
end 

puts 3.yen 
# Output is 
# 0.039 

我的问题是为什么不,如果我们更换@@货币与即时可变@currencies并添加attr_reader此代码的工作:货币取代静态变量在鳕鱼

像这样的事情

class Numeric 
@currencies = {'yen' => 0.013, 'euro' => 1.292, 'rupee' => 0.019} 
attr_accessor :currencies 

    def method_missing(method_id, *args, &block) 
    singular_currency = method_id.to_s.gsub(/s$/, '') 
    if @currencies.has_key?(singular_currency) 
     self * @currencies[singular_currency] 
    else 
     super 
    end 
    end 
end 

puts 3.yen 
# Output 
# method_missing': undefined method `has_key?' for nil:NilClass (NoMethodError) 
# from Untitled.rb:15:in `<main>' 

不是3已经是类的数字,因此,设置货币应该能够工作并返回正确的哈希组合?

编辑:so method_missing是一个静态方法呢?为什么不用self.method_missing定义?

+0

'method_missing'是一个实例方法,而不是一个静态方法。 – August 2014-11-14 22:22:29

回答

0

你可以这样做:

class Numeric 
    @currencies = {'yen' => 0.013, 'euro' => 1.292, 'rupee' => 0.019} 
    class << self 
    attr_reader :currencies 
    end 
    def method_missing(method_id, *args, &block) 
    singular_currency = method_id.to_s.gsub(/s$/, '') 
    if Numeric.currencies.key?(singular_currency) 
     self * Numeric.currencies[singular_currency] 
    else 
     super 
    end 
    end 
end 

的线条:

class << self 
    attr_reader :currencies 
end 

创建读取类实例变量的访问器10。

你可能会倾向于写(像我一样开始):

if self.class.currencies.key?(singular_currency) 

但是,这并不工作,因为method_missing3调用的Fixnum一个实例中,Numeric一个子类。回想一下,子类不能直接访问其祖先的类方法。这就是为什么我们需要明确识别Numeric作为接收器。

由于:

Fixnum.ancestors => [Fixnum, Integer, Numeric,...] 

我们看到:

if self.class.superclass.superclass.currencies.key?(singular_currency) 

将与Fixnum的工作,但不与其他Numeric子类:BignumFloat

0

实例变量仅适用于类的实例。类变量可用于整个班级。

换句话说,在@currencies.has_key?在你的代码是不确定的,因为它不能看到@currencies上线2

1

类声明的范围内设置@currencies集类本身的实例变量:

Numeric.instance_variable_get(:@currencies) 
#=> {"yen"=>0.013, "euro"=>1.292, "rupee"=>0.019} 

在另一方面,@currenciesmethod_missing类和currencies存取是指在数值的一个特定实例的@currencies变量,它没有定义:

Numeric.new.instance_variable_get(:@currencies) 
#=> nil 

您可以通过在method_missing方法定义的类本身的访问,并调用访问解决这个问题:

class Numeric 
    @currencies = {'yen' => 0.013, 'euro' => 1.292, 'rupee' => 0.019} 
    class << self 
    attr_accessor :currencies 
    end 

    def method_missing(method_id, *args, &block) 
    singular_currency = method_id.to_s.gsub(/s$/, '') 
    if self.class.currencies.has_key?(singular_currency) 
     self * self.class.currencies[singular_currency] 
    else 
     super 
    end 
    end 
end 

Numeric.currencies 
#=> {"yen"=>0.013, "euro"=>1.292, "rupee"=>0.019} 

虽然有仍然使用这种方法的一个问题。尽管currencies访问器现在引用了类中的实例变量(而不是该类的特定实例上的实例变量,但与前面的情况相同),@currencies仍然只在Numeric类中设置,而不是其任何子类:

Fixnum.currencies 
#=> nil 

为了解决这个问题,你可以修改的属性访问器为每个单独的类(所以FixnumFloat每个将有自己独立的@currencies变量)自动提供一个默认值,或者回去使用类变量,像这样:

class Numeric 
    @@currencies = {'yen' => 0.013, 'euro' => 1.292, 'rupee' => 0.019} 
    def self.currencies 
    @@currencies 
    end 
    def self.currencies= new_currencies 
    @@currencies = new_currencies 
    end 

    def method_missing(method_id, *args, &block) 
    singular_currency = method_id.to_s.gsub(/s$/, '') 
    if @@currencies.has_key?(singular_currency) 
     self * @@currencies[singular_currency] 
    else 
     super 
    end 
    end 
end 

Numeric.currencies 
#=> {"yen"=>0.013, "euro"=>1.292, "rupee"=>0.019} 
Fixnum.currencies 
#=> {"yen"=>0.013, "euro"=>1.292, "rupee"=>0.019} 
1.yen 
#=> 0.013 
Numeric.currencies['bitcoin'] = 394.03 
#=> 394.03 
5.bitcoin 
#=> 1970.1499999999999 
+0

很好的答案。 +1。这跟我的非常相似,我在你之后发布。我们只是按照同样的思路思考,因为我习惯在准备自己的答案之前不要阅读其他答案。事实上,我有时候不会仔细阅读这个问题,这可能会导致问题。 – 2014-11-15 05:40:51

0

问题是您正在初始化@currencies以外的方法。不在方法内部的任何代码将在类对象的上下文中进行评估,而不是类的实例。

看看这个例子类:

class Foo 
    @bar = "baz" 

    def self.bar; @bar; end 

    def bar; @bar; end 
end 

现在让我们来看看,我们定义了这两种方法的结果。

Foo.bar  # => "baz" 
Foo.new.bar # => nil 

这意味着@bar属于Foo类,而不是的Foo一个实例。

您的问题可以通过在方法的初始化@currency,通常initialize解决:

class Numeric 
    def initialize 
    @currency = ... 
    end 
end