Prevent duplicate inserts with has_many :through?

Does anyone have an efficient/elegant way to prevent duplicate inserts
when using a has_many :through relationship.

So I have something like this:

class User < ActiveRecord::Base
has_many :favorite_teams
has_many :teams, :through => :favorite_teams
end

class FavoriteTeams < ActiveRecord::Base
belongs_to :user
belongs_to :team
end

class Teams < ActiveRecord::Base
has_many :favorite_teams
has_many :users, :through => :favorite_teams
end

If one user creates a team like ‘nationals’ it will create the record in
the teams table and the associated favorite_teams table. That’s fine.

Now, in my case if another user comes along and has the same favorite
team (‘nationals’) I would rather not have the duplicate insert into the
teams table. I just want the association record created in
favorite_teams.

On top of that I need to be able to handle deletion in a particular way.
If one user removes ‘nationals’ as their favorite team I need to know if
any other user has that favorite team, if so then it should remain and
only the association for that user should be deleted. If no other user
has that favorite team then I need both records deleted.

Dave wrote:

Does anyone have an efficient/elegant way to prevent duplicate inserts
when using a has_many :through relationship.

So I have something like this:

class User < ActiveRecord::Base
has_many :favorite_teams
has_many :teams, :through => :favorite_teams
end

class FavoriteTeams < ActiveRecord::Base
belongs_to :user
belongs_to :team
end

class Teams < ActiveRecord::Base
has_many :favorite_teams
has_many :users, :through => :favorite_teams
end

If one user creates a team like ‘nationals’ it will create the record in
the teams table and the associated favorite_teams table. That’s fine.

Now, in my case if another user comes along and has the same favorite
team (‘nationals’) I would rather not have the duplicate insert into the
teams table. I just want the association record created in
favorite_teams.

On top of that I need to be able to handle deletion in a particular way.
If one user removes ‘nationals’ as their favorite team I need to know if
any other user has that favorite team, if so then it should remain and
only the association for that user should be deleted. If no other user
has that favorite team then I need both records deleted.

From your opening sentence I thought you were going to ask how to avoid
duplicate rows in the join model table itself. This is actually a more
interesting problem I think :slight_smile:

The problem you’re trying to solve is what ActiveRecord lifecycle
callbacks are for. Check out chapter 15 of AWDR for a good explanation.

class User < ActiveRecord::Base
has_many :favorite_teams, :dependent => :destroy
has_many :teams, :through => :favorite_teams
end

class FavoriteTeam < ActiveRecord::Base
belongs_to :user
belongs_to :team
after_create { |fave| fave.team.ref_up }
after_destroy { |fave| fave.team.ref_down }
end

Unfortunately, the counter_cache feature won’t work with join models
because has_many :through associations don’t work like regular has_many
collections. (see http://blog.hasmanythrough.com/articles/read/150 for
more background)

But you can still put a ref count in the Team class to track how many
Favorites reference it. Start it at 1 when created (since it gets
created with an existing FavoriteTeam). Use #ref_up and #ref_down to
increment and decrement the count. When it drops to zero, it can
self-destruct.

If you don’t want to maintain the refcount, you can lose the #ref_up
method (and the after_create callback in FavoriteTeam) and just have
#ref_down query for FavoriteTeam records to see if it’s referenced; when
there are no records with a ref it can self-destruct.

Not sure which way you should go. I’ll leave it up to you to figure out
which approach works better for your system.

By the way, Rails convention is for model class names to be singular,
tables plural.

good luck!


Josh S.
http://blog.hasmanythrough.com