# Breakdowns in has\_many abstraction

**URL:** <https://www.ruby-forum.com/t/breakdowns-in-has-many-abstraction/95286>\
**Category:** Rails\
**Created:** [May 26, 2006, 7:08pm UTC](https://www.ruby-forum.com/t/breakdowns-in-has-many-abstraction/95286 "2006-05-26T19:08:53Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![jimmyether](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/j/e9a140/32.png) [@jimmyether](https://www.ruby-forum.com/u/jimmyether)\
**Post date:** [May 26, 2006, 7:08pm UTC](https://www.ruby-forum.com/t/breakdowns-in-has-many-abstraction/95286/1 "2006-05-26T19:08:53Z")

</div>

I discovered an interesting aspect of has\_many behavior that I’m  
struggling to work around. I’m not sure if I’m doing something wrong,  
or if it’s a legitimate bug, or if it’s an inherent part of Rails that  
I just have to learn to deal with.

It boils down to these two problems:

- changes in collection objects (i.e. models that belong\_to a  
container model) don’t propagate to the container object’s collection  
view until the collection objects are saved and the association  
reloaded
- the collection= method doesn’t do anything when you give it updated  
objects

I encountered these problems when I was updating both the container  
object and some or all of the collection objects at the same time, and  
I wanted to use the container.collection before everything is saved  
(i.e. for collective validation).

The first problem comes from the fact that  
container.collection.find(id) returns a completely different Ruby  
object than the objects contained in the container.collection array.  
When you modify the object returned by find, those changes don’t show  
up in the container.collection array until the modified objects are  
saved and reloaded from the database. How could these be better  
synced up?

I tried using container.collection= to overwrite the ruby objects in  
the collection with those that were generated by find, but that  
doesn’t work, because of the second problem. The collection= method  
ignores objects that match the id of an object already in the  
collection! So even if the objects you pass in to  
container.collection= are completely different except for their ids,  
the collection remains unchanged. So that’s a weird behavior, too.  
Why is it like that?

Here’s my simplified test case, part of a new Web 2.0 app, Rain’d.  
🙂 The umbrellas are all named, and we want to make sure that each  
person has uniquely-named umbrellas.

class Person \< ActiveRecord::Base  
has\_many :umbrellas

```
def validate
  errors.add("umbrellas", "must be uniquely named") unless

```

no\_duplicate\_names?  
end

```
def no_duplicate_names?
  names = umbrellas.collect {|u| u.name}
  names.length == names.uniq.length
end

```

end

class Umbrella \< ActiveRecord::Base  
belongs\_to :person  
end

In people\_controller.rb (note the comment in the middle):

def update  
@person = Person.find(params[:person][:id], :include=\>:umbrellas)  
@person.attributes = params[:person]  
@umbrellas = params[:umbrellas].values.collect do |u|  
umbrella = u[:id] ? @person.umbrellas.find(u[:id]):  
@person.umbrellas.build(u)  
umbrella.attributes = u  
umbrella  
end  
begin  
Person.transaction do  
@umbrellas.each {|u| u.save!}

```
    # in order for the validation to work, you have to save the

```

umbrellas first,  
# then call this reloading trick here:  
@person.umbrellas(true)

```
    @person.save! # now this will validate
  end
  flash[:notice] = 'Person was successfully updated.'
  redirect_to :action => 'show', :id=>@person.id
rescue => exception
  flash[:notice] = exception.to_s
  render_scaffold('edit')
end

```

end

Here’s a transcript showing the second problem, from a breakpoint I  
set in the update method. It illustrates some of the weirdness that’s  
going on here.

> @umbrellas = @person.umbrellas  
> =\> [#\<Umbrella:0x38d7218 @attributes={“name”=\>“U. Horatio”, “id”=\>“1”,  
> “person\_i  
> d”=\>“1”}\>, #\<Umbrella:0x38d5ca0 @attributes={“name”=\>“U. Glavellus”,  
> “id”=\>“2”,  
> “person\_id”=\>“1”}\>]

> @umbrellas.each{|u| u.name = u.id.to\_s}  
> =\> [#\<Umbrella:0x38d7218 @attributes={“name”=\>“1”, “id”=\>“1”,  
> “person\_id”=\>“1”}\>  
> , #\<Umbrella:0x38d5ca0 @attributes={“name”=\>“2”, “id”=\>“2”,  
> “person\_id”=\>“1”}\>]

> @person.umbrellas  
> =\> [#\<Umbrella:0x38d7218 @attributes={“name”=\>“1”, “id”=\>“1”,  
> “person\_id”=\>“1”}\>  
> , #\<Umbrella:0x38d5ca0 @attributes={“name”=\>“2”, “id”=\>“2”,  
> “person\_id”=\>“1”}\>]

> @umbrellas = [@person.umbrellas.find(1), @person.umbrellas.find(2)]  
> =\> [#\<Umbrella:0x389b578 @attributes={“name”=\>“U. Horatio”, “id”=\>“1”,  
> “person\_i  
> d”=\>“1”}\>, #\<Umbrella:0x38977b0 @attributes={“name”=\>“U. Glavellus”,  
> “id”=\>“2”,  
> “person\_id”=\>“1”}\>]

> @person.umbrellas  
> =\> [#\<Umbrella:0x38d7218 @attributes={“name”=\>“1”, “id”=\>“1”,  
> “person\_id”=\>“1”}\>  
> , #\<Umbrella:0x38d5ca0 @attributes={“name”=\>“2”, “id”=\>“2”,  
> “person\_id”=\>“1”}\>]

> @person.umbrellas = @umbrellas  
> =\> [#\<Umbrella:0x389b578 @attributes={“name”=\>“U. Horatio”, “id”=\>“1”,  
> “person\_i  
> d”=\>“1”}\>, #\<Umbrella:0x38977b0 @attributes={“name”=\>“U. Glavellus”,  
> “id”=\>“2”,  
> “person\_id”=\>“1”}\>]

> @person.umbrellas  
> =\> [#\<Umbrella:0x38d7218 @attributes={“name”=\>“1”, “id”=\>“1”,  
> “person\_id”=\>“1”}\>  
> , #\<Umbrella:0x38d5ca0 @attributes={“name”=\>“2”, “id”=\>“2”,  
> “person\_id”=\>“1”}\>]

Sorry for all the text, and thanks if you’ve gotten this far. It  
seems like this is a relatively simple thing to want to accomplish,  
and I’m surprised that in doing so I’ve run into a bunch of Rails  
weirdnesses. I can work around them fine, I’m just wondering if  
there’s a Better Way™.

-RYaN

---

<div class="post-metadata">

**Author:** ![jimmyether](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/j/e9a140/32.png) [@jimmyether](https://www.ruby-forum.com/u/jimmyether)\
**Post date:** [May 27, 2006, 8:27pm UTC](https://www.ruby-forum.com/t/breakdowns-in-has-many-abstraction/95286/2 "2006-05-27T20:27:49Z")

</div>

So one way of getting around the first problem is to return the actual  
object from within the collection.

@umbrellas = params[:umbrellas].values.collect do |u|  
umbrella = u[:id] ? @person.umbrellas.detect{|u| u.id ==  
u[:id].to\_i}: @person.umbrellas.build(u)  
umbrella.attributes = u  
umbrella  
end

This means that you don’t have to reload the umbrellas collection in  
the transaction anymore. However, this solution seems inelegant, and  
slow when you have a lot of umbrellas.

Is there no better way?

-RYaN

---

<div class="post-metadata">

**Author:** ![jimmyether](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/j/e9a140/32.png) [@jimmyether](https://www.ruby-forum.com/u/jimmyether)\
**Post date:** [May 28, 2006, 12:45am UTC](https://www.ruby-forum.com/t/breakdowns-in-has-many-abstraction/95286/3 "2006-05-28T00:45:14Z")

</div>

Hello Ryan,

The no\_duplicate\_names? method pulls umbrellas from the database with  
this line:  
names = umbrellas.collect {|u| u.name}

Try switching that line to use the instance variable @umbrellas such  
that:  
names = @umbrellas.collect {|u| u.name}

---

<div class="post-metadata">

**Author:** ![jimmyether](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/j/e9a140/32.png) [@jimmyether](https://www.ruby-forum.com/u/jimmyether)\
**Post date:** [May 28, 2006, 1:40am UTC](https://www.ruby-forum.com/t/breakdowns-in-has-many-abstraction/95286/4 "2006-05-28T01:40:02Z")

</div>

Huh, I didn’t think of that. But it doesn’t seem to make a  
difference. I think that the umbrellas method uses the @umbrellas  
instance variable if it exists, and otherwise creates it from the  
database. So in my case, the @umbrellas array is out-of-sync.

-RYaN
