Ruby方法链接错误

时间:2014-02-19 19:26:26

标签: ruby casting ruby-2.0

TaxArray类继承自Array类:

class TaxArray < Array

  # instance methods
  def for_region(region_code)
    self.select{|tax|tax[:region_code].include?(region_code)}
  end

  def for_type(type)
    self.select{|tax|tax[:type].include?(type)}
  end

end

它包含hashes税:

@taxes=TaxArray.new
@taxes << {name: "Minnesota Sales", rate: 6.875/100, type: [:food,:liquor], region_code: 'MN'}
@taxes << {name: "Downtown Liquor", rate: 3.0/100, type: [:liquor], region_code: 'MN'}
@taxes << {name: "Downtown Restaurant", rate: 3.0/100, type: [:food], region_code: 'MN'}
# fictitious WI rates 
@taxes << {name: "Wisconsin Sales", rate: 5.0/100, type: [:food,:liquor], region_code: 'WI'}
@taxes << {name: "Wisconsin Food", rate: 2.0/100, type: [:food], region_code: 'WI'}
@taxes << {name: "Wisconsin Liquor", rate: 1.0/100, type: [:liquor], region_code: 'WI'}

for_type方法按预期工作:

> @taxes.for_type(:liquor)
=> [{name: "Minnesota Sales", rate: 6.875/100, type: [:food,:liquor], region_code: 'MN'},{name: "Downtown Liquor", rate: 3.0/100, type: [:liquor], region_code: 'MN'},{name: "Wisconsin Sales", rate: 5.0/100, type: [:food,:liquor], region_code: 'WI'},{name: "Wisconsin Liquor", rate: 1.0/100, type: [:liquor], region_code: 'WI'}]

for_region方法按预期工作:

> @taxes.for_region('WI')
=> [{:name=>"Wisconsin Sales", :rate=>0.06, :type=>[:food, :liquor], :region_code=>"WI"}, {:name=>"Wisconsin Food", :rate=>0.02, :type=>[:food], :region_code=>"WI"}, {:name=>"Wisconsin Liquor", :rate=>0.01, :type=>[:liquor], :region_code=>"WI"}] 

但是,当我将这些方法链接在一起时,我收到一个错误:

> @taxes.for_type(:liquor).for_region('WI')
NoMethodError: undefined method `for_region' for #<Array:0x007f90320d7c20>

每种方法都会返回Array,而不是TaxArray

我应该将每个方法的返回值转换为TaxArray还是有其他方法?

5 个答案:

答案 0 :(得分:2)

一般来说,我不建议继承Ruby原语,正是因为你碰到的原因。包含数组实例变量并对其进行操作同样简单:

class TaxArray
  attr_reader :tax_hashes

  def initialize(tax_hashes)
    @tax_hashes = tax_hashes
  end

  def for_type(type)
    self.class.new(tax_hashes.select {|h| h[:type] == type })
  end
end

你也可以使用define_method一次定义你的整个api:

class TaxArray

  attr_reader :tax_hashes

  def initialize(hashes)
    @tax_hashes = hashes
  end

  [:name, :rate, :type, :region_code].each do |attr|
    define_method :"for_#{attr}" do |arg|
      self.class.new tax_hashes.select {|tax| Array(tax[attr]).include?(arg) }
    end
  end
end

为什么不更进一步,并将所有未知方法转发给数组,假设这个类应该响应数组的任何内容:

class TaxArray
  def method_missing(name, *args, &block)
    if tax_hashes.respond_to?(name)
      self.class.new(tax_hashes.send(name, *args, &block))
    else
      super
    end
  end
end

答案 1 :(得分:1)

不确定这是最好的解决方案,但我会这样做:

class TaxArray < Array

  ...

  def select
    self.class.new(super)
  end

end

答案 2 :(得分:0)

这是因为你的方法返回普通的数组对象而没有其他的方法。

您可以让您的方法返回TaxArray对象,如下所示:

class TaxArray < Array
  def for_region(region_code)
    array = self.select{|tax|tax[:region_code].include?(region_code)}
    TaxArray.new(array)
  end

  def for_type(type)
    array = self.select{|tax|tax[:type].include?(type)}
    TaxArray.new(array)
  end
end

答案 3 :(得分:0)

我认为如果不是TaxArray < Array您实现了一个简单的Tax类,而不是像以下

那么问题会更简单
class Tax

  attr_reader :name, :rate, :type, :region_code

  def initialize(name, rate, type, region_code)
    @name = name
    @rate = rate
    @type = type
    @region_code = region_code
  end

  def for_region(r_code)
    region_code.include?(r_code)
  end

  def for_type(t)
    type.include?(t)
  end
end

并对Tax个实例的(通常)数组执行所需的操作。

答案 4 :(得分:0)

您可以使用Module#refine(v2.1):

module M
  refine Array do
    def for_region(region_code)
      select { |tax|tax[:region_code].include?(region_code) }
    end

    def for_type(type)
      select { |tax|tax[:type].include?(type) }
    end
  end
end

using M

taxes = []

现在添加一些数据(我已经删除了带有键:rate的哈希元素以略微简化):

taxes << {name: "Minnesota Sales",    type: [:food,:liquor], region_code: 'MN'}
taxes << {name: "Downtown Liquor",    type: [:liquor],       region_code: 'MN'}
taxes << {name: "Downtown Restaurant",type: [:food],         region_code: 'MN'}
# fictitious WI rates 
taxes << {name: "Wisconsin Sales",    type: [:food,:liquor], region_code: 'WI'}
taxes << {name: "Wisconsin Food",     type: [:food],         region_code: 'WI'}
taxes << {name: "Wisconsin Liquor",   type: [:liquor],       region_code: 'WI'}

p taxes.for_type(:liquor)
  [{:name=>"Minnesota Sales",  :type=>[:food, :liquor], :region_code=>"MN"},
   {:name=>"Downtown Liquor",  :type=>[:liquor],        :region_code=>"MN"},
   {:name=>"Wisconsin Sales",  :type=>[:food, :liquor], :region_code=>"WI"},
   {:name=>"Wisconsin Liquor", :type=>[:liquor],        :region_code=>"WI"}]

p taxes.for_region('WI')
  [{:name=>"Wisconsin Sales",  :type=>[:food, :liquor], :region_code=>"WI"},
   {:name=>"Wisconsin Food",   :type=>[:food],          :region_code=>"WI"},
   {:name=>"Wisconsin Liquor", :type=>[:liquor],        :region_code=>"WI"}]

p taxes.for_type(:liquor).for_region('WI')
  [{:name=>"Wisconsin Sales",  :type=>[:food, :liquor], :region_code=>"WI"},
   {:name=>"Wisconsin Liquor", :type=>[:liquor],        :region_code=>"WI"}]

使用refine的一个限制是:"You may only activate refinements at top-level...",这可以防止在Pry和IRB中进行明显的测试。

或者,在某处我读到这应该有效: - ):

def for_region(taxes, region_code)
  taxes.select{|tax|tax[:region_code].include?(region_code)}
end

def for_type(taxes, type)
  taxes.select{|tax|tax[:type].include?(type)}
end

for_region(for_type(taxes, :liquor), 'WI')