Сохранение порядка связанных записей в Rails has_many: посредством ассоциации - PullRequest
7 голосов
/ 11 октября 2011

Я работаю над плагином Rails, который включает способ изменения порядка связанных записей в ассоциации has_many: through. Скажем, у нас есть следующие модели:

class Playlist < ActiveRecord::Base
  has_many :playlists_songs, :dependent => :destroy
  has_many :songs, :through => :playlists_songs
end

class Song < ActiveRecord::Base
  has_many :playlists_songs, :dependent => :destroy
  has_many :playlists, :through => :playlists_songs
end

class PlaylistsSong < ActiveRecord::Base
  belongs_to :playlist
  belongs_to :song
end

Если мы изменим порядок песен Playlist (например, @playlist.songs.rotate!), Rails не коснется записей в таблице playlists_songs (я использую Rails 3.1), что имеет смысл. Я хотел бы сделать любой вызов метода Playlist's song =, чтобы сохранить порядок песен, хотя, возможно, либо удалив соответствующие существующие строки в playlists_songs, либо создав новые в правильном порядке (чтобы :order => "id" можно было использовать при их извлечение) или добавив столбец sort: integer в playlists_songs и соответствующим образом обновив эти значения.

Я не видел никаких обратных вызовов (например, before_add), которые позволили бы это. В ActiveRecord :: Associates :: CollectionAssociation соответствующие методы выглядят как writer , replace и replace_records , но я потерял на том, что будет лучшим следующим шагом. Есть ли способ расширить или безопасно переопределить один из этих методов, чтобы учесть функциональность, которую я ищу (желательно только для определенных ассоциаций), или есть другой, лучший подход для этого?

1 Ответ

6 голосов
/ 11 октября 2011

Вы смотрели на acts_as_list ?Это один из самых старых плагинов рельсов, предназначенный для решения подобных проблем.

Вместо сортировки по id сортировка выполняется по позиционному столбцу.Тогда это просто вопрос обновления позиции, а не грязный бизнес по изменению id или удалению / замене записей.

В вашем случае вы просто добавили бы position целочисленный столбец к PlayListSong, затем:

class PlayListSong
  acts_as_list :scope => :play_list_id
end

Как вы указали в комментариях, методы в acts_as_list в основном работают с отдельными элементами в списке, и нет функции «переупорядочить» из коробки.Я не рекомендовал бы вмешиваться с replace_records, чтобы сделать это.Было бы чище и яснее написать метод, использующий тот же столбец позиции, что и плагин.Например.

class PlayList
  # It makes sense for these methods to be on the association.  You might make it
  # work for #songs instead (as in your question), but the join table is what's
  # keeping the position.
  has_many :play_list_songs, ... do

    # I'm not sure what rotate! should do, so...

    # This first method makes use of acts_as_list's functionality
    #
    # This should take the last song and move it to the first, incrementing 
    # the position of all other songs, effectively rotating the list forward 
    # by 1 song.
    def rotate!
      last.move_to_top unless empty?
    end

    # this, on the other hand, would reorder given an array of play_list_songs.
    # 
    # Note: this is a rough (untested) idea and could/should be reworked for 
    # efficiency and safety. 
    def reorder!(reordered_songs)
      position = 0
      reordered_songs.each do |song|
        position += 1

        # Note: update_column is 3.1+, but I'm assuming you're using it, since
        # that was the source you linked to in your question
        find(song.id).update_column(:position, position)
      end
    end
  end
end
...