Blog coding and discussion of coding about JavaScript, PHP, CGI, general web building etc.

Thursday, March 17, 2016

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

Paulo Henrique for Ruby method chaining error

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

Related Posts:

0 comments:

Post a Comment

Popular Posts

Fun Page

Powered by Blogger.