Rails has_many, избегая дублирования - PullRequest
4 голосов
/ 30 марта 2012

У меня есть has_many через настройку связи между моделью песни и моделью исполнителя.Мой код выглядит примерно так

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

У меня есть форма, гдепользователь отправляет песню и вводит название песни и исполнителя песни.

Поэтому, когда пользователь отправляет песню, а в моей таблице Artists еще нет исполнителя для песни, я хочу, чтобы он создал этого исполнителяи настройте карту в SongArtistMap

Если пользователь отправляет песню с исполнителем, который уже находится в таблице Artists, я просто хочу, чтобы SongArtistMap был создан, но художник не дублировался.

В настоящее время каждый раз пользовательотправляет песню, которую новый исполнитель создает в моей таблице исполнителей, даже если такая же уже существует, и для этого дублированного исполнителя создается SongArtistMap.

Есть идеи о том, как решить эту проблему?Я чувствую, что у рельсов, вероятно, есть какой-то простой маленький трюк, чтобы исправить это, уже встроенный. Спасибо!

Ответы [ 3 ]

1 голос
/ 16 апреля 2012

Хорошо, я выяснил это некоторое время назад и забыл опубликовать. Итак, вот как я исправил свою проблему. Прежде всего, я понял, что мне не нужно иметь отношения has_many through.

Что мне действительно было нужно, так это has_and_belongs_to_many отношения. Я настроил это и сделал таблицу для этого.

Тогда в моей Artists модели я добавил это

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

И в моей Song модели я добавил это

before_save :get_artists
def get_artists
  self.artists.map! do |artist|
   Artist.find_or_create_by_name(artist.name)
  end
end

И это именно то, что я хотел.

0 голосов
/ 30 марта 2012

Попробуйте это:

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

Мы, по сути, обманываем рельсы, перегружая функцию reject_if (поскольку мы никогда не возвращаем true).

Вы можете дополнительно оптимизировать это, выполняя поиск без учета регистра (не требуется, если вы используете MySQL)

    artist['id'] = ( 
     Artist.where("LOWER(name) = ? ", artist['name'].downcase).first ||       
     Artist.create(:name => artist['name'])
    ).id
0 голосов
/ 30 марта 2012

Я использую метод в модели таблицы, через которую проходят два других, который вызывается с before_create. Это, вероятно, может быть сделано намного аккуратнее и быстрее, хотя.

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...