# After\_save filter on the join model cannot create new objects

**URL:** <https://www.ruby-forum.com/t/after-save-filter-on-the-join-model-cannot-create-new-objects/140176>\
**Category:** Rails\
**Created:** [June 11, 2008, 4:08pm UTC](https://www.ruby-forum.com/t/after-save-filter-on-the-join-model-cannot-create-new-objects/140176 "2008-06-11T16:08:50Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![Morgan\_G](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/57b2e6/32.png) [@Morgan\_G](https://www.ruby-forum.com/u/Morgan_G)\
**Post date:** [June 11, 2008, 4:08pm UTC](https://www.ruby-forum.com/t/after-save-filter-on-the-join-model-cannot-create-new-objects/140176/1 "2008-06-11T16:08:50Z")

</div>

Establishing a friend relationship with another user with a join model  
(user\_id, friend\_id). Normally this would result in one-way  
relationships  
(Jack is a friend of Jill but Jill is not a friend of Jack). To get  
around  
this I want to have an inverted entry in the join model table (not the  
nicest approach but I see no reason why it cannot work) so I set up my  
models like this:

class User \< ActiveRecord::Base  
has\_many :user\_friends, :dependent =\> :destroy  
has\_many :friends, :through =\> :user\_friends, :order =\> :first\_name  
end

class UserFriend \< ActiveRecord::Base  
attr\_accessor :final

belongs\_to :user  
belongs\_to :friend, :foreign\_key =\> :friend\_id, :class\_name =\> “User”  
validates\_presence\_of :user, :friend

# Make the relationship bidirectional

after\_save :duplicate\_inverted  
after\_destroy :destroy\_inverted

protected

```
# Create an inverted version of this record (unless one already 

```

exists)  
def duplicate\_inverted  
return true if self.final  
unless UserFriend.exists? :user\_id =\> friend\_id, :friend\_id =\>  
user\_id  
self.class.create :user\_id =\> friend\_id, :friend\_id =\> user\_id,  
:final =\> true  
end  
end

```
# Remove the inverted version of this object
def destroy_inverted
  if UserFriend.exists? :user_id => friend_id, :friend_id => user_id
    self.class.destroy :user_id => friend_id, :friend_id => user_id
  end
end

```

end

The UserFriend#final method exists so the iteration only happens once.

What happens, though, when I run this:

u1 = User.find 1  
u2 = User.find 2  
u1.friends.clear  
u1.friends \<\< u2

User Columns (0.001914) SHOW FIELDS FROM `users`  
User Load (0.000986) SELECT \* FROM `users` WHERE (`users`.`id` = 1)  
User Load (0.000980) SELECT \* FROM `users` WHERE (`users`.`id` = 2)  
User Load (0.001556) SELECT `users`.\* FROM `users` INNER JOIN  
user\_friends ON users.id = user\_friends.friend\_id WHERE  
((`user_friends`.user\_id = 1)) ORDER BY first\_name  
SQL (0.000288) BEGIN  
UserFriend Columns (0.000853) SHOW FIELDS FROM `user_friends`  
User Load (0.000990) SELECT \* FROM `users` WHERE (`users`.`id` = 1)  
User Load (0.000960) SELECT \* FROM `users` WHERE (`users`.`id` = 2)  
\*\*\* UserFriend Create (0.000443) INSERT INTO `user_friends`  
(`updated_at`, `user_id`, `friend_id`, `created_at`) VALUES(‘2008-06-11  
15:04:46’, 1, 2, ‘2008-06-11 15:04:46’)  
UserFriend Exists (0.000477) SELECT `user_friends`.id FROM  
`user_friends` WHERE (`user_friends`.`user_id` = 2 AND  
`user_friends`.`friend_id` = 1) LIMIT 1  
User Load (0.000993) SELECT \* FROM `users` WHERE (`users`.`id` = 1)  
User Load (0.000967) SELECT \* FROM `users` WHERE (`users`.`id` = 2)  
\*\*\* UserFriend Create (0.000396) INSERT INTO `user_friends`  
(`updated_at`, `user_id`, `friend_id`, `created_at`) VALUES(‘2008-06-11  
15:04:46’, 1, 2, ‘2008-06-11 15:04:46’)  
SQL (0.020969) COMMIT

I’ve put \*\*\* in front of the two important rows. The first creates the  
requested object, the second creates the inverted one. However, the  
second  
insert has user\_id and friend\_id set to the same values as the first  
insertion?

I tested this by replacing  
unless UserFriend.exists? :user\_id =\> friend\_id, :friend\_id =\>  
user\_id  
self.class.create :user\_id =\> friend\_id, :friend\_id =\> user\_id,  
:final =\> true  
end

with  
unless UserFriend.exists? :user\_id =\> friend\_id, :friend\_id =\>  
user\_id  
self.class.create :user\_id =\> 5, :friend\_id =\> 5, :final =\> true  
end

and the results were exactly the same. This leads me to believe there is  
some sort of with\_scope or something going one which is appending those  
values to my create call but I don’t know how to find out whether or not  
this is the case.

Can anyone shine some light on this behaviour?

## Cheers, Morgan G…

* * *

## Morgan G. - Just Landed General Tel: +34 91 590 2611 removed\_email\_address@domain.invalid

## [http://www.justlanded.com](http://www.justlanded.com) - Helping people abroad! 30 countries, in up to 8 languages, more to come…

---

<div class="post-metadata">

**Author:** ![Morgan\_G](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/57b2e6/32.png) [@Morgan\_G](https://www.ruby-forum.com/u/Morgan_G)\
**Post date:** [June 11, 2008, 4:40pm UTC](https://www.ruby-forum.com/t/after-save-filter-on-the-join-model-cannot-create-new-objects/140176/2 "2008-06-11T16:40:49Z")

</div>

2008/6/11 Frederick C. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid):

> > user\_id, :final =\> true  
> > there is some sort of with\_scope or something going one which is  
> > appending those values to my create call but I don’t know how to  
> > find out whether or not this is the case.
> 
> That sounds eminently plausible. with\_exclusive\_scope might help you  
> out.

Thanks for that.

I’ve been trying a few variants and running into issues. It seems to me  
this

```
  UserFriend.send :with_exclusive_scope, { :user_id => friend_id,

```

:friend\_id =\> user\_id } do  
UserFriend.create :final =\> true unless UserFriend.exists?  
end

should work but it leads to

ArgumentError: Unknown key(s): user\_id, friend\_id

I can see why they decided to make with\_scope a protected method. It’s a  
real pain in the butt.

I’ll keep trying but if you can see can what I’m doing wrong I’d love to  
hear it.

Cheers.

> > Morgan G. - Just Landed
> 
> >

## –

## Morgan G. - Just Landed General Tel: +34 91 590 2611 removed\_email\_address@domain.invalid

## [http://www.justlanded.com](http://www.justlanded.com) - Helping people abroad! 30 countries, in up to 8 languages, more to come…

---

<div class="post-metadata">

**Author:** ![Morgan\_G](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/57b2e6/32.png) [@Morgan\_G](https://www.ruby-forum.com/u/Morgan_G)\
**Post date:** [June 11, 2008, 4:42pm UTC](https://www.ruby-forum.com/t/after-save-filter-on-the-join-model-cannot-create-new-objects/140176/3 "2008-06-11T16:42:34Z")

</div>

> should work but it leads to
> 
> ArgumentError: Unknown key(s): user\_id, friend\_id
> 
> I can see why they decided to make with\_scope a protected method. It’s a  
> real pain in the butt.
> 
> I’ll keep trying but if you can see can what I’m doing wrong I’d love to  
> hear it.

Sorry, the error that specific version leads to is

TypeError: can’t dup Fixnum

Which is when I replaced friend.id with friend\_id and got the unknown  
keys  
error.

Cheers.

## –

## Morgan G. - Just Landed General Tel: +34 91 590 2611 removed\_email\_address@domain.invalid

## [http://www.justlanded.com](http://www.justlanded.com) - Helping people abroad! 30 countries, in up to 8 languages, more to come…

---

<div class="post-metadata">

**Author:** ![Morgan\_G](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/57b2e6/32.png) [@Morgan\_G](https://www.ruby-forum.com/u/Morgan_G)\
**Post date:** [June 11, 2008, 4:46pm UTC](https://www.ruby-forum.com/t/after-save-filter-on-the-join-model-cannot-create-new-objects/140176/4 "2008-06-11T16:46:06Z")

</div>

2008/6/11 Morgan G. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid):

> > Thanks for that.
> 
> TypeError: can’t dup Fixnum
> 
> Which is when I replaced friend.id with friend\_id and got the unknown keys  
> error.

I’m on fire today. Just realised that with\_scope takes a hash of actions  
and  
params, not params directly.

I’ll check this out now.

Cheers.

## –

## Morgan G. - Just Landed General Tel: +34 91 590 2611 removed\_email\_address@domain.invalid

## [http://www.justlanded.com](http://www.justlanded.com) - Helping people abroad! 30 countries, in up to 8 languages, more to come…

---

<div class="post-metadata">

**Author:** ![Morgan\_G](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/57b2e6/32.png) [@Morgan\_G](https://www.ruby-forum.com/u/Morgan_G)\
**Post date:** [June 11, 2008, 4:12pm UTC](https://www.ruby-forum.com/t/after-save-filter-on-the-join-model-cannot-create-new-objects/140176/5 "2008-06-11T16:12:25Z")

</div>

On 11 Jun 2008, at 15:07, Morgan G. wrote:

> ```
> unless UserFriend.exists? :user_id => friend_id, :friend_id =>  
> 
> ```
> 
> user\_id  
> self.class.create :user\_id =\> 5, :friend\_id =\> 5, :final =\>  
> true  
> end
> 
> and the results were exactly the same. This leads me to believe  
> there is some sort of with\_scope or something going one which is  
> appending those values to my create call but I don’t know how to  
> find out whether or not this is the case.

That sounds eminently plausible. with\_exclusive\_scope might help you  
out.

Fred

---

<div class="post-metadata">

**Author:** ![Morgan\_G](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/57b2e6/32.png) [@Morgan\_G](https://www.ruby-forum.com/u/Morgan_G)\
**Post date:** [June 11, 2008, 4:57pm UTC](https://www.ruby-forum.com/t/after-save-filter-on-the-join-model-cannot-create-new-objects/140176/6 "2008-06-11T16:57:53Z")

</div>

2008/6/11 Morgan G. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid):

> > > > That sounds eminently plausible. with\_exclusive\_scope might help you  
> > > > UserFriend.create :final =\> true unless UserFriend.exists?  
> > > > hear it.  
> > > > I’m on fire today. Just realised that with\_scope takes a hash of actions  
> > > > and params, not params directly.
> 
> I’ll check this out now.

Bang on. The final working bi-directional friendship version:

class UserFriend \< ActiveRecord::Base  
attr\_accessor :final

belongs\_to :user  
belongs\_to :friend, :foreign\_key =\> :friend\_id, :class\_name =\> “User”  
validates\_presence\_of :user, :friend

# Make the relationship bidirectional

after\_save :duplicate\_inverted  
after\_destroy :destroy\_inverted

protected

```
# Create an inverted version of this record (unless one already 

```

exists)  
def duplicate\_inverted  
return true if self.final  
UserFriend.send(:with\_exclusive\_scope, :find =\> {}, :create =\> {})  
do  
unless UserFriend.exists? :user\_id =\> friend\_id, :friend\_id =\>  
user\_id  
UserFriend.create :final =\> true, :user\_id =\> friend\_id,  
:friend\_id =\> user\_id  
end  
end  
end

```
# Remove the inverted version of this object
def destroy_inverted
  if UserFriend.exists? :user_id => self.friend_id, :friend_id =>

```

self.user\_id  
UserFriend.destroy\_all :user\_id =\> self.friend\_id, :friend\_id =\>  
self.user\_id  
end  
end

end

Thanks for your help.

## –

## Morgan G. - Just Landed General Tel: +34 91 590 2611 removed\_email\_address@domain.invalid

## [http://www.justlanded.com](http://www.justlanded.com) - Helping people abroad! 30 countries, in up to 8 languages, more to come…

---

<div class="post-metadata">

**Author:** ![Morgan\_G](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/57b2e6/32.png) [@Morgan\_G](https://www.ruby-forum.com/u/Morgan_G)\
**Post date:** [June 11, 2008, 5:09pm UTC](https://www.ruby-forum.com/t/after-save-filter-on-the-join-model-cannot-create-new-objects/140176/7 "2008-06-11T17:09:01Z")

</div>

2008/6/11 Frederick C. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid):

> > unknown keys error.  
> > end

You’re right. Works fine.

Cheers.

## –

## Morgan G. - Just Landed General Tel: +34 91 590 2611 removed\_email\_address@domain.invalid

## [http://www.justlanded.com](http://www.justlanded.com) - Helping people abroad! 30 countries, in up to 8 languages, more to come…

---

<div class="post-metadata">

**Author:** ![Morgan\_G](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/57b2e6/32.png) [@Morgan\_G](https://www.ruby-forum.com/u/Morgan_G)\
**Post date:** [June 11, 2008, 5:00pm UTC](https://www.ruby-forum.com/t/after-save-filter-on-the-join-model-cannot-create-new-objects/140176/8 "2008-06-11T17:00:49Z")

</div>

On 11 Jun 2008, at 15:44, Morgan G. wrote:

> actions and params, not params directly.
> 
> I’ll check this out now.

conceivably you could just do  
with\_exclusive\_scope do  
…  
end

Fred
