class WorkPacket

File

work_packet.rb

(C)

Hipposoft 2007

Purpose

Describe the behaviour of WorkPacket objects. See below for more details.


24-Dec-2007 (ADH): Created.

Public Class Methods

find_by_task_user_and_range( range, task_id = nil, user_id = nil ) click to toggle source

Find work packets in rows related to the given task ID, held in timesheets owned by the given user ID, between the Dates in the given range. The range MUST be inclusive, for reasons discussed below. The results are sorted by work packet date, descending.

The task and user IDs are optional. All tasks and/or users will be included in the count if the given task and/or user ID is nil. The date range is mandatory.

IMPORTANT - at the time of writing, Rails 2.1 (and earlier versions) will build a BETWEEN statement in SQL with the given range. Although SQL says that the values on either side of BETWEEN should be treated as inclusive, i.e. a Ruby “a..b” kind of range, some databases may treat the right side as exclusive; PostgreSQL is fine, but if in doubt you need to go to the Rails console and run a test. For example, issue something like this:

User.all.collect { |x| x.id }.sort

Note any two consecutive IDs listed - e.g. “[1, 2, …]” - 1 and 2 will do. Use these as part of range conditions for a find:

User.find(:all, :conditions => { :id => 1..2 } )

Assuming you actually have users with IDs 1 and 2, then both should be returned. If you only get one, BETWEEN isn’t working and you need to use another database or change the function below to do something else (e.g. hard-code a condition using “>=” and “<=” if your database supports those operators).

A final twist is that Rails’ “to_s( :db )” operator assumes all ranges are inclusive and generates SQL accordingly. There’s a ticket for this in the case of dates:

http://dev.rubyonrails.org/ticket/8549

…but actually Rails seems to do this for any kind of range - e.g. change the “1..2” to “1…2” in the User find above and note that the generated SQL is the same. We’d expect it to only look for a user with id ‘1’ (or between 1 and 1) in this case.

As a result, ensure you only ever pass inclusive ranges to this function.

# File app/models/work_packet.rb, line 92
def self.find_by_task_user_and_range( range, task_id = nil, user_id = nil )
  return WorkPacket.find_by_task_user_range_and_committed(
    range,
    nil,
    task_id,
    user_id
  )
end
find_by_task_user_range_and_committed( range, committed, task_id = nil, user_id = nil ) click to toggle source

Support ::find_by_task_user_and_range, ::find_committed_by_task_user_and_range and find_not_committed_by_task_user_and_range. An extra mandatory second parameter must be set to ‘true’ to only include work packets from committed timesheets, ‘false’ for not committed timesheets and ‘nil’ for either.

# File app/models/work_packet.rb, line 132
def self.find_by_task_user_range_and_committed( range, committed, task_id = nil, user_id = nil )

  # The 'include' part needs some explanation. We include the timesheet rows,
  # a second order association, because the rows lead to tasks and timesheets.
  # We need to eager-load tasks because the search is limited by task ID. We
  # need to eager-load timesheets because they lead to users and the search is
  # also limited by user ID. Rails supports eager-loading of third and deeper
  # order associations through passing hashes in as the value to ":include".
  # Each key's value is the next level of association. So :timesheet_row is
  # at the second order, pointing to an array giving two third order things;
  # :task and, itself a hash key, :timesheet; since it is a hash key,
  # :timesheet's value is the second-order association of timesheets, or the
  # fourth-order association of the work packets - :user.
  #
  # Ultimately eager-loading means LEFT OUTER JOIN in SQL statements. Due to
  # the way that ActiveRecord assembles the query, using :include rather than
  # :joins with some hard-coded SQL makes for a very verbose query in the
  # "find" case; it's nice and compact for "sum", though. In any event, at
  # least it is a query generated entirely through the database adapter, so
  # it stands a fighting chance of working fine on multiple database types.

  conditions = { :date => range }
  conditions[ 'tasks.id' ] = task_id unless task_id.nil?
  conditions[ 'users.id' ] = user_id unless user_id.nil?
  conditions[ 'timesheets.committed' ] = committed unless committed.nil?

  return WorkPacket.all(
    :include     => { :timesheet_row => [ :task, { :timesheet => :user } ] },
    :conditions  => conditions,
    :order       => 'date DESC'
  )

end
find_committed_by_task_user_and_range( range, task_id = nil, user_id = nil ) click to toggle source

As ::find_by_task_user_and_range, but only counts work packets belonging to committed timesheets.

# File app/models/work_packet.rb, line 104
def self.find_committed_by_task_user_and_range( range, task_id = nil, user_id = nil )
  # TODO: Use with_scope? Can we cope with the 'nil' case cleanly?

  return WorkPacket.find_by_task_user_range_and_committed(
    range,
    true,
    task_id,
    user_id
  )
end
find_earliest_by_tasks( task_ids = [] ) click to toggle source

Return the earliest (first by date) work packet, either across all tasks (pass nothing) or for the given tasks specified as an array of task IDs. The work packet may be in either a not committed or committed timesheet.

# File app/models/work_packet.rb, line 170
def self.find_earliest_by_tasks( task_ids = [] )
  return WorkPacket.find_first_by_tasks_and_order( task_ids, 'date ASC' )
end
find_first_by_tasks_and_order( task_ids, order ) click to toggle source

Support ::find_earliest_by_tasks and find_latest_by_tasks. Pass an array of task IDs and a sort order (SQL fragment, e.g. “date ASC”).

# File app/models/work_packet.rb, line 185
def self.find_first_by_tasks_and_order( task_ids, order )
  if ( task_ids.empty? )
    return WorkPacket.significant.first( :order => order )
  else
    return WorkPacket.significant.first(
      :include    => [ :timesheet_row ],
      :conditions => [ 'timesheet_rows.task_id IN (?)', task_ids ],
      :order      => order
    )
  end
end
find_latest_by_tasks( task_ids = [] ) click to toggle source

Return the latest (last by date) work packet, either across all tasks (pass nothing) or for the given tasks specified as an array of task IDs. The work packet may be in either a not committed or committed timesheet.

# File app/models/work_packet.rb, line 178
def self.find_latest_by_tasks( task_ids = [] )
  return WorkPacket.find_first_by_tasks_and_order( task_ids, 'date DESC' )
end
find_not_committed_by_task_user_and_range( range, task_id = nil, user_id = nil ) click to toggle source

As ::find_by_task_user_and_range, but only counts work packets belonging to timesheets which are not committed.

# File app/models/work_packet.rb, line 118
def self.find_not_committed_by_task_user_and_range( range, task_id = nil, user_id = nil )
  return WorkPacket.find_by_task_user_range_and_committed(
    range,
    false,
    task_id,
    user_id
  )
end