Search code examples
rubysetter

Ruby: How to add validation for a class field's << method


I understand how to implement a (validating) setter (def item=), but how do I intercept the << operation on a field?

class Bla 
  attr_reader :item

  def initialize
    @item = []
  end

  # only called for =, +=, -= operations (not <<)
  def item=(value)
    puts "Changing value to #{value}"
    # pretend that there is a conditional here
    @item = value
  end

  # This is wrong:
  #def item<<(value)
  #  puts "adding value #{value}"
  #  @item << value
  #end

end

b = Bla.new
b.item = ['one']  # works
b.item += ['one'] # works
b.item << 'two'   # bypasses my setter

I've tried def item<<(value), that doesn't seem to work.


Solution

  • When you call b.item << 'two', you are calling the << method on item directly. So you have a few options here:

    1. Implement << directly on your Bla class, then use b << 'two':

      # in class Bla
      def <<(value)
        # do validation here
        @item << value
      end
      
    2. Use some other, nicer-named wrapper method name like add_item:

      # in class Bla
      def add_item(value)
        # do validation here
        @item << value
      end
      
    3. Use a special array class for @item which has a custom definition for <<:

      class MyArray < Array
        def <<(item)
          # run validation here
          super
        end
      end
      
      # in Bla class
      def initialize
        @item = MyArray.new
      end
      

    I would probably go with option 2, it's the most simple and readable.