views:

321

answers:

5

I'm creating a library-style system in Ruby on Rails, and I'm trying to come up with a way to calculate the overdue days while excluding weekends when a borrowed item is returned. Right now I'm just calculating "dayslate" as the difference between the due date and the date the item was actually returned, but I want to exclude weekends, since items can only be returned on weekdays.

This is my first real experience with Ruby and Rails, so my apologies if I'm missing something obvious. Thanks for any help you all can provide.

Here's the code I have for the "return" function:

   def return
     @product = Product.find(params[:id])
     today = Date.today
     dayslate = today - @product.due_date
     if @product.due_date >= today
       @product.borrower = @product.check_out = @product.due_date = @product.extended_checkout = nil
       @product.save!
       flash[:notice] = "Okay, it's checked in!"
       redirect_to(products_url)
     else
       @product.borrower = @product.check_out = @product.due_date = @product.extended_checkout = nil
       @product.save!
       flash[:notice] = "Checked in, but it was #{dayslate} days late!"
       redirect_to(products_url)
     end
 end 
+1  A: 

You might check out this page and see if you can add a counter into the loop and check against the current date through each iteration.

md5sum
I was just looking at this, and does look like a step in the right direction. No mention is made of holidays, though. I don't know if the OP cares about holidays, but he might, especially if this app will be calculating a late fee based on the number of days the item was returned late.
pkaeding
+2  A: 

If holidays matter to you (i.e. you don't want to count a day as a weekday if it is a holiday), you might want to look into http://rubyforge.org/projects/holidays/. If you combine the loop that @md5sum mentioned with a check to see if the weekday is a holiday, you may be golden.

pkaeding
Good point... I didn't think about that.
md5sum
+2  A: 

Um.. just one more thing which could be useful since it's your first rails experience - pay attention to that business logic in the controller. Your model, that's where it belongs.

neutrino
Could you clarify? I don't have a background in programming, but I've been working my way through the Pragmatic Programmers Ruby and Rails books, and I'm trying to stay consistent with their examples.
Eric K
Ideally, all that your controller should be doing is pull some models from db, call some methods on them, optionally put something into the flash and then render or redirect. These are responsibilities of a controller in our classical MVC. In your case, those date calculations and @product attributes assignments are really a part of the Product model, because it's business logic.
neutrino
A: 

neutrino is quite right about moving all the logic that isnt involved in the display/render/redirect to the models.

E.g.

class ProductLoan
  def due_date_passed?
    days_late > 0
  end

  def days_late
    Date.today - due_date
  end

  def complete!
    self.borrower = self.check_out = self.due_date = self.extended_checkout = nil
    save
  end
end

class ReturnsController
  def create
    @product = ProductLoan.find(params[:id])

    if @product.due_date_passed?
      flash[:notice] = "Okay, it's checked in!"
    else
      flash[:notice] = "Checked in, but it was #{@product.days_late} days late!"
    end
    @product.complete!

    redirect_to products_url
  end
end

Ive just typed this without testing it but, something along these lines could calculate the days passed excluding weekends:

class DateOffsetCalculator
  DAYS_IN_A_WEEK = 7
  DAYS_IN_A_WEEKEND = 2

  def initialize(date)
    @date = date
  end

  def compute
    days_passed - weekend_days_passed
  end

  def days_passed
    (Date.today - @date).to_i
  end

  def weekend_days_passed
    weekends_passed * DAYS_IN_A_WEEKEND
  end

  def weekends_passed
    rounded_up_week_days / DAYS_IN_A_WEEK
  end

  def rounded_up_week_days
    days_passed + commercial_working_day_correction
  end

  def commercial_working_day_correction
    @date.cwday - 1
  end
end
Stuart S
This doesn't work for dates that occur in the middle of the week.
EmFi
+2  A: 

Here's a snippet of code to find the number of weekdays in a Range of Date objects

require 'date'
# Calculate the number of weekdays since 14 days ago
p ( (Date.today - 14)..(Date.today) ).select {|d| (1..5).include?(d.wday) }.size

This is how I would use it in your case.

class Product
  def days_late
    weekdays_in_date_range( self.due_date..(Date.today) )
  end

  protected
  def weekdays_in_date_range(range)
    # You could modify the select block to also check for holidays
    range.select { |d| (1..5).include?(d.wday) }.size
  end
end
Ben Marini