Class | WorkPacket |
In: |
app/models/work_packet.rb
|
Parent: | ActiveRecord::Base |
File: | work_packet.rb |
(C): | Hipposoft 2008, 2009 |
Purpose: | Describe the behaviour of WorkPacket objects. See below for more details. |
24-Dec-2007 (ADH): Created.
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 92: def self.find_by_task_user_and_range( range, task_id = nil, user_id = nil ) 93: return WorkPacket.find_by_task_user_range_and_committed( 94: range, 95: nil, 96: task_id, 97: user_id 98: ) 99: end
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 132: def self.find_by_task_user_range_and_committed( range, committed, task_id = nil, user_id = nil ) 133: 134: # The 'include' part needs some explanation. We include the timesheet rows, 135: # a second order association, because the rows lead to tasks and timesheets. 136: # We need to eager-load tasks because the search is limited by task ID. We 137: # need to eager-load timesheets because they lead to users and the search is 138: # also limited by user ID. Rails supports eager-loading of third and deeper 139: # order associations through passing hashes in as the value to ":include". 140: # Each key's value is the next level of association. So :timesheet_row is 141: # at the second order, pointing to an array giving two third order things; 142: # :task and, itself a hash key, :timesheet; since it is a hash key, 143: # :timesheet's value is the second-order association of timesheets, or the 144: # fourth-order association of the work packets - :user. 145: # 146: # Ultimately eager-loading means LEFT OUTER JOIN in SQL statements. Due to 147: # the way that ActiveRecord assembles the query, using :include rather than 148: # :joins with some hard-coded SQL makes for a very verbose query in the 149: # "find" case; it's nice and compact for "sum", though. In any event, at 150: # least it is a query generated entirely through the database adapter, so 151: # it stands a fighting chance of working fine on multiple database types. 152: 153: conditions = { :date => range } 154: conditions[ 'tasks.id' ] = task_id unless task_id.nil? 155: conditions[ 'users.id' ] = user_id unless user_id.nil? 156: conditions[ 'timesheets.committed' ] = committed unless committed.nil? 157: 158: return WorkPacket.all( 159: :include => { :timesheet_row => [ :task, { :timesheet => :user } ] }, 160: :conditions => conditions, 161: :order => 'date DESC' 162: ) 163: 164: end
As find_by_task_user_and_range, but only counts work packets belonging to committed timesheets.
# File app/models/work_packet.rb, line 104 104: def self.find_committed_by_task_user_and_range( range, task_id = nil, user_id = nil ) 105: # TODO: Use with_scope? Can we cope with the 'nil' case cleanly? 106: 107: return WorkPacket.find_by_task_user_range_and_committed( 108: range, 109: true, 110: task_id, 111: user_id 112: ) 113: end
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 170: def self.find_earliest_by_tasks( task_ids = [] ) 171: return WorkPacket.find_first_by_tasks_and_order( task_ids, 'date ASC' ) 172: end
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 185: def self.find_first_by_tasks_and_order( task_ids, order ) 186: if ( task_ids.empty? ) 187: return WorkPacket.significant.first( :order => order ) 188: else 189: return WorkPacket.significant.first( 190: :include => [ :timesheet_row ], 191: :conditions => [ 'timesheet_rows.task_id IN (?)', task_ids ], 192: :order => order 193: ) 194: end 195: end
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 178: def self.find_latest_by_tasks( task_ids = [] ) 179: return WorkPacket.find_first_by_tasks_and_order( task_ids, 'date DESC' ) 180: end
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 118: def self.find_not_committed_by_task_user_and_range( range, task_id = nil, user_id = nil ) 119: return WorkPacket.find_by_task_user_range_and_committed( 120: range, 121: false, 122: task_id, 123: user_id 124: ) 125: end