views:

123

answers:

4

I am trying to accomplish the atypical library learning a language application. I have been able to create Books, People, and BookCheckOuts. I've got the relationships working pretty well but am having issues with wrapping my head around how to handle books that have never been checked out.

I've created two properties on my book class CheckedOut (returns a boolean) and LastCheckedOutTo (returns a person). I am pretty much at peace with CheckedOut and feel confident that I am using the right RoR mechanism for determining if a book is presently checked out and returning a boolean in either case. I am not as confident about LastCheckedOutTo as my implementation seems like a kludge.

Am I going about this correctly? Is there a better way?

Book Class in its Entirety

class Book < ActiveRecord::Base
  has_many :book_check_outs
  has_many :people, :through => :book_check_outs

  def checked_out
    if (book_check_outs.find(:first, :conditions => "return_date is null"))
      true
    else
      false
    end
  end

  def last_checked_out_to
    if (book_check_outs.count > 0)
      book_check_outs.find(:first,
        :order => "out_date desc").person
    else
      Person.new()
    end
  end
end
A: 
  1. Regarding def checked_out i'd rename it to checked_out? since there is unwritten (or maybe written) ruby convention that any method returning true or falls end up with question-mark.
  2. The second method is pretty much ok, but it won't go well for heavy-dute websites. I'd suggest denormalizing this part and adding last_checked_out_to_id attribute to books table and updating it after each checkout process. Other way would be book_check_outs.last.person for existing person and book_check_outs.people.build for new one.
Eimantas
+1  A: 

There are actually lots of ways to do this. Here are some ideas:

You can add order and another has_many since you really care about the return date:

  has_many :book_check_outs, :order => "out_date asc"
  has_many :current_book_check_outs, :conditions=>'return_date is null'

Then you get:

def checked_out?
  current_book_check_outs.any?
end

def last_checked_out_to
  if (book_check_outs.count > 0)
     book_check_outs.last.person
  else
      Person.new()
  end
end

But I'm a little confused about how I'd use last_checked_out_to. I think I'd prefer it to return nil if it has no last person.

You should check out named scopes, as those help build these dynamic queries modularly. They would work quite well here.

Although you're not using persons (people?) in this code, I'd rework the terminology a little bit so it reads better. book.persons doesn't really seem right for what it's telling us. What do librarians call them? book.checker_outters or something?

ndp
+4  A: 

Perhaps:

class Book < ActiveRecord::Base
  has_many :book_loans
  has_many :borrowers, :class_name => 'Person', :through => :book_loans

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

  # I would be reluctant to return a new Person object
  #  just because it was not checked out by anyone, instead you could return nil
  #  OR exception out.   
  def current_borrower
    book_loans.first(:order => "out_date desc").person 
  end
end

# you can use a helper to keep your presentation clean 
module BookHelper 
  def borrower_name(book)
     if borrower = book.borrower
       borrower.name 
     else 
       "not checked out" 
     end
  end
end
Sam Saffron
I'd use try(:person), this way it doesn't bomb out.
François Beausoleil
Okay so how do I handle in my view last_checked_out_to. I would assume its best to not have a bunch of logic bleed into the view from the model. Specifically, how do I account for a book view but not error out in the instances where the book has never been checked out therefore making last_checked_out_to nil.
ahsteele
I guess a better restatement would be if rather than being LastCheckedOutTo if it were CurrentlyCheckedOutTo.
ahsteele
this is definitely nicer. Consider aliasing :people as :borrowers for the context of this class association. It will make your code read that much more cleanly.
austinfromboston
@austin, thanks expanded
Sam Saffron
had to change the second has_many to has_many :borrowers, :through => :book_loans, :source => :person but this worked great! Thanks for the help!
ahsteele
A: 

It may well be overkill for this particular example but alternatively you might want to explore the option of implementing a state machine i.e. aasm.

fractious