Reputation: 2319
I have a has_many through association setup between a song model and an artist model. My code looks something like this
SongArtistMap Model
class SongArtistMap < ActiveRecord::Base
belongs_to :song
belongs_to :artist
end
Artist Model
class Artist < ActiveRecord::Base
has_many :song_artist_maps
has_many :songs, :through => :song_artist_maps
validates_presence_of :name
end
Song Model
class Song < ActiveRecord::Base
has_many :song_artist_maps
has_many :artists, :through => :song_artist_maps
accepts_nested_attributes_for :artists
end
I have a form where a user submits a song and enters in the song title and the song artist.
So when a user submits a song and my Artists table doesn't already have the artist for the song I want it to create that artist and setup the map in SongArtistMap
If a user submits a song with an artist that is already in the Artists table I just want the SongArtistMap created but the artist not duplicated.
Currently everytime a user submits a song a new artist gets created in my artists table even if the same one already exists and a SongArtistMap is created for that duplicated artist.
Any idea on how to tackle this issue? I feel like rails probably has some easy little trick to fix this already built in. Thanks!
Upvotes: 4
Views: 1017
Reputation: 2319
Ok I got this figured out awhile ago and forgot to post. So here's how I fixed my problem. First of all I realized I didn't need to have a has_many through
relationship.
What I really needed was a has_and_belongs_to_many
relationship. I setup that up and made the table for it.
Then in my Artists
model I added this
def self.find_or_create_by_name(name)
k = self.find_by_name(name)
if k.nil?
k = self.new(:name => name)
end
return k
end
And in my Song
model I added this
before_save :get_artists
def get_artists
self.artists.map! do |artist|
Artist.find_or_create_by_name(artist.name)
end
end
And that did exactly what I wanted.
Upvotes: 1
Reputation: 64363
Try this:
class Song < ActiveRecord::Base
has_many :song_artist_maps
has_many :artists, :through => :song_artist_maps
accepts_nested_attributes_for :artists, :reject_if => :normalize_artist
def normalize_artist(artist)
return true if artist['name'].blank?
artist['id'] = Artist.find_or_create_by_name(artist['name']).id
false # This is needed
end
end
We are essentially tricking rails by over-loading the reject_if
function(as we never return true
).
You can further optimize this by doing case insensitive lookup ( not required if you are on MySQL)
artist['id'] = (
Artist.where("LOWER(name) = ? ", artist['name'].downcase).first ||
Artist.create(:name => artist['name'])
).id
Upvotes: 0
Reputation: 3706
I use a method in the model of the table the other two go through, that is called with before_create. This can probably be made much neater and faster though.
before_create :ensure_only_one_instance_of_a_user_in_a_group
private
def ensure_only_one_instance_of_a_user_in_a_group
user = User.find_by_id(self.user_id)
unless user.groups.empty?
user.groups.each do |g|
if g.id == self.group_id
return false
end
end
end
return true
end
Upvotes: 0