Ruby method chaining error
Ruby method chaining error
The TaxArray
class inherits from the Array
class:
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
It contains hashes
of taxes:
@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'}
The for_type
method works as expected:
> @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'}]
The for_region
method works as expected:
> @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"}]
However, when I chain the methods together, I get an error:
> @taxes.for_type(:liquor).for_region('WI') NoMethodError: undefined method `for_region' for #
Each method returns an Array
, rather than a TaxArray
.
Should I cast the returned value of each method to a TaxArray
or is there another way?
Answer by Victor Moroz for Ruby method chaining error
Not sure it's the best solution but I would do it this way:
class TaxArray < Array ... def select self.class.new(super) end end
Answer by Zach Kemp for Ruby method chaining error
Generally, I wouldn't recommend subclassing Ruby primitives, for exactly the reasons you're bumping into. It's just as simple to include an array instance variable and operate on that:
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
You could also just define your whole api in one fell swoop using define_method
:
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
And why not go one step further, and forward all unknown methods to the array, with the assumption that this class should respond to anything the array would:
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
Answer by
It is because your methods return plain array objects and it does not have the other method.
You can make your methods return a TaxArray object like so:
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
Answer by xlembouras for Ruby method chaining error
I think the problem would be much simpler if instead of TaxArray < Array
you implemented a simple Tax
class like the following
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
and performed the desired operations to a (usual) array of Tax
instances.
Answer by Cary Swoveland for Ruby method chaining error
You could use Module#refine for this (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 = []
Now add some data (I've removed the hash element with key :rate
to simplify slightly):
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"}]
One of the restrictions in the use of refine
is: "You may only activate refinements at top-level...", which prevents evidently testing in Pry and IRB.
Alternatively, somewhere I read that this should work :-):
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')
Fatal error: Call to a member function getElementsByTagName() on a non-object in D:\XAMPP INSTALLASTION\xampp\htdocs\endunpratama9i\www-stackoverflow-info-proses.php on line 72
0 comments:
Post a Comment