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.
When you call b.item << 'two', you are calling the << method on item directly. So you have a few options here:
Implement << directly on your Bla class, then use b << 'two':
# in class Bla
def <<(value)
# do validation here
@item << value
end
Use some other, nicer-named wrapper method name like add_item:
# in class Bla
def add_item(value)
# do validation here
@item << value
end
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.
Here is an alternate suggestion:
class Bla
# DO NOT DEFINE A READER:
# attr_reader :item
def initialize
@items = []
end
def set_items(new_items)
puts "Changing value to #{new_items}"
# pretend that there is a conditional here
@items = new_items
end
def remove_item(item)
# pretend that there is a conditional here
@items -= items
end
def add_item(item)
# pretend that there is a conditional here
@items += items
end
end
By not exposing the @items object directly, you remain in full control of the interface for how the variable is manipulated.
(Unless a caller does something really hacky, like bla.instance_variable_get('@items')!!)
class Pit
def <<(dirt)
puts 'shovel ' + dirt
end
end
Pit.new << 'sandy loam'
# => shovel sandy loam