Question

I've created three classes to represent Books, People, and BookLoans. While I am able to show the association of People to Books through BookLoans I've been seeding my database.

I now need to save a checkout of a book. It was my intention to do this action through the book controller. Specifically, creating a loan action in the BooksController. While this makes sense to me in theory I am having a terrible time implementing the appropriate syntax.

I've added the ability to loan a book from the show view of a book. This view now contains a form which uses the loan action of the book controller to record the loan.

I've added what I believe are the appropriate methods to my Book model. With the help of theIV I have captured the appropriate information in the Controller. Unfortunately, when I press Loan on the book show view a book_loan record is no being recorded.

What am I missing?

Book Model

class Book < ActiveRecord::Base
  has_many :book_loans
  has_many :borrowers, :through => :book_loans, :source => :person

  accepts_nested_attributes_for :book_loans, :allow_destroy => true

  def loaned?
    book_loans.exists?(:return_date => nil)
  end

  def current_borrower
    if loaned?
      book_loans.first(:order => "out_date desc").person
    end
  end

  def add_loan (person_id)
      book_loans.create(:book_id => id,
                        :person_id => person_id,
                        :out_date => Date.current)
  end
end

Loan Method from BooksController

def loan
  @book.add_loan(params[:book_loan][:person_id])

  redirect_to :action => 'book', :id => params[:id]
end

Book Show View w/ Loan Form

<p>
  <b>Title:</b>
  <%=h @book.title %>
</p>
<p>
  <b>Isbn10:</b>
  <%=h @book.isbn10 %>
</p>
<p>
  Currently loaned to:
  <%=h borrower_name(@book) %>
</p>

<% form_for(@book) do |x| %>
  <p>
    <%= x.label :loan_person_id %><br/>
    <%= collection_select(:book_loan, :person_id,
      Person.find(:all, :order => 'name ASC'), :id, :name) %>
    <%= x.submit 'Loan', :action => 'loan' %>
  </p>
<% end %>

BookLoan Model

class BookLoan < ActiveRecord::Base
  belongs_to :book
  belongs_to :person
end

Person Model

class Person < ActiveRecord::Base
  has_many :book_loans
  has_many :books, :through => :book_loans
end

Development Log

Processing BooksController#update (for 127.0.0.1 at 2009-09-24 13:43:05) [PUT]
  Parameters: {"commit"=>"Loan", "authenticity_token"=>"XskHLuco7Q7aoEnDfVIiYwVrMEh5uwidvJZdrMbYYWs=", "id"=>"1", "book_loan"=>{"person_id"=>"3"}}
  [4;35;1mBook Columns (3.0ms)[0m   [0mSHOW FIELDS FROM `books`[0m
  [4;36;1mBook Load (4.0ms)[0m   [0;1mSELECT * FROM `books` WHERE (`books`.`id` = 1) [0m
  [4;35;1mSQL (0.0ms)[0m   [0mBEGIN[0m
  [4;36;1mBook Load (1.0ms)[0m   [0;1mSELECT `books`.id FROM `books` WHERE (`books`.`title` = BINARY 'Dreaming in Code: Two Dozen Programmers, Three Years, 4,732 Bugs, and One Quest for Transcendent Software' AND `books`.id <> 1) LIMIT 1[0m
  [4;35;1mSQL (1.0ms)[0m   [0mCOMMIT[0m
Redirected to http://localhost:3000/books/1
Completed in 19ms (DB: 10) | 302 Found [http://localhost/books/1]
  [4;36;1mSQL (0.0ms)[0m   [0;1mSET NAMES 'utf8'[0m
  [4;35;1mSQL (0.0ms)[0m   [0mSET SQL_AUTO_IS_NULL=0[0m
Was it helpful?

Solution

When you has_many a model, you get access to a few extra methods, two in particular are collection.build and collection.createbuild is like new, create is like create :). Have a look at the has many documentation. In this case, you could rewrite add_loan as

def add_loan (person_id)
  book_loans.create(:book_id   => id,
                    :person_id => person_id,
                    :out_date  => Date.current)
end

or something similar.

To answer your question about what the view will be sending to the controller, it will send the params hash as usual, but if you just want to pass the person_id to add_loan, you can extract that. Assuming that the part of the view you have above is wrapped in a form for a book loan, you can access the person by params[:book_loan][:person_id]. You'll also want to do a find in that action first, or else @book is going to be nil.

Hope this helps. Cheers.

EDIT: I'm not sure if the way you have it right now works, but I think you want to change your Person model to read

class Person < ActiveRecord::Base
  has_many :book_loans
  has_many :books, :through => :book_loans
end

EDIT 2: Your development log says you aren't actually hitting loan, you're hitting update. A few things you could do: check to make sure you have loan as a listed resource in your routes; merge the loan action into the update action—could start getting kind of messy so I don't know if this is the best approach. I'm sure there are more, but those are the first things that pop to mind. Also, I don't know if you can add :action => 'loan' to a submit tag. I think it will just look at that as if it were an html option. You might want to change your form_for to read

<% form_for(@book), :url => { :action => 'loan' } do |x| %>

once you've made sure that the routes are in order. But as I said earlier, I'm pretty sure you will be thrown an error on that action because you haven't defined a Book.find for it.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top