Work with :through associations made easy
Augusts Bautra
Posted on June 5, 2024
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
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]
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 }
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 %>
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: []
).
Posted on June 5, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.