# Syntax error when defining index setter \[\]=

**URL:** <https://www.ruby-forum.com/t/syntax-error-when-defining-index-setter/48767>\
**Category:** Ruby\
**Created:** [November 24, 2005, 9:30am UTC](https://www.ruby-forum.com/t/syntax-error-when-defining-index-setter/48767 "2005-11-24T09:30:19Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![jchris](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/j/85e7bf/32.png) [@jchris](https://www.ruby-forum.com/u/jchris)\
**Post date:** [November 24, 2005, 9:30am UTC](https://www.ruby-forum.com/t/syntax-error-when-defining-index-setter/48767/1 "2005-11-24T09:30:19Z")

</div>

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.

Perhaps I should investigate another direction?

Thanks for any pointers!

Chris

---

<div class="post-metadata">

**Author:** ![jchris](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/j/85e7bf/32.png) [@jchris](https://www.ruby-forum.com/u/jchris)\
**Post date:** [November 24, 2005, 12:39pm UTC](https://www.ruby-forum.com/t/syntax-error-when-defining-index-setter/48767/2 "2005-11-24T12:39:54Z")

</div>

> 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

class T  
def initialize  
@items = []  
end  
def items  
@items.dup  
end  
def []=(idx, item)  
@items[idx] = item  
end  
def   
@items[idx]  
end  
end

t = T.new  
t[0] = ‘original1’  
t[1] = ‘original2’

puts 'before: '+t[0]  
t[0] = ‘modified’  
puts ‘after:’  
t.items.each {|elem| puts elem}

Regards,  
Yuri Kozlov

---

<div class="post-metadata">

**Author:** ![jchris](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/j/85e7bf/32.png) [@jchris](https://www.ruby-forum.com/u/jchris)\
**Post date:** [November 24, 2005, 7:28pm UTC](https://www.ruby-forum.com/t/syntax-error-when-defining-index-setter/48767/3 "2005-11-24T19:28:10Z")

</div>

Thanks Yuri,  
unfortunately my problems is not as simple. If all I needed was an  
array I could write

class T  
attr\_accessor :items  
def initialize  
@items = []  
end  
end

but what I need is something that acts like an array from outside of  
the class, but handles transformations to and from a more complex data  
structure “behind the scenes”, ie:

class List \< ActiveRecord::Base  
has\_many :middlemen

def items  
middlemen.collect {|middleman| middleman.item}  
end

def items=(new\_items)  
new\_middlemen = []  
new\_items.each do |new\_item|  
new\_middlemen \<\< Middleman.create :item =\> new\_item  
end  
middlemen = new\_middlemen  
end

def items[]=(index, new\_item) #### this line gives a syntax error  
new\_middleman = Middleman.create :item =\> new\_item  
middlemen[index] = new\_middleman  
end

end

I could take this to a Rails forum, but it is more of a Ruby question,  
as matter of syntax are arising. Running up against this error makes me  
think I will come up against more when trying to define items\<\< etc.

Perhaps better would be to create a dummy object (Array) and have it  
run an observer pattern on itself (or something like it). When the  
array is changed, it would push those changes through to the middlemen.  
Even this is not as clean as I’d like it to be, but maybe there is an  
easier way that I’m just not seeing.

Thanks  
Chris

---

<div class="post-metadata">

**Author:** ![jchris](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/j/85e7bf/32.png) [@jchris](https://www.ruby-forum.com/u/jchris)\
**Post date:** [November 24, 2005, 8:12pm UTC](https://www.ruby-forum.com/t/syntax-error-when-defining-index-setter/48767/4 "2005-11-24T20:12:29Z")

</div>

On Nov 24, 2005, at 3:27 AM, removed\_email\_address@domain.invalid wrote:

> But the last one is nasty. When I try to define this:
> 
> def items[]= (index, newitem)  
> …doesn’t matter what’s here…  
> end

You can’t do this in Ruby because unlike the assignment  
method syntax, you can’t prefix []= with an identifier:

def whatever=(arg); end # OK  
def whatever[]=(index, arg); end # syntax error

The main problem is that it would create an ambiguous  
parsing situation for x[i] = v

1. self.x.[]=(i, v)
2. self.x[]=(i, v)

and similarly for the lookup x[i]. It isn’t simple because  
the parser can’t know ahead of time what ‘x’ is referencing  
and so can’t know which interpretation is the sensible one.

It would be a useful construct because it would allow a class  
to manage the interface to container objects without having  
to create a proxy for the containers. Right now you have to  
give up the []/[]= syntax and use something like

```
set_item(i, v)
get_item(i)

```

or you have to construct a proxy class for the container  
or you have to expose the entire container to the client.

I suggested a couple weeks ago an alternate syntax such as:

```
def whatever@[](index); end
def whatever@[]=(index, var); end

```

With this syntax you can write:

```
item = obj.item@[5]

obj.item@[5] = new_item

```

and the parser would know that item@[] and item@[]= were  
methods based on the syntax. It might be possible to to  
discard the []'s but I wasn’t sure if that would create  
some ambiguity with instance variables. I just didn’t  
explore the idea that deeply.

Gary W.

---

<div class="post-metadata">

**Author:** ![jchris](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/j/85e7bf/32.png) [@jchris](https://www.ruby-forum.com/u/jchris)\
**Post date:** [November 24, 2005, 8:16pm UTC](https://www.ruby-forum.com/t/syntax-error-when-defining-index-setter/48767/5 "2005-11-24T20:16:31Z")

</div>

removed\_email\_address@domain.invalid schrieb:

> ```
> middlemen[index] = new_middleman
> 
> ```
> 
> end
> 
> end

If you really want to have the syntax

list.items[5] = “new”

you need to define the “[]=” method on the return value of the “items”  
method, for example (untested)

def items  
result = middlemen.collect {|middleman| middleman.item}  
def result.[]=(index, new\_item)  
new\_middleman = Middleman.create :item =\> new\_item  
middlemen[index] = new\_middleman  
end  
end

Regards,  
Pit

---

<div class="post-metadata">

**Author:** ![jchris](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/j/85e7bf/32.png) [@jchris](https://www.ruby-forum.com/u/jchris)\
**Post date:** [November 24, 2005, 8:36pm UTC](https://www.ruby-forum.com/t/syntax-error-when-defining-index-setter/48767/6 "2005-11-24T20:36:40Z")

</div>

Pit,

Thanks for the glimmer of hope. The only problem I’m finding at this  
point is that “middlemen” is out of scope when result.[]= is actually  
run. I tried saving self to a variable (‘saved\_self’) and then using  
saved\_self.middlemen[index] to get in there, but saved\_self is just out  
of scope then.

NameError: undefined local variable or method `saved\_self’ for  
#Array:0x248a294

Scope. hmmm, that seems like it should be a Rubyish problem to solve.  
Probably with blocks or procs…

Thanks,  
Chris

---

<div class="post-metadata">

**Author:** ![jchris](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/j/85e7bf/32.png) [@jchris](https://www.ruby-forum.com/u/jchris)\
**Post date:** [November 25, 2005, 4:52am UTC](https://www.ruby-forum.com/t/syntax-error-when-defining-index-setter/48767/7 "2005-11-25T04:52:23Z")

</div>

Hi –

On Thu, 24 Nov 2005, removed\_email\_address@domain.invalid wrote:

> be accomplished by  
> accomplish those same three tasks but written like this:
> 
> end  
> middlemen.collect {|middleman| middleman.item}  
> list.items[1].title= ‘changed title’
> 
> 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

---

<div class="post-metadata">

**Author:** ![jchris](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/j/85e7bf/32.png) [@jchris](https://www.ruby-forum.com/u/jchris)\
**Post date:** [November 25, 2005, 8:41am UTC](https://www.ruby-forum.com/t/syntax-error-when-defining-index-setter/48767/8 "2005-11-25T08:41:57Z")

</div>

removed\_email\_address@domain.invalid schrieb:

> Scope. hmmm, that seems like it should be a Rubyish problem to solve.  
> Probably with blocks or procs…

Yes, I noticed it after sending the code. You’d need define\_method with  
a block, which would have access to the local variables. But see David’s  
code for a working implementation. (BTW: nice idea to reuse the array  
with the singleton method!)

Regards,  
Pit
