DEV Community

Cover image for Work with :through associations made easy
Augusts Bautra
Augusts Bautra

Posted on • Edited on

Work with :through associations made easy

As explained in Rails association reference, defining a has_many association gives 17 methods.

We'll focus on the collection_singular_ids=(ids) part.

class Book
  has_many :book_genres
  has_many :genres, through: :book_genres
end

class Genre
  has_many :book_genres
  has_many :books, through: :book_genres
end

# the tie model
class BookGenre
  belongs_to :book
  belongs_to :genre  
end
Enter fullscreen mode Exit fullscreen mode

Given a book, we'd get a genre_ids= (from has_many :genres). This method is very powerful. It's designed in such a way that you can use the value as-is straight from controller params in mass-assigment, and only the genres you specified would remain. Yes, genre_ids= will take care of identifying assocs being removed and new ones being added and only apply the change, not touching unchanged records!

book.genre_ids #=> [1, 2]

# this will delete the BookGenre record tying this book to genre 1
# and create a new one tying it to genre 3, leaving tie to 2 unchanged!
book.update!(genre_ids: ["2", "3"])

book.genre_ids #=> [2, 3] 
Enter fullscreen mode Exit fullscreen mode

This only leaves validation errors. Say you want to validate that every book has at least one genre specified.

Easy, you can even use a macro validation:

Book.validates :genre_ids, length: { minimum: 1, message: :at_least_one_required }
Enter fullscreen mode Exit fullscreen mode

The last piece of the puzzle is how will the form react. You may need to align what field/helper you are using, so validation errors color the field nice and red on any problems.

<%= f.input :genre_ids %>

# or maybe

<%= f.association :genres %>
Enter fullscreen mode Exit fullscreen mode

Just make sure the param being submitted is :genre_ids, so mass-assignment works.

For StrongParams you may need to do this:

params.require(:book).permit(
  :title, genre_ids: []
).
Enter fullscreen mode Exit fullscreen mode

Top comments (0)