LOGIN / SIGN UP
2 Author: Claes Nästén
Date: Tue Mar 02 17:07:36 +0100 2010
Subject: Introduce TaskChangeSet grouping TaskChanges on Tasks.
    To support observing task editing and only sending one mail on multiple
    updates group the changes into one TaskChangeSet and observe it.
    
    Updates should be done on activities to use the same functionality.

app/controllers/tasks_controller.rb
 
180 @@ -180,41 +180,36 @@
180 # Edit single task
181 def update
182 check_project_access(@project, '!guest', true)
183 changes = []
184
185 # Check for updated attributes
186 attrs = find_changed_attributes(@task, params[:task])
187 if not attrs.empty?
188 update_task_attrs(attrs, changes)
189 end
190
191 # Check for uploaded file
192 if params[:file_info] and params[:file_info][:file].methods.include?('original_filename')
193 update_task_file(changes)
194 end
195 ActiveRecord::Base::transaction do
196 begin
197 task_change_set = @task.task_change_sets.create!(
198 :user_id => current_user.id)
199
200 # Check for comment
201 if params[:comment]
202 update_task_comment(changes)
203 end
204 # Check for updated attributes, uploaded file and comment
205 attrs = find_changed_attributes(@task, params[:task])
206 if not attrs.empty?
207 update_task_attrs(task_change_set, attrs)
208 end
209 if params[:file_info] and params[:file_info][:file].methods.include?('original_filename')
210 update_task_file(task_change_set, params[:file_info][:file])
211 end
212 if params[:comment]
213 update_task_comment(task_change_set, params[:comment])
214 end
215
216 # Update task changes if task was changed
217 if changes.size > 0
218 find_task
219
220 # Update time for the task, is done manually as comments and
221 # files are not direct attributes of the task.
222 @task.updated_at = Time.now
223 @task.save
224
225 # Send notification e-mail if changes went through ok
226 if attrs.include?('status') and Task::CONDITION_CLOSED.include?(@task.status)
227 send_mail_close(@task)
228 else
229 send_mail_updated(@task, changes)
230 if task_change_set.task_changes.size > 0
231 # Trigger a new save (with changes recorded) and re-read the task
232 # from the database.
233 task_change_set.save
234 find_task
235 else
236 flash.now[:notice] = 'Nothing changed'
237 end
238 rescue
239 log_error($!)
240 flash.now[:error] = 'Error occurred while updating the task'
241 end
242 else
243 flash.now[:notice] = 'Nothing changed'
244 end
245
246 # Get data required for editing
...  
251 @@ -256,72 +251,48 @@
251 private
252
253 # Update task metadata attributes
259 def update_task_attrs(attrs, changes)
260 attr_changes = []
261
262 ActiveRecord::Base::transaction do
263 begin
264 # Add changes
265 logger.debug('Updating attributes for "%s"' % @task.name)
266
267 attrs.each_pair do |key, value|
268 logger.debug('Updating attribute %s to %s' % [key, value])
269 attr_changes << @task.task_changes.create!(
270 :user_id => current_user.id,
271 :field => task_field_to_string(key),
272 :value_before => task_field_value(@task, key, nil),
273 :value_after => task_field_value(@task, key, value))
274 end
275
276 # Save the actual task
277 @task.update_attributes!(attrs)
278 flash.now[:notice] = 'Updated task'
279 rescue
280 log_error($!)
281 flash.now[:error] = 'Failed updating task'
282 attr_changes = []
283 end
284 end
285
286 # Append validated changes
287 changes.concat(attr_changes)
283 def update_task_attrs(task_change_set, attrs)
284 logger.debug('Updating attributes for "%s"' % @task.name)
285
286 attrs.each_pair do |key, value|
287 logger.debug('Updating attribute %s to %s' % [key, value])
288 task_change_set.task_changes.create!(
289 :task_id => @task.id, :user_id => current_user.id,
290 :field => task_field_to_string(key),
291 :value_before => task_field_value(@task, key, nil),
292 :value_after => task_field_value(@task, key, value))
293 end
294
295 @task.update_attributes!(attrs)
296 end
297
303 def update_task_file(changes)
299 def update_task_file(task_change_set, file)
300 # Attach file
301 logger.debug('Attaching file to "%s"' % @task.name)
302
308 begin
309 @file_info = @task.file_infos.new(:file => params[:file_info][:file])
310 @file_info.save!
306 file_info = @task.file_infos.new(:file => file)
307 file_info.save!
308
314 begin
315 changes << @task.task_changes.create!(:user_id => current_user.id,
316 :field => task_field_to_string('attachment'),
317 :value_before => '', :value_after => @file_info.filename)
318
319 flash.now[:notice] = 'Attached file to task'
320 rescue
321 log_error($!)
322 flash.now[:error] = 'Failed to attach file'
323
324 # Remove newly created file.
325 @file_info.destroy
326 end
322 begin
323 task_change_set.task_changes.create!(
324 :task_id => @task.id, :user_id => current_user.id,
325 :field => task_field_to_string('attachment'),
326 :value_before => '', :value_after => file_info.filename)
327 rescue
328 log_error($!)
334 flash.now[:error] = 'Failed to attach file'
335 end
331 file_info.destroy
332 throw $!
333 end
334 end
335
341 def update_task_comment(changes)
337 def update_task_comment(task_change_set, comment)
338 # Add comment
339 logger.debug('Adding comment %s' % params[:comment])
340
341 # Avoid re-post issues, check for duplicate comment
347 change = @task.task_changes.find(
343 change = TaskChange.find(
344 :first, :order => 'task_changes.created_at DESC',
350 :conditions => { :user_id => current_user.id, :field => 'comment' })
346 :conditions => { :task_id => @task.id, :user_id => current_user.id, :field => 'comment' })
347
348 if params[:comment].size == 0
349 # Skip empty comments
...  
301 @@ -330,14 +301,9 @@
301 elsif not change.nil? and change.value_after == params[:comment]
302 flash.now[:error] = 'Skipping duplicate comment'
303 else
333 begin
334 changes << @task.task_changes.create!(:user_id => current_user.id, :field => 'comment',
335 :value_after => params[:comment])
336 flash.now[:notice] = 'Added comment'
337 rescue
338 log_error($!)
339 flash.now[:error] = 'Failed to add comment'
340 end
312 task_change_set.task_changes.create!(
313 :task_id => @task.id, :user_id => current_user.id,
314 :field => 'comment', :value_after => params[:comment])
315 end
316 end
317
...  
335 @@ -369,30 +335,6 @@
335 attrs
336 end
337
372 # Send mail with close message for task
373 def send_mail_close(task)
374 begin
375 mail = TaskMailer.create_close_message(CONFIGURATION['site_email'], CONFIGURATION['site_url'],
376 @project, current_user, task)
377 mail_status = TaskMailer.deliver(mail)
378 rescue
379 log_error($!)
380 flash[:error] = 'Failed sending notification e-mail!'
381 end
382 end
383
384 # Send mail with information about what was changed.
385 def send_mail_updated(task, changes)
386 begin
387 mail = TaskMailer.create_update_message(CONFIGURATION['site_email'], CONFIGURATION['site_url'],
388 @project, current_user, task, changes)
389 mail_status = TaskMailer.deliver(mail)
390 rescue
391 log_error($!)
392 flash[:error] = 'Failed sending notification e-mail!'
393 end
394 end
395
362 # Before filter getting task
363 def find_task
364 @task = @project.tasks.find_by_id(params[:id], :include => [:user, :file_infos, :phase])
...  
348 @@ -406,7 +348,7 @@
348
349 # Get task changes for current task
350 def find_task_changes
409 changes = @task.task_changes.find_by_sql(['SELECT c.*, SUBSTR(c.created_at, 1, 10) AS day FROM task_changes c WHERE task_id = ? ORDER BY created_at DESC', @task.id])
352 changes = TaskChange.find_by_sql(['SELECT c.*, SUBSTR(c.created_at, 1, 10) AS day FROM task_changes c, task_change_sets cs WHERE cs.task_id = ? AND cs.id = c.task_change_set_id ORDER BY cs.created_at DESC, c.id', @task.id])
353
354 day = nil
355 user_id = nil
...  

app/models/task_mailer.rb
 
30 @@ -30,21 +30,26 @@
30 end
31
32 # Create e-mail message for task update
33 def update_message(from, site_url, project, user, task, changes, sent_at = Time.now)
34 @subject = "#{project.name} task ##{task.id} \"#{task.name}\" updated by #{user.display_name}"
35 @body = { :site_url => site_url, :project => project, :user => user, :task => task,
36 :changes => changes }
37 @recipients = task_recipients(task)
38 def update_message(from, site_url, task_change_set, sent_at = Time.now)
39 @task = task_change_set.task
40 @user = task_change_set.user
41 @project = @task.project
42 @subject = "#{@project.name} task ##{@task.id} \"#{@task.name}\" updated by #{@user.display_name}"
43 @body = { :site_url => site_url, :project => @project, :user => @user, :task => @task, :task_change_set => task_change_set }
44 @recipients = task_recipients(@task)
45 @from = from
46 @sent_on = sent_at
47 @headers = {}
48 end
49
50 # Create e-mail message for task closing
51 def close_message(from, site_url, project, user, task, sent_at = Time.now)
52 @subject = "#{project.name} task ##{task.id} \"#{task.name}\" closed by #{user.display_name}"
53 @body = { :site_url => site_url, :project => project, :user => user, :task => task }
54 @recipients = task_recipients(task)
55 def close_message(from, site_url, task_change_set, sent_at = Time.now)
56 @task = task_change_set.task
57 @user = task_change_set.user
58 @project = @task.project
59 @subject = "#{@project.name} task ##{@task.id} \"#{@task.name}\" closed by #{@user.display_name}"
60 @body = { :site_url => site_url, :project => @project, :user => @user, :task => @task, :task_change_set => task_change_set }
61 @recipients = task_recipients(@task)
62 @from = from
63 @sent_on = sent_at
64 @headers = {}
...