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
还是有其他方法?
答案 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')