From: "David A. Black" Date: 2005-11-25T12:50:46+09:00 Subject: Re: syntax error when defining index setter []= Hi -- On Thu, 24 Nov 2005, jchris@gmail.com wrote: > Hi, > I'm trying to write some convenience methods for one of my classes. My > class has an array of objects, each of which points to another object > that I am interested in. (I'm describing an Active Record has_many > relationship... you can skip ahead if you like.) According to this > setup I can write > > list.middlemen[1].item > > to access the first item in the list. Iterating through my items would > be accomplished by > > list.middlemen.collect {|middleman| middleman.item}.each > > and setting the first item in the list to a different item is > complicated: > > list.middlemen[1]= Middleman.new {:item => newitem} > > > That's it for the setup. The convenience methods I want to write would > accomplish those same three tasks but written like this: > > list.items[1] > > list.items.each > > list.items[1]= new_item > > Defining the first two of these conveniences is simple, within the List > class, I just use a wrapper called 'items' that returns an array of my > items. > > def items > middlemen.collect {|middleman| middleman.item} > end > > [[The Juicy Bit]] > > But the last one is nasty. When I try to define this: > > def items[]= (index, newitem) > ...doesn't matter what's here... > end > > I get a syntax error on the method name at the brackets. I could just > define []= on my List class, as I don't have anything else conflicting > there, but it seems messy to talk about items sometimes and not always. > And I definitely want to be able to return an array by calling the > List.items instance method. > > What I really want is for the items method to act like an attr_accessor > on the array generated by > > middlemen.collect {|middleman| middleman.item} > > and for assignments made to the generated array to effect the objects > that the middlemen point to with their 'item' method. (with my current > setup, alteration made to properties of items stick, but > assignment/replacing of whole items does not). > > eg: > > list.items[1].title #=> 'an original item' > > list.items[1].title= 'changed title' > > gives: > > list.items[1].title #=> 'changed title' > > but then running: > > new_item = Item.create {:title => 'a new item'} > > list.items[1]= new_item > > still results in: > > list.items[1].title #=> 'changed title' > > I know this is long, and the problem is not completely defined, but > this seems like a common enough problem, and I've had little luck > searching the web for it. I imagine the same problem will hold for the > << method and others. I don't know if this is an exact fit, and it may not scale if you have lots of such things... but see if it's at all helpful. It's a variant of Pit's idea of adding a singleton []= method to the items array. class List attr_reader :middlemen Item = Struct.new(:title) Middleman = Struct.new(:item) def initialize @middlemen = [] # Populate @middlemen with objects 5.times do |n| @middlemen << Middleman.new(Item.new("Title #{n}")) end # Create an @items array and give it access, via []=, to # @middlemen @items = [] m = @middlemen m_lambda = lambda {|index,item| m[index] = Middleman.new(item) } (class << @items; self; end).class_eval do define_method(:[]=, &m_lambda) end end # Don't create a new array. Repopulate the old one (@items), # because otherwise you'll lose its special []= method. def items @items.replace(middlemen.collect {|m| m.item }) end end list = List.new p list.items[1] list.items[1] = List::Item.new("Title 100") p list.items[1].title list.items[1].title = "Title 200" p list.items[1].title __END__ David -- David A. Black dblack@wobblini.net