Accessing attributes from has_many :through join models 18 Mar 2012
I’ve been trying to help out a bit in the Rails section on Stack Overflow lately and have noticed a question that has come up several times lately.
Accessing an attribute from the join model on a has_many :through relationship. For the basics, check out the Rails guide on the subject.
Here’s a pretty standard application of this in my awesome Library SAAS app:
class Reader < ActiveRecord::Base
has_many :book_loans
has_many :books, :through => :book_loans
end
class Book < ActiveRecord::Base
has_many :book_loans
has_many :readers, :through => :book_loans
end
class BookLoan < ActiveRecord::Base
belongs_to :book
belongs_to :reader
end
class BookController < ApplicationController
def show
@book = Book.includes(:readers).find(params[:id])
end
end
And on the books#show page we want to show all the people who have checked out a particular book.
<%= @book.name %>
Readers:
<ul>
<% @book.readers.each do |reader| %>
<li><%= reader.name %></li>
<% end %>
</ul>
Now what if I want to add the librarian who checked out the book to them to the page. This is stored as the librarian
column on the BookLoan model, which we don’t actually have access to in the view at the moment.
This is my current solution to the problem, it involves a bit of SQL which might be a little scary for beginners, please leave a comment if you’ve got a better idea, I may even look into seeing if it’s achievable with ARel sometime soon.
class Book < ActiveRecord::Base
has_many :readers, :through => :book_loans,
:select => "readers.*, book_loans.book_loan_librarian AS book_loan_librarian"
end
<% @book.readers.each do |reader| %>
<li><%= reader.name %> by <%= reader.book_loan_librarian %></li>
<% end %>
If anyone has any questions/suggestions feel free to leave a comment or hit me up on Twitter.
N.B. I was originally going to use book_loan.created_at
but ActiveRecord doesn’t automatically typecast the extra columns returned which would be a bit out of the scope of this post.
EDIT: In Rails 4+ it would look like this
has_many :readers, -> { select("readers.*, book_loans.book_loan_librarian AS book_loan_librarian") },
:through => :book_loans