skip to Main Content

I have two models Playlist and Song. The playlist has an ordered list of songs through a join table playlist_song_associations with a position attribute, e.g.

class Playlist < ApplicationRecord
  has_many :playlist_song_associations, dependent: :destroy
  has_many :songs, through: :playlist_song_associations
end

class Song < ApplicationRecord
  has_many :playlist_song_associations, dependent: :destroy
  has_many :playlists, through: :playlist_song_associations
end

class PlaylistSongAssociation < ApplicationRecord
  belongs_to :playlist, touch: true
  belongs_to :song
  default_scope { order(position: :asc) }
end

Now I have an update controller method, where I do something like

playlist.songs = get_ordered_songs_from_params

And I want the associations to be ordered according to the order within the get_ordered_songs_from_params array.

Is there a way to set this up, so that rails automagically sets the position attribute on the associations?

2

Answers


  1. The answer to whether or not you can get rails to do that automagically is maybe no, but maybe that’s not what you really want?

    The way you have things set up, PlaylistSongAssociation is a real object. It exists like it has meaning. But it really kind of doesn’t have any meaning outside the context of the playlist. You never want to edit it alone.

    A better model might be to have an array of song_ids in the Playlist and drop the PlaylistSongAssociation. The downside is that you almost certainly lose referential integrity to songs – but that might depend on your database of choice, and maybe that isn’t a real concern for you (if song deletion is unlikely or super rare or you’re fine recovering from it).

    Add an array column in Rails

    or using a json column. Again, depending on your database.

    Login or Signup to reply.
  2. You can automatically fill the "position" field by using callbacks when creating PlaylistSongAssociation.

    before_save :ensure_position
    def ensure_position
      self.position = playlist.songs.count + 1
    end
    

    If you recently added the possibility of ordering your playlist and there was no "position" field before, you need to fill this field with data by using either migration or rake task. You can fill the field easily:

    Playlist.all.each do |playlist|
      playlist.playlist_song_associations.each_with_index do |psa, i|
        psa.update(position: i + 1)
      end
    end
    

    The idea here is to automate position setting when it gets added to playlist only once. If you will try to update position on the fly each time you call the playlist songs – you will ruin loading speed and will spam the db with requests. Always try to make only absolutely necessary amount of requests to preserve resources, cause every little thing counts.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search