From 64fddeaf7a0ba239ddfed2888478266453ee9053 Mon Sep 17 00:00:00 2001 From: Dan Rice Date: Sun, 30 Mar 2014 14:41:12 -0400 Subject: [PATCH 01/24] Rename the tracks rakefile to match the namespace --- lib/tasks/{reset_password.rake => tracks.rake} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/tasks/{reset_password.rake => tracks.rake} (100%) diff --git a/lib/tasks/reset_password.rake b/lib/tasks/tracks.rake similarity index 100% rename from lib/tasks/reset_password.rake rename to lib/tasks/tracks.rake From 1a593d97964cdde2a3c69e33b5740a4b0a85e26d Mon Sep 17 00:00:00 2001 From: Dan Rice Date: Sun, 30 Mar 2014 14:59:17 -0400 Subject: [PATCH 02/24] Add rake task to check for deprecated password hashes --- lib/tasks/tracks.rake | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/tasks/tracks.rake b/lib/tasks/tracks.rake index 3dbd8a3c..2a5ef952 100644 --- a/lib/tasks/tracks.rake +++ b/lib/tasks/tracks.rake @@ -20,5 +20,15 @@ namespace :tracks do user.errors.each_full { |msg| puts "- #{msg}\n" } end end + + desc 'Check all passwords for deprecated hashes' + task :check_passwords => :environment do + puts "The following users have deprecated password hashes:" + User.all.each do |user| + if user.uses_deprecated_password? + puts " #{user.login}" + end + end + end end From ece69b8540e802564be36ed28364bb47c3b3f259 Mon Sep 17 00:00:00 2001 From: Dan Rice Date: Sun, 30 Mar 2014 16:47:21 -0400 Subject: [PATCH 03/24] Use Date class to assist with date math in Project model Fixes an intermittent failure in ProjectTest#test_age_in_days --- app/models/project.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/project.rb b/app/models/project.rb index f60718ed..349d711e 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -136,7 +136,7 @@ class Project < ActiveRecord::Base end def age_in_days - @age_in_days ||= ((Time.now.utc - created_at).to_i / 1.day) + 1 + @age_in_days ||= (Time.now.to_date - created_at.to_date).to_i + 1 end def self.import(filename, params, user) From 8e13059df1f9f5addcbf24b25cf7cdb35fa07c5c Mon Sep 17 00:00:00 2001 From: Dan Rice Date: Sun, 30 Mar 2014 21:36:31 -0400 Subject: [PATCH 04/24] Further improve time comparison in Project model --- app/models/project.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/project.rb b/app/models/project.rb index 349d711e..834428b0 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -136,7 +136,7 @@ class Project < ActiveRecord::Base end def age_in_days - @age_in_days ||= (Time.now.to_date - created_at.to_date).to_i + 1 + @age_in_days ||= (Time.current.to_date - created_at.to_date).to_i + 1 end def self.import(filename, params, user) From 78c07d52b76d3f84a41f936a26f5d5cf2ae31918 Mon Sep 17 00:00:00 2001 From: Reinier Balt Date: Mon, 27 Jan 2014 16:42:54 +0100 Subject: [PATCH 05/24] Start major refactoring of recurring_todos. Started with creating new recurring todos. All current and new tests pass --- app/controllers/recurring_todos_controller.rb | 40 ++---- app/models/recurring_todo.rb | 2 +- .../abstract_recurring_todos_builder.rb | 72 +++++++++++ .../abstract_repeat_pattern.rb | 42 ++++++ .../daily_recurring_todos_builder.rb | 19 +++ .../recurring_todos/daily_repeat_pattern.rb | 42 ++++++ .../monthly_recurring_todos_builder.rb | 25 ++++ .../recurring_todos/monthly_repeat_pattern.rb | 49 +++++++ .../recurring_todos_builder.rb | 104 +++++++++++++++ .../weekly_recurring_todos_builder.rb | 22 ++++ .../recurring_todos/weekly_repeat_pattern.rb | 29 +++++ .../yearly_recurring_todos_builder.rb | 24 ++++ .../recurring_todos/yearly_repeat_pattern.rb | 46 +++++++ lib/todo_from_recurring_todo.rb | 7 +- .../recurring_todos_controller_test.rb | 46 +++++++ .../abstract_recurring_todos_builder_test.rb | 122 ++++++++++++++++++ .../abstract_repeat_pattern_test.rb | 46 +++++++ .../daily_recurring_todos_builder_test.rb | 35 +++++ .../daily_repeat_pattern_test.rb | 58 +++++++++ .../monthly_recurring_todos_builder_test.rb | 35 +++++ .../monthly_repeat_pattern_test.rb | 74 +++++++++++ .../recurring_todos_builder_test.rb | 122 ++++++++++++++++++ .../weekly_recurring_todos_builder_test.rb | 35 +++++ .../weekly_repeat_pattern_test.rb | 52 ++++++++ .../yearly_recurring_todos_builder_test.rb | 36 ++++++ .../yearly_repeat_pattern_test.rb | 66 ++++++++++ 26 files changed, 1218 insertions(+), 32 deletions(-) create mode 100644 app/models/recurring_todos/abstract_recurring_todos_builder.rb create mode 100644 app/models/recurring_todos/abstract_repeat_pattern.rb create mode 100644 app/models/recurring_todos/daily_recurring_todos_builder.rb create mode 100644 app/models/recurring_todos/daily_repeat_pattern.rb create mode 100644 app/models/recurring_todos/monthly_recurring_todos_builder.rb create mode 100644 app/models/recurring_todos/monthly_repeat_pattern.rb create mode 100644 app/models/recurring_todos/recurring_todos_builder.rb create mode 100644 app/models/recurring_todos/weekly_recurring_todos_builder.rb create mode 100644 app/models/recurring_todos/weekly_repeat_pattern.rb create mode 100644 app/models/recurring_todos/yearly_recurring_todos_builder.rb create mode 100644 app/models/recurring_todos/yearly_repeat_pattern.rb create mode 100644 test/models/recurring_todos/abstract_recurring_todos_builder_test.rb create mode 100644 test/models/recurring_todos/abstract_repeat_pattern_test.rb create mode 100644 test/models/recurring_todos/daily_recurring_todos_builder_test.rb create mode 100644 test/models/recurring_todos/daily_repeat_pattern_test.rb create mode 100644 test/models/recurring_todos/monthly_recurring_todos_builder_test.rb create mode 100644 test/models/recurring_todos/monthly_repeat_pattern_test.rb create mode 100644 test/models/recurring_todos/recurring_todos_builder_test.rb create mode 100644 test/models/recurring_todos/weekly_recurring_todos_builder_test.rb create mode 100644 test/models/recurring_todos/weekly_repeat_pattern_test.rb create mode 100644 test/models/recurring_todos/yearly_recurring_todos_builder_test.rb create mode 100644 test/models/recurring_todos/yearly_repeat_pattern_test.rb diff --git a/app/controllers/recurring_todos_controller.rb b/app/controllers/recurring_todos_controller.rb index fe8f6132..5452038b 100644 --- a/app/controllers/recurring_todos_controller.rb +++ b/app/controllers/recurring_todos_controller.rb @@ -106,33 +106,9 @@ class RecurringTodosController < ApplicationController end def create - p = RecurringTodoCreateParamsHelper.new(params, recurring_todo_params) - p.attributes['end_date']=parse_date_per_user_prefs(p.attributes['end_date']) - p.attributes['start_from']=parse_date_per_user_prefs(p.attributes['start_from']) - - # make sure we set :recurring_period first, since other setters depend on it being set - # TODO: move logic into model - @recurring_todo = current_user.recurring_todos.build(:recurring_period => params[:recurring_period]) - @recurring_todo.assign_attributes(p.selector_attributes) - @recurring_todo.update_attributes(p.attributes) - - if p.project_specified_by_name? - project = current_user.projects.where(:name => p.project_name).first_or_create - @new_project_created = project.new_record_before_save? - @recurring_todo.project_id = project.id - end - - if p.context_specified_by_name? - context = current_user.contexts.where(:name => p.context_name).first_or_create - @new_context_created = context.new_record_before_save? - @recurring_todo.context_id = context.id - end - - @saved = @recurring_todo.save - if @saved && p.tag_list.present? - @recurring_todo.tag_with(p.tag_list) - @recurring_todo.tags.reload - end + builder = RecurringTodos::RecurringTodosBuilder.new(current_user, all_recurring_todo_params) + @saved = builder.save + @recurring_todo = builder.saved_recurring_todo if @saved @status_message = t('todos.recurring_action_saved') @@ -278,7 +254,7 @@ class RecurringTodosController < ApplicationController :ends_on, :end_date, :number_of_occurences, :occurences_count, :target, :show_from_delta, :recurring_period, :recurrence_selector, :every_other1, :every_other2, :every_other3, :every_day, :only_work_days, :every_count, - :weekday, :show_always, + :weekday, :show_always, :context_name, :project_name, :tag_list, # form attributes :recurring_period, :daily_selector, :monthly_selector, :yearly_selector, :recurring_target, :daily_every_x_days, :monthly_day_of_week, @@ -293,6 +269,14 @@ class RecurringTodosController < ApplicationController ) end + def all_recurring_todo_params + # move context_name, project_name and tag_list into :recurring_todo hash for easier processing + params[:recurring_todo][:context_name] = params[:context_name] unless params[:context_name].blank? + params[:recurring_todo][:project_name] = params[:project_name] unless params[:project_name].blank? + params[:recurring_todo][:tag_list] = params[:tag_list] unless params[:tag_list].blank? + recurring_todo_params + end + def init @days_of_week = [] 0.upto 6 do |i| diff --git a/app/models/recurring_todo.rb b/app/models/recurring_todo.rb index 536779ca..1a87c305 100644 --- a/app/models/recurring_todo.rb +++ b/app/models/recurring_todo.rb @@ -107,7 +107,7 @@ class RecurringTodo < ActiveRecord::Base errors[:base] << "Please fill in the number of days to show the todo before the due date" if show_from_delta.blank? end else - raise Exception.new, "unexpected value of recurrence target selector '#{recurrence_target}'" + raise Exception.new, "unexpected value of recurrence target selector '#{target}'" end end diff --git a/app/models/recurring_todos/abstract_recurring_todos_builder.rb b/app/models/recurring_todos/abstract_recurring_todos_builder.rb new file mode 100644 index 00000000..e06c31c5 --- /dev/null +++ b/app/models/recurring_todos/abstract_recurring_todos_builder.rb @@ -0,0 +1,72 @@ +module RecurringTodos + + class AbstractRecurringTodosBuilder + + def initialize(user, attributes) + @user = user + @attributes = attributes + @filterred_attributes = filter_attributes(attributes) + @saved = false + end + + def filter_attributes(attributes) + raise Exception.new, "filter_attributes should be overridden" + end + + def filter_generic_attributes(attributes) + attributes['tag_list'] = + { + recurring_period: attributes["recurring_period"], + description: attributes['description'], + notes: attributes['notes'], + tag_list: tag_list_or_empty_string(attributes), + start_from: attributes['start_from'], + end_date: attributes['end_date'], + ends_on: attributes['ends_on'], + show_always: attributes['show_always'], + target: attributes['target'], + project: attributes[:project], + context: attributes[:context], + target: attributes['recurring_target'], + show_from_delta: attributes['recurring_show_days_before'], + show_always: attributes['recurring_show_always'] + } + end + + # build does not add tags. For tags, the recurring todos needs to be saved + def build + @recurring_todo = @pattern.build_recurring_todo + + @recurring_todo.context = @filterred_attributes[:context] + @recurring_todo.project = @filterred_attributes[:project] + end + + def save + build + @saved = @recurring_todo.save + @recurring_todo.tag_with(@filterred_attributes[:tag_list]) if @saved && @filterred_attributes[:tag_list].present? + return @saved + end + + def saved_recurring_todo + if !@saved + raise Exception.new, @recurring_todo.valid? ? "Recurring todo was not saved yet" : "Recurring todos was not saved because of validation errors" + end + + @recurring_todo + end + + def attributes + @pattern.attributes + end + + private + + def tag_list_or_empty_string(attributes) + # avoid nil + attributes['tag_list'].blank? ? "" : attributes['tag_list'].strip + end + + end + +end \ No newline at end of file diff --git a/app/models/recurring_todos/abstract_repeat_pattern.rb b/app/models/recurring_todos/abstract_repeat_pattern.rb new file mode 100644 index 00000000..678e54b0 --- /dev/null +++ b/app/models/recurring_todos/abstract_repeat_pattern.rb @@ -0,0 +1,42 @@ +module RecurringTodos + + class AbstractRepeatPattern + + def initialize(user, attributes) + @attributes = attributes + @user = user + end + + def build_recurring_todo + @recurring_todo = @user.recurring_todos.build(mapped_attributes) + end + + def mapped_attributes + @attributes + end + + def attributes + mapped_attributes + end + + def map(mapping, key, source_key) + mapping[key] = mapping[source_key] + mapping.except(source_key) + end + + def get_selector(key) + raise Exception.new, "recurrence selector pattern (#{key}) not given" unless @attributes.key?(key) + raise Exception.new, "unknown recurrence selector pattern: '#{@attributes[key]}'" unless valid_selector?(@attributes[key]) + + selector = @attributes[key] + @attributes = @attributes.except(key) + return selector + end + + def valid_selector?(selector) + raise Exception.new, "valid_selector? should be overridden in subclass of AbstractRepeatPattern" + end + + end + +end \ No newline at end of file diff --git a/app/models/recurring_todos/daily_recurring_todos_builder.rb b/app/models/recurring_todos/daily_recurring_todos_builder.rb new file mode 100644 index 00000000..48f07836 --- /dev/null +++ b/app/models/recurring_todos/daily_recurring_todos_builder.rb @@ -0,0 +1,19 @@ +module RecurringTodos + + class DailyRecurringTodosBuilder < AbstractRecurringTodosBuilder + attr_reader :recurring_todo, :pattern + + def initialize(user, attributes) + super(user, attributes) + @pattern = DailyRepeatPattern.new(user, @filterred_attributes) + end + + def filter_attributes(attributes) + @filterred_attributes = filter_generic_attributes(attributes) + %w{daily_selector daily_every_x_days}.each{|key| @filterred_attributes[key] = attributes[key] if attributes.key?(key)} + @filterred_attributes + end + + end + +end \ No newline at end of file diff --git a/app/models/recurring_todos/daily_repeat_pattern.rb b/app/models/recurring_todos/daily_repeat_pattern.rb new file mode 100644 index 00000000..32eaa553 --- /dev/null +++ b/app/models/recurring_todos/daily_repeat_pattern.rb @@ -0,0 +1,42 @@ +module RecurringTodos + + class DailyRepeatPattern < AbstractRepeatPattern + + def initialize(user, attributes) + super user, attributes + @selector = get_selector('daily_selector') + end + + def mapped_attributes + mapping = @attributes + + mapping[:only_work_days] = only_work_days?(@selector) + + mapping[:every_other1] = mapping['daily_every_x_days'] + mapping = mapping.except('daily_every_x_days') + + mapping + end + + def every_x_days + @recurring_todo.every_other1 + end + + private + + def only_work_days?(daily_selector) + case daily_selector + when 'daily_every_x_day' + return false + when 'daily_every_work_day' + return true + end + end + + def valid_selector?(selector) + %w{daily_every_x_day daily_every_work_day}.include?(selector) + end + + end + +end \ No newline at end of file diff --git a/app/models/recurring_todos/monthly_recurring_todos_builder.rb b/app/models/recurring_todos/monthly_recurring_todos_builder.rb new file mode 100644 index 00000000..a343269e --- /dev/null +++ b/app/models/recurring_todos/monthly_recurring_todos_builder.rb @@ -0,0 +1,25 @@ +module RecurringTodos + + class MonthlyRecurringTodosBuilder < AbstractRecurringTodosBuilder + + def initialize(user, attributes) + super(user, attributes) + @pattern = MonthlyRepeatPattern.new(user, @filterred_attributes) + end + + def filter_attributes(attributes) + @filterred_attributes = filter_generic_attributes(attributes) + + %w{ + monthly_selector monthly_every_x_day monthly_every_x_month + monthly_every_x_month2 monthly_every_xth_day monthly_day_of_week + }.each do |key| + @filterred_attributes[key] = attributes[key] if attributes.key?(key) + end + + @filterred_attributes + end + + end + +end \ No newline at end of file diff --git a/app/models/recurring_todos/monthly_repeat_pattern.rb b/app/models/recurring_todos/monthly_repeat_pattern.rb new file mode 100644 index 00000000..31d754a2 --- /dev/null +++ b/app/models/recurring_todos/monthly_repeat_pattern.rb @@ -0,0 +1,49 @@ +module RecurringTodos + + class MonthlyRepeatPattern < AbstractRepeatPattern + + def initialize(user, attributes) + super user, attributes + @selector = get_selector('monthly_selector') + end + + def mapped_attributes + mapping = @attributes + + mapping = map(mapping, :every_other1, 'monthly_every_x_day') + mapping = map(mapping, :every_other3, 'monthly_every_xth_day') + mapping = map(mapping, :every_count, 'monthly_day_of_week') + + mapping[:every_other2] = mapping[get_every_other2] + mapping = mapping.except('monthly_every_x_month').except('monthly_every_x_month2') + + mapping[:recurrence_selector] = get_recurrence_selector + + mapping + end + + def every_x_day? + @recurring_todo.recurrence_selector == 0 + end + + def every_xth_day? + @recurring_todo.recurrence_selector == 1 + end + + private + + def get_recurrence_selector + @selector=='monthly_every_x_day' ? 0 : 1 + end + + def get_every_other2 + get_recurrence_selector == 0 ? 'monthly_every_x_month' : 'monthly_every_x_month2' + end + + def valid_selector?(selector) + %w{monthly_every_x_day monthly_every_xth_day}.include?(selector) + end + + end + +end \ No newline at end of file diff --git a/app/models/recurring_todos/recurring_todos_builder.rb b/app/models/recurring_todos/recurring_todos_builder.rb new file mode 100644 index 00000000..f38b8ea4 --- /dev/null +++ b/app/models/recurring_todos/recurring_todos_builder.rb @@ -0,0 +1,104 @@ +module RecurringTodos + + class RecurringTodosBuilder + + attr_reader :builder, :project, :context, :tag_list, :user + + def initialize (user, attributes) + @user = user + @attributes = attributes + + parse_dates + parse_project + parse_context + + @builder = create_builder(attributes['recurring_period']) + end + + def create_builder(selector) + if %w{daily weekly monthly yearly}.include?(selector) + return eval("RecurringTodos::#{selector.capitalize}RecurringTodosBuilder.new(@user, @attributes)") + else + raise Exception.new("Unknown recurrence selector (#{selector})") + end + end + + def build + @builder.build + end + + def save + @project.save if @new_project_created + @context.save if @new_context_created + + return @builder.save + end + + def saved_recurring_todo + @builder.saved_recurring_todo + end + + def recurring_todo + @builder.recurring_todo + end + + def attributes + @builder.attributes + end + + private + + def parse_dates + %w{end_date start_from}.each {|date| @attributes[date] = @user.prefs.parse_date(@attributes[date])} + end + + def parse_project + if project_specified_by_name? + @project = @user.projects.where(:name => project_name).first + unless @project + @project = @user.projects.build(:name => project_name) + @new_project_created = true + end + else + @project = @attributes['project_id'].present? ? @user.projects.find(@attributes['project_id']) : nil + end + @attributes[:project] = @project + end + + def parse_context + if context_specified_by_name? + @context = @user.contexts.where(:name => context_name).first + unless @context + @context = @user.contexts.build(:name => context_name) + @new_context_created = true + end + else + @context = @attributes['context_id'].present? ? @user.contexts.find(@attributes['context_id']) : nil + end + @attributes[:context] = @context + end + + def project_specified_by_name? + return false if @attributes['project_id'].present? + return false if project_name.blank? + return false if project_name == 'None' + true + end + + def context_specified_by_name? + return false if @attributes['context_id'].present? + return false if context_name.blank? + true + end + + def project_name + @attributes['project_name'].strip unless @attributes['project_name'].nil? + end + + def context_name + @attributes['context_name'].strip unless @attributes['context_name'].nil? + end + + end + +end \ No newline at end of file diff --git a/app/models/recurring_todos/weekly_recurring_todos_builder.rb b/app/models/recurring_todos/weekly_recurring_todos_builder.rb new file mode 100644 index 00000000..59233b4e --- /dev/null +++ b/app/models/recurring_todos/weekly_recurring_todos_builder.rb @@ -0,0 +1,22 @@ +module RecurringTodos + + class WeeklyRecurringTodosBuilder < AbstractRecurringTodosBuilder + + def initialize(user, attributes) + super(user, attributes) + @pattern = WeeklyRepeatPattern.new(user, @filterred_attributes) + end + + def filter_attributes(attributes) + @filterred_attributes = filter_generic_attributes(attributes) + + weekly_attributes = %w{weekly_selector weekly_every_x_week} + %w{monday tuesday wednesday thursday friday saturday sunday}.each{|day| weekly_attributes << "weekly_return_#{day}"} + weekly_attributes.each{|key| @filterred_attributes[key] = attributes[key] if attributes.key?(key)} + + @filterred_attributes + end + + end + +end \ No newline at end of file diff --git a/app/models/recurring_todos/weekly_repeat_pattern.rb b/app/models/recurring_todos/weekly_repeat_pattern.rb new file mode 100644 index 00000000..2330be7a --- /dev/null +++ b/app/models/recurring_todos/weekly_repeat_pattern.rb @@ -0,0 +1,29 @@ +module RecurringTodos + + class WeeklyRepeatPattern < AbstractRepeatPattern + + def initialize(user, attributes) + super user, attributes + end + + def mapped_attributes + mapping = @attributes + + mapping = map(mapping, :every_other1, 'weekly_every_x_week') + + { monday: 1, tuesday: 2, wednesday: 3, thursday: 4, friday: 5, saturday: 6, sunday: 0 }.each{|day, index| mapping = map_day(mapping, :every_day, "weekly_return_#{day}", index)} + + mapping + end + + def map_day(mapping, key, source_key, index) + mapping[key] ||= ' ' # avoid nil + mapping[source_key] ||= ' ' # avoid nil + + mapping[key] = mapping[key][0, index] + mapping[source_key] + mapping[key][index+1, mapping[key].length] + mapping + end + + end + +end \ No newline at end of file diff --git a/app/models/recurring_todos/yearly_recurring_todos_builder.rb b/app/models/recurring_todos/yearly_recurring_todos_builder.rb new file mode 100644 index 00000000..89cc8a60 --- /dev/null +++ b/app/models/recurring_todos/yearly_recurring_todos_builder.rb @@ -0,0 +1,24 @@ +module RecurringTodos + + class YearlyRecurringTodosBuilder < AbstractRecurringTodosBuilder + + def initialize(user, attributes) + super(user, attributes) + @pattern = YearlyRepeatPattern.new(user, @filterred_attributes) + end + + def filter_attributes(attributes) + @filterred_attributes = filter_generic_attributes(attributes) + + %w{ yearly_selector yearly_month_of_year yearly_month_of_year2 + yearly_every_x_day yearly_every_xth_day yearly_day_of_week + }.each do |key| + @filterred_attributes[key] = attributes[key] if attributes.key?(key) + end + + @filterred_attributes + end + + end + +end \ No newline at end of file diff --git a/app/models/recurring_todos/yearly_repeat_pattern.rb b/app/models/recurring_todos/yearly_repeat_pattern.rb new file mode 100644 index 00000000..d475d497 --- /dev/null +++ b/app/models/recurring_todos/yearly_repeat_pattern.rb @@ -0,0 +1,46 @@ +module RecurringTodos + + class YearlyRepeatPattern < AbstractRepeatPattern + + def initialize(user, attributes) + super user, attributes + @selector = get_selector('yearly_selector') + end + + def mapped_attributes + mapping = @attributes + + mapping[:recurrence_selector] = get_recurrence_selector + + mapping[:every_other2] = mapping[get_every_other2] + mapping = mapping.except('yearly_month_of_year').except('yearly_month_of_year2') + + mapping = map(mapping, :every_other1, 'yearly_every_x_day') + mapping = map(mapping, :every_other3, 'yearly_every_xth_day') + mapping = map(mapping, :every_count, 'yearly_day_of_week') + + mapping + end + + private + + def get_recurrence_selector + @selector=='yearly_every_x_day' ? 0 : 1 + end + + def get_every_other2 + case get_recurrence_selector + when 0 + 'yearly_month_of_year' + when 1 + 'yearly_month_of_year2' + end + end + + def valid_selector?(selector) + %w{yearly_every_x_day yearly_every_xth_day}.include?(selector) + end + + end + +end \ No newline at end of file diff --git a/lib/todo_from_recurring_todo.rb b/lib/todo_from_recurring_todo.rb index a5bff067..531f0db0 100644 --- a/lib/todo_from_recurring_todo.rb +++ b/lib/todo_from_recurring_todo.rb @@ -1,5 +1,6 @@ class TodoFromRecurringTodo attr_reader :user, :recurring_todo, :todo + def initialize(user, recurring_todo) @user = user @recurring_todo = recurring_todo @@ -32,9 +33,9 @@ class TodoFromRecurringTodo def attributes { :description => recurring_todo.description, - :notes => recurring_todo.notes, - :project_id => recurring_todo.project_id, - :context_id => recurring_todo.context_id + :notes => recurring_todo.notes, + :project_id => recurring_todo.project_id, + :context_id => recurring_todo.context_id } end diff --git a/test/controllers/recurring_todos_controller_test.rb b/test/controllers/recurring_todos_controller_test.rb index 30c456a7..0a8c03df 100644 --- a/test/controllers/recurring_todos_controller_test.rb +++ b/test/controllers/recurring_todos_controller_test.rb @@ -62,6 +62,52 @@ class RecurringTodosControllerTest < ActionController::TestCase assert_equal orig_todo_count+1, Todo.count end + def test_new_recurring_todo_handles_attribs_outside_rec_todo + login_as(:admin_user) + + # check new rec todo is not there + assert_nil RecurringTodo.where(:description => "new recurring pattern").first + + put :create, + "context_name"=>"library", + "project_name"=>"Build a working time machine", + "recurring_todo" => + { + "daily_every_x_days"=>"1", + "daily_selector"=>"daily_every_x_day", + "description"=>"new recurring pattern", + "end_date" => "31/08/2010", + "ends_on" => "ends_on_end_date", + "monthly_day_of_week" => "1", + "monthly_every_x_day" => "18", + "monthly_every_x_month2" => "1", + "monthly_every_x_month" => "1", + "monthly_every_xth_day"=>"1", + "monthly_selector"=>"monthly_every_x_day", + "notes"=>"with some notes", + "number_of_occurences" => "", + "recurring_period"=>"yearly", + "recurring_show_days_before"=>"10", + "recurring_target"=>"due_date", + "recurring_show_always" => "1", + "start_from"=>"18/08/2008", + "weekly_every_x_week"=>"1", + "weekly_return_monday"=>"m", + "yearly_day_of_week"=>"1", + "yearly_every_x_day"=>"8", + "yearly_every_xth_day"=>"1", + "yearly_month_of_year2"=>"8", + "yearly_month_of_year"=>"6", + "yearly_selector"=>"yearly_every_x_day" + }, + "tag_list"=>"one, two, three, four", :format => :js + + new_rec_todo = RecurringTodo.where(:description => "new recurring pattern").first + + assert_not_nil new_rec_todo + + end + def test_recurring_todo_toggle_check # the test fixtures did add recurring_todos but not the corresponding todos, # so we check complete and uncheck to force creation of a todo from the diff --git a/test/models/recurring_todos/abstract_recurring_todos_builder_test.rb b/test/models/recurring_todos/abstract_recurring_todos_builder_test.rb new file mode 100644 index 00000000..30e2ba1f --- /dev/null +++ b/test/models/recurring_todos/abstract_recurring_todos_builder_test.rb @@ -0,0 +1,122 @@ +require_relative '../../test_helper' + +module RecurringTodos + + class AbstractRecurringTodosBuilderTest < ActiveSupport::TestCase + fixtures :users + + def setup + @admin = users(:admin_user) + end + + def test_filter_attributes_should_throw_exception + attributes = { + 'recurring_period' => "daily", + 'description' => "test", + 'tag_list' => "tag, this, that", + 'context_name' => "my new context", + 'daily_selector' => 'daily_every_work_day', + 'target' => 'due_date', + 'show_always' => true, + 'start_from' => '01/01/01', + 'ends_on' => 'no_end_date' + } + + assert_raise(Exception, "should have exception since we are using abstract builder") do + builder = AbstractRecurringTodosBuilder.new(@admin, attributes) + end + end + + def test_tags_should_be_filled_or_empty_string + # given attributes with filled tag_list + attributes = { + 'recurring_period' => 'daily', + 'daily_selector' => 'daily_every_work_day', + 'tag_list' => "tag, this, that" + } + + builder = RecurringTodosBuilder.new(@admin, attributes) + assert_equal "tag, this, that", builder.attributes[:tag_list] + + # given attributes without tag_list + attributes = { + 'recurring_period' => 'daily', + 'daily_selector' => 'daily_every_work_day', + } + + builder = RecurringTodosBuilder.new(@admin, attributes) + assert_equal "", builder.attributes[:tag_list] + + # given attributes with nil tag_list + attributes = { + 'recurring_period' => 'daily', + 'daily_selector' => 'daily_every_work_day', + 'tag_list' => nil + } + + builder = RecurringTodosBuilder.new(@admin, attributes) + assert_equal "", builder.attributes[:tag_list] + + # given attributes with empty tag_list ==> should be stripped + attributes = { + 'recurring_period' => 'daily', + 'daily_selector' => 'daily_every_work_day', + 'tag_list' => " " + } + + builder = RecurringTodosBuilder.new(@admin, attributes) + assert_equal "", builder.attributes[:tag_list] + end + + def test_tags_should_be_saved + attributes = { + 'recurring_period' => "daily", + 'description' => "test", + 'tag_list' => "tag, this, that", + 'context_name' => "my new context", + 'daily_selector' => 'daily_every_work_day', + 'recurring_target' => 'show_from_date', + 'show_always' => true, + 'start_from' => '01/01/01', + 'ends_on' => 'no_end_date' + } + + builder = RecurringTodosBuilder.new(@admin, attributes) + assert builder.save, "it should be saved" + assert_equal "tag, that, this", builder.saved_recurring_todo.tag_list, "tags should be saved" + + attributes['tag_list'] = '' # clear tag_list + + builder = RecurringTodosBuilder.new(@admin, attributes) + assert !builder.tag_list.present?, "tag list should not be present" + assert builder.save, "it should be saved" + assert_equal "", builder.saved_recurring_todo.tag_list, "tag list should be empty" + end + + + def test_saved_should_raise_exception_on_validation_errors + attributes = { + 'recurring_period' => "daily", + 'description' => "test", + 'tag_list' => "tag, this, that", + 'context_name' => "my new context", + 'daily_selector' => 'daily_every_work_day', + 'recurring_target' => 'due_date', + 'show_always' => true, + 'start_from' => '01/01/01', + 'ends_on' => 'no_end_date_error' # invalid end_on value + } + + # creating builder should not raise exception + builder = RecurringTodosBuilder.new(@admin, attributes) + builder.build + + assert !builder.recurring_todo.valid?, "model should have validation errors" + + assert !builder.save, "should not be able to save because of validation errors" + assert_raise(Exception, "should have exception since there is no saved recurring todo"){ builder.saved_recurring_todo } + end + + end + +end \ No newline at end of file diff --git a/test/models/recurring_todos/abstract_repeat_pattern_test.rb b/test/models/recurring_todos/abstract_repeat_pattern_test.rb new file mode 100644 index 00000000..aa1067a2 --- /dev/null +++ b/test/models/recurring_todos/abstract_repeat_pattern_test.rb @@ -0,0 +1,46 @@ +require_relative '../../test_helper' + +module RecurringTodos + + class AbstractRepeatPatternTest < ActiveSupport::TestCase + fixtures :users + + class TestRepeatPattern < AbstractRepeatPattern + def valid_selector?(selector) + true + end + end + + def setup + @admin = users(:admin_user) + end + + def test_map_removes_mapped_key + attributes = { :source => "value"} + + arp = AbstractRepeatPattern.new(@admin, attributes) + attributes = arp.map(attributes, :target, :source) + + assert_equal "value", attributes[:target] + assert_nil attributes[:source] + assert !attributes.key?(:source) + end + + def test_get_selector_removes_selector_from_hash + attributes = { :selector => "weekly" } + arp = TestRepeatPattern.new(@admin, attributes) + + assert "weekly", arp.get_selector(:selector) + assert !arp.attributes.key?(:selector) + end + + def test_get_selector_raises_exception_when_missing_selector + attributes = { } + arp = TestRepeatPattern.new(@admin, attributes) + + assert_raise(Exception, "should raise exception when recurrence selector is missing"){ arp.get_selector(:selector) } + end + + end + +end \ No newline at end of file diff --git a/test/models/recurring_todos/daily_recurring_todos_builder_test.rb b/test/models/recurring_todos/daily_recurring_todos_builder_test.rb new file mode 100644 index 00000000..34e1b192 --- /dev/null +++ b/test/models/recurring_todos/daily_recurring_todos_builder_test.rb @@ -0,0 +1,35 @@ +require_relative '../../test_helper' + +module RecurringTodos + + class DailyRecurringTodosBuilderTest < ActiveSupport::TestCase + fixtures :users + + def setup + @admin = users(:admin_user) + end + + def test_pattern_is_daily + object = RecurringTodosBuilder.new(@admin, { 'recurring_period' => 'daily', 'daily_selector' => 'daily_every_x_day' }) + assert object.builder.is_a? DailyRecurringTodosBuilder + end + + def test_filter_non_daily_attributes + attributes = { + 'recurring_period' => 'daily', + 'description' => 'a repeating todo', # generic + 'daily_selector' => 'daily_every_x_day', # daily specific + 'bla_bla' => 'go away' # irrelevant for daily + } + + result = RecurringTodosBuilder.new(@admin, attributes).attributes + + assert_nil result['bla_bla'], "bla_bla should be filtered" + assert_nil result[:bla_bla], "bla_bla should be filtered" + assert_equal false, result[:only_work_days], "daily attributes should be preserved" + assert_equal "a repeating todo", result[:description], "description should be preserved" + end + + end + +end \ No newline at end of file diff --git a/test/models/recurring_todos/daily_repeat_pattern_test.rb b/test/models/recurring_todos/daily_repeat_pattern_test.rb new file mode 100644 index 00000000..9c91d569 --- /dev/null +++ b/test/models/recurring_todos/daily_repeat_pattern_test.rb @@ -0,0 +1,58 @@ +require_relative '../../test_helper' + +module RecurringTodos + + class DailyRepeatPatternTest < ActiveSupport::TestCase + fixtures :users + + def setup + @admin = users(:admin_user) + end + + def test_valid_selector + attributes = { + 'recurring_period' => 'daily' + } + + # should not raise + %w{daily_every_x_day daily_every_work_day}.each do |selector| + attributes['daily_selector'] = selector + DailyRepeatPattern.new(@admin, attributes) + end + + # should raise + attributes = { + 'recurring_period' => 'daily', + 'daily_selector' => 'wrong value' + } + + # should raise + assert_raise(Exception, "should have exception since daily_selector has wrong value"){ DailyRepeatPattern.new(@admin, attributes) } + end + + def test_mapping_of_attributes + attributes = { + 'recurring_period' => 'daily', + 'description' => 'a repeating todo', # generic + 'daily_selector' => 'daily_every_x_day', # daily specific --> mapped to only_work_days=false + 'daily_every_x_days' => '5' # mapped to every_other1 + } + + pattern = DailyRepeatPattern.new(@admin, attributes) + + assert_equal '5', pattern.mapped_attributes[:every_other1], "every_other1 should be set to daily_every_x_days" + assert_equal false, pattern.mapped_attributes[:only_work_days], "only_work_days should be set to false for daily_every_x_day" + + attributes = { + 'recurring_period' => 'daily', + 'description' => 'a repeating todo', # generic + 'daily_selector' => 'daily_every_work_day', # daily specific --> mapped to only_work_days=true + } + + pattern = DailyRepeatPattern.new(@admin, attributes) + assert_equal true, pattern.mapped_attributes[:only_work_days] + end + + end + +end \ No newline at end of file diff --git a/test/models/recurring_todos/monthly_recurring_todos_builder_test.rb b/test/models/recurring_todos/monthly_recurring_todos_builder_test.rb new file mode 100644 index 00000000..8f0ceeeb --- /dev/null +++ b/test/models/recurring_todos/monthly_recurring_todos_builder_test.rb @@ -0,0 +1,35 @@ +require_relative '../../test_helper' + +module RecurringTodos + + class MonthlyRecurringTodosBuilderTest < ActiveSupport::TestCase + fixtures :users + + def setup + @admin = users(:admin_user) + end + + def test_pattern_is_monthly + object = RecurringTodosBuilder.new(@admin, { 'recurring_period' => 'monthly', 'monthly_selector' => 'monthly_every_x_day' }) + assert object.builder.is_a?(MonthlyRecurringTodosBuilder), "Builder should be of type MonthlyRecurringTodosBuilder" + end + + def test_filter_non_daily_attributes + attributes = { + 'recurring_period' => 'monthly', + 'description' => 'a repeating todo', # generic + 'monthly_selector' => 'monthly_every_x_day', # monthly specific + 'monthly_every_x_day' => 5, # should be preserved as :every_other1 + 'bla_bla' => 'go away' # irrelevant for daily + } + + result = RecurringTodosBuilder.new(@admin, attributes).attributes + + assert_nil result['bla_bla'], "bla_bla should be filtered" + assert_nil result[:bla_bla], "bla_bla should be filtered" + assert_equal 5, result[:every_other1], "should be preserved" + end + + end + +end \ No newline at end of file diff --git a/test/models/recurring_todos/monthly_repeat_pattern_test.rb b/test/models/recurring_todos/monthly_repeat_pattern_test.rb new file mode 100644 index 00000000..6e963ff0 --- /dev/null +++ b/test/models/recurring_todos/monthly_repeat_pattern_test.rb @@ -0,0 +1,74 @@ +require_relative '../../test_helper' + +module RecurringTodos + + class MonthlyRepeatPatternTest < ActiveSupport::TestCase + fixtures :users + + def setup + @admin = users(:admin_user) + end + + def test_valid_selector + attributes = { + 'recurring_period' => 'monthly' + } + + # should not raise + %w{monthly_every_x_day monthly_every_xth_day}.each do |selector| + attributes['monthly_selector'] = selector + MonthlyRepeatPattern.new(@admin, attributes) + end + + # should raise + attributes = { + 'recurring_period' => 'monthly', + 'monthly_selector' => 'wrong value' + } + + # should raise + assert_raise(Exception, "should have exception since monthly_selector has wrong value"){ MonthlyRepeatPattern.new(@admin, attributes) } + end + + def test_mapping_of_attributes + attributes = { + 'recurring_period' => 'monthly', + 'description' => 'a repeating todo', # generic + 'monthly_selector' => 'monthly_every_x_day', # monthly specific + 'monthly_every_x_day' => '5', # mapped to :every_other1 + 'monthly_every_xth_day' => '7', # mapped to :every_other3 + 'monthly_day_of_week' => 3, # mapped to :every_count + 'monthly_every_x_month' => '10', # mapped to :every_other2 + 'monthly_every_x_month2' => '20' # not mapped + } + + pattern = MonthlyRepeatPattern.new(@admin, attributes) + assert_equal 0, pattern.mapped_attributes[:recurrence_selector], "selector should be 0 for monthly_every_x_day" + assert_equal '5', pattern.mapped_attributes[:every_other1], "every_other1 should be set to monthly_every_x_days" + assert_equal '10', pattern.mapped_attributes[:every_other2], "every_other2 should be set to monthly_every_x_month when selector is monthly_every_x_day (=0)" + assert_equal '7', pattern.mapped_attributes[:every_other3], "every_other3 should be set to monthly_every_xth_day" + assert_equal 3, pattern.mapped_attributes[:every_count], "every_count should be set to monthly_day_of_week" + + pattern.build_recurring_todo + assert pattern.every_x_day?, "every_x_day? should say true for selector monthly_every_x_day" + + attributes = { + 'recurring_period' => 'monthly', + 'description' => 'a repeating todo', # generic + 'monthly_selector' => 'monthly_every_xth_day', # monthly specific + 'monthly_every_x_day' => '5', # mapped to :every_other1 + 'monthly_every_x_month' => '10', # not mapped + 'monthly_every_x_month2' => '20' # mapped to :every_other2 + } + + pattern = MonthlyRepeatPattern.new(@admin, attributes) + assert_equal 1, pattern.mapped_attributes[:recurrence_selector], "selector should be 1 for monthly_every_xth_day" + assert_equal '20', pattern.mapped_attributes[:every_other2], "every_other2 should be set to monthly_every_x_month2 when selector is monthly_every_xth_day (=0)" + + pattern.build_recurring_todo + assert pattern.every_xth_day?, "every_xth_day? should say true for selector monthly_every_xth_day" + end + + end + +end \ No newline at end of file diff --git a/test/models/recurring_todos/recurring_todos_builder_test.rb b/test/models/recurring_todos/recurring_todos_builder_test.rb new file mode 100644 index 00000000..fe73634c --- /dev/null +++ b/test/models/recurring_todos/recurring_todos_builder_test.rb @@ -0,0 +1,122 @@ +require_relative '../../test_helper' + +module RecurringTodos + + class RecurringTodosBuilderTest < ActiveSupport::TestCase + fixtures :users + + def setup + @admin = users(:admin_user) + end + + def test_create_builder_needs_selector + assert_raise(Exception){ builder = RecurringTodosBuilder.new(@admin, {}) } + end + + def test_create_builder_uses_selector + builder = RecurringTodosBuilder.new(@admin, {'recurring_period' => "daily", 'daily_selector' => 'daily_every_work_day'}).builder + assert builder.is_a?(DailyRecurringTodosBuilder) + + builder = RecurringTodosBuilder.new(@admin, {'recurring_period' => "weekly"}).builder + assert builder.is_a?(WeeklyRecurringTodosBuilder) + + builder = RecurringTodosBuilder.new(@admin, {'recurring_period' => "monthly", 'monthly_selector' => 'monthly_every_x_day'}).builder + assert builder.is_a?(MonthlyRecurringTodosBuilder) + + builder = RecurringTodosBuilder.new(@admin, {'recurring_period' => "yearly", 'yearly_selector' => 'yearly_every_x_day'}).builder + assert builder.is_a?(YearlyRecurringTodosBuilder) + end + + def test_dates_are_parsed + builder = RecurringTodosBuilder.new(@admin, { + 'recurring_period' => "daily", + 'daily_selector' => 'daily_every_work_day', + 'start_from' => "01/01/01", + 'end_date' => '05/05/05' + }) + + assert builder.attributes[:start_from].is_a?(ActiveSupport::TimeWithZone), "Dates should be parsed to ActiveSupport::TimeWithZone class" + assert builder.attributes[:end_date].is_a?(ActiveSupport::TimeWithZone), "Dates should be parsed to ActiveSupport::TimeWithZone class" + end + + def test_exisisting_project_is_used + # test by project_name + builder = RecurringTodosBuilder.new(@admin, { + 'recurring_period' => "daily", + 'project_name' => @admin.projects.first.name, + 'daily_selector' => 'daily_every_work_day'}) + + assert_equal @admin.projects.first, builder.project + + # test by project_id + builder = RecurringTodosBuilder.new(@admin, { + 'recurring_period' => "daily", + 'daily_selector' => 'daily_every_work_day', + 'project_id' => @admin.projects.first.id}) + + assert_equal @admin.projects.first, builder.project + end + + def test_not_exisisting_project_is_created + builder = RecurringTodosBuilder.new(@admin, { + 'recurring_period' => "daily", + 'project_name' => "my new project", + 'daily_selector' => 'daily_every_work_day', + 'recurring_target' => 'due_date'}) + + assert_equal "my new project", builder.project.name, "project should exist" + assert !builder.project.persisted?, "new project should not be persisted before save" + + builder.save + assert builder.project.persisted?, "new project should be persisted after save" + end + + def test_exisisting_context_is_used + builder = RecurringTodosBuilder.new(@admin, { + 'recurring_period' => "daily", + 'context_name' => @admin.contexts.first.name, + 'daily_selector' => 'daily_every_work_day'}) + + assert_equal @admin.contexts.first, builder.context + + builder = RecurringTodosBuilder.new(@admin, { + 'recurring_period' => "daily", + 'daily_selector' => 'daily_every_work_day', + 'context_id' => @admin.contexts.first.id}) + + assert_equal @admin.contexts.first, builder.context + end + + def test_not_exisisting_context_is_created + builder = RecurringTodosBuilder.new(@admin, { + 'recurring_period' => "daily", + 'context_name' => "my new context", + 'daily_selector' => 'daily_every_work_day', + 'recurring_target' => 'due_date'}) + + assert_equal "my new context", builder.context.name, "context should exist" + assert !builder.context.persisted?, "new context should not be persisted before save" + + builder.save + assert builder.context.persisted?, "new context should be persisted after save" + end + + def test_project_is_optional + builder = RecurringTodosBuilder.new(@admin, { + 'recurring_period' => "daily", + 'description' => "test", + 'context_name' => "my new context", + 'daily_selector' => 'daily_every_work_day', + 'recurring_target' => 'show_from_date', + 'show_always' => true, + 'start_from' => '01/01/01', + 'ends_on' => 'no_end_date'}) + + assert_nil builder.project, "project should not exist" + builder.save + assert_nil builder.saved_recurring_todo.project + end + + end + +end \ No newline at end of file diff --git a/test/models/recurring_todos/weekly_recurring_todos_builder_test.rb b/test/models/recurring_todos/weekly_recurring_todos_builder_test.rb new file mode 100644 index 00000000..76a5286b --- /dev/null +++ b/test/models/recurring_todos/weekly_recurring_todos_builder_test.rb @@ -0,0 +1,35 @@ +require_relative '../../test_helper' + +module RecurringTodos + + class WeeklyRecurringTodosBuilderTest < ActiveSupport::TestCase + fixtures :users + + def setup + @admin = users(:admin_user) + end + + def test_pattern_is_weekly + object = RecurringTodosBuilder.new(@admin, { 'recurring_period' => 'weekly' }) + assert object.builder.is_a? WeeklyRecurringTodosBuilder + end + + def test_filter_non_daily_attributes + attributes = { + 'recurring_period' => 'weekly', + 'description' => 'a repeating todo', # generic + 'weekly_return_monday' => 'm', # weekly specific + 'bla_bla' => 'go away' # irrelevant + } + + result = RecurringTodosBuilder.new(@admin, attributes).attributes + + assert_nil result['bla_bla'], "bla_bla should be filtered" + assert_nil result[:bla_bla], "bla_bla should be filtered" + assert_equal ' m ', result[:every_day], "weekly attributes should be preserved" + assert_equal "a repeating todo", result[:description], "description should be preserved" + end + + end + +end \ No newline at end of file diff --git a/test/models/recurring_todos/weekly_repeat_pattern_test.rb b/test/models/recurring_todos/weekly_repeat_pattern_test.rb new file mode 100644 index 00000000..45d713e9 --- /dev/null +++ b/test/models/recurring_todos/weekly_repeat_pattern_test.rb @@ -0,0 +1,52 @@ +require_relative '../../test_helper' + +module RecurringTodos + + class WeeklyRepeatPatternTest < ActiveSupport::TestCase + fixtures :users + + def setup + @admin = users(:admin_user) + end + + def test_mapping_of_attributes + attributes = { + 'recurring_period' => 'weekly', + 'description' => 'a repeating todo', # generic + 'weekly_every_x_week' => '5', # mapped to every_other1 + 'weekly_return_monday' => 'm' + } + + pattern = WeeklyRepeatPattern.new(@admin, attributes) + + assert_equal '5', pattern.mapped_attributes[:every_other1], "every_other1 should be set to weekly_every_x_week" + assert_equal ' m ', pattern.mapped_attributes[:every_day], "weekly_return_ should be mapped to :every_day in format 'smtwtfs'" + end + + def test_map_day + attributes = { + 'recurring_period' => 'weekly', + 'description' => 'a repeating todo', # generic + 'weekly_every_x_week' => '5' # mapped to every_other1 + } + + pattern = WeeklyRepeatPattern.new(@admin, attributes) + assert_equal ' ', pattern.mapped_attributes[:every_day], "all days should be empty in :every_day" + + # add all days + { sunday: 's', monday: 'm', tuesday: 't', wednesday: 'w', thursday: 't', friday: 'f', saturday: 's' }.each do |day, short| + attributes["weekly_return_#{day}"] = short + end + + pattern = WeeklyRepeatPattern.new(@admin, attributes) + assert_equal 'smtwtfs', pattern.mapped_attributes[:every_day], "all days should be filled in :every_day" + + # remove wednesday + attributes = attributes.except('weekly_return_wednesday') + pattern = WeeklyRepeatPattern.new(@admin, attributes) + assert_equal 'smt tfs', pattern.mapped_attributes[:every_day], "only wednesday should be empty in :every_day" + end + + end + +end \ No newline at end of file diff --git a/test/models/recurring_todos/yearly_recurring_todos_builder_test.rb b/test/models/recurring_todos/yearly_recurring_todos_builder_test.rb new file mode 100644 index 00000000..2579336e --- /dev/null +++ b/test/models/recurring_todos/yearly_recurring_todos_builder_test.rb @@ -0,0 +1,36 @@ +require_relative '../../test_helper' + +module RecurringTodos + + class YearlyRecurringTodosBuilderTest < ActiveSupport::TestCase + fixtures :users + + def setup + @admin = users(:admin_user) + end + + def test_pattern_is_yearly + object = RecurringTodosBuilder.new(@admin, { 'recurring_period' => 'yearly', 'yearly_selector' => 'yearly_every_x_day' }) + assert object.builder.is_a? YearlyRecurringTodosBuilder + end + + def test_filter_non_daily_attributes + attributes = { + 'recurring_period' => 'yearly', + 'description' => 'a repeating todo', # generic + 'yearly_selector' => 'yearly_every_x_day', # daily specific + 'yearly_month_of_year' => '1', # mapped to evert_other2 because yearly_selector is yearly_every_x_day + 'bla_bla' => 'go away' # irrelevant for daily + } + + result = RecurringTodosBuilder.new(@admin, attributes).attributes + + assert_nil result['bla_bla'], "bla_bla should be filtered" + assert_nil result[:bla_bla], "bla_bla should be filtered" + assert_equal '1', result[:every_other2], "yearly attributes should be preserved" + assert_equal "a repeating todo", result[:description], "description should be preserved" + end + + end + +end \ No newline at end of file diff --git a/test/models/recurring_todos/yearly_repeat_pattern_test.rb b/test/models/recurring_todos/yearly_repeat_pattern_test.rb new file mode 100644 index 00000000..4836f57a --- /dev/null +++ b/test/models/recurring_todos/yearly_repeat_pattern_test.rb @@ -0,0 +1,66 @@ +require_relative '../../test_helper' + +module RecurringTodos + + class YearlyRepeatPatternTest < ActiveSupport::TestCase + fixtures :users + + def setup + @admin = users(:admin_user) + end + + def test_valid_selector + attributes = { + 'recurring_period' => 'yearly' + } + + # should not raise + %w{yearly_every_x_day yearly_every_xth_day}.each do |selector| + attributes['yearly_selector'] = selector + YearlyRepeatPattern.new(@admin, attributes) + end + + # should raise + attributes = { + 'recurring_period' => 'yearly', + 'yearly_selector' => 'wrong value' + } + + # should raise + assert_raise(Exception, "should have exception since yearly_selector has wrong value"){ YearlyRepeatPattern.new(@admin, attributes) } + end + + def test_mapping_of_attributes + attributes = { + 'recurring_period' => 'yearly', + 'description' => 'a repeating todo', # generic + 'yearly_selector' => 'yearly_every_x_day', # yearly specific + 'yearly_every_x_day' => '5', # mapped to every_other1 + 'yearly_every_xth_day' => '7', # mapped to every_other3 + 'yearly_day_of_week' => '3', # mapped to every_count + 'yearly_month_of_year' => '1', # mapped to evert_other2 because yearly_selector is yearly_every_x_day + 'yearly_month_of_year2' => '2' # ignored because yearly_selector is yearly_every_x_day + } + + pattern = YearlyRepeatPattern.new(@admin, attributes) + + assert_equal '5', pattern.mapped_attributes[:every_other1], "every_other1 should be set to yearly_every_x_day" + assert_equal '1', pattern.mapped_attributes[:every_other2], "every_other2 should be set to yearly_month_of_year because selector is yearly_every_x_day" + assert_equal '7', pattern.mapped_attributes[:every_other3], "every_other3 should be set to yearly_every_xth_day" + assert_equal '3', pattern.mapped_attributes[:every_count], "every_count should be set to yearly_day_of_week" + + attributes = { + 'recurring_period' => 'yearly', + 'description' => 'a repeating todo', # generic + 'yearly_selector' => 'yearly_every_xth_day', # daily specific --> mapped to only_work_days=false + 'yearly_month_of_year' => '1', # ignored because yearly_selector is yearly_every_xth_day + 'yearly_month_of_year2' => '2' # mapped to evert_other2 because yearly_selector is yearly_every_xth_day + } + + pattern = YearlyRepeatPattern.new(@admin, attributes) + assert_equal '2', pattern.mapped_attributes[:every_other2], "every_other2 should be set to yearly_month_of_year2 because selector is yearly_every_xth_day" + end + + end + +end \ No newline at end of file From 00af159be7d12aaeabde76df7b0f6162df590324 Mon Sep 17 00:00:00 2001 From: Reinier Balt Date: Mon, 27 Jan 2014 20:28:57 +0100 Subject: [PATCH 06/24] remove unused code and refactor builder to remove duplication --- app/controllers/recurring_todos_controller.rb | 52 ----------------- .../recurring_todos_builder.rb | 58 ++++++++++++------- 2 files changed, 38 insertions(+), 72 deletions(-) diff --git a/app/controllers/recurring_todos_controller.rb b/app/controllers/recurring_todos_controller.rb index 5452038b..50b6b8bb 100644 --- a/app/controllers/recurring_todos_controller.rb +++ b/app/controllers/recurring_todos_controller.rb @@ -193,58 +193,6 @@ class RecurringTodosController < ApplicationController end end - class RecurringTodoCreateParamsHelper - - def initialize(params, recurring_todo_params) - @params = params['request'] || params - @attributes = recurring_todo_params - - # make sure all selectors (recurring_period, recurrence_selector, - # daily_selector, monthly_selector and yearly_selector) are first in hash - # so that they are processed first by the model - @selector_attributes = { - 'recurring_period' => @attributes['recurring_period'], - 'daily_selector' => @attributes['daily_selector'], - 'monthly_selector' => @attributes['monthly_selector'], - 'yearly_selector' => @attributes['yearly_selector'] - } - end - - def attributes - @attributes - end - - def selector_attributes - return @selector_attributes - end - - def project_name - @params['project_name'].strip unless @params['project_name'].nil? - end - - def context_name - @params['context_name'].strip unless @params['context_name'].nil? - end - - def tag_list - @params['tag_list'] - end - - def project_specified_by_name? - return false if @attributes['project_id'].present? - return false if project_name.blank? - return false if project_name == 'None' - true - end - - def context_specified_by_name? - return false if @attributes['context_id'].present? - return false if context_name.blank? - true - end - - end - private def recurring_todo_params diff --git a/app/models/recurring_todos/recurring_todos_builder.rb b/app/models/recurring_todos/recurring_todos_builder.rb index f38b8ea4..ccbad04f 100644 --- a/app/models/recurring_todos/recurring_todos_builder.rb +++ b/app/models/recurring_todos/recurring_todos_builder.rb @@ -53,29 +53,47 @@ module RecurringTodos end def parse_project - if project_specified_by_name? - @project = @user.projects.where(:name => project_name).first - unless @project - @project = @user.projects.build(:name => project_name) - @new_project_created = true - end - else - @project = @attributes['project_id'].present? ? @user.projects.find(@attributes['project_id']) : nil - end - @attributes[:project] = @project + @project, @new_project_created = parse(:project, @user.projects, project_name) end def parse_context - if context_specified_by_name? - @context = @user.contexts.where(:name => context_name).first - unless @context - @context = @user.contexts.build(:name => context_name) - @new_context_created = true - end + @context, @new_context_created = parse(:context, @user.contexts, context_name) + end + + def parse(object_type, relation, name) + object = nil + new_object_created = false + + if specified_by_name?(object_type) + # find or create context or project by given name + object, new_object_created = find_or_create_by_name(relation, name) else - @context = @attributes['context_id'].present? ? @user.contexts.find(@attributes['context_id']) : nil + # find context or project by its id + object = attribute_with_id_of(object_type).present? ? relation.find(attribute_with_id_of(object_type)) : nil end - @attributes[:context] = @context + @attributes[object_type] = object + return object, new_object_created + end + + def attribute_with_id_of(object_type) + map = { project: 'project_id', context: 'context_id' } + @attributes[map[object_type]] + end + + def find_or_create_by_name(relation, name) + new_object_created = false + + object = relation.where(:name => name).first + unless object + object = relation.build(:name => name) + new_object_created = true + end + + return object, new_object_created + end + + def specified_by_name?(object_type) + self.send("#{object_type}_specified_by_name?") end def project_specified_by_name? @@ -92,11 +110,11 @@ module RecurringTodos end def project_name - @attributes['project_name'].strip unless @attributes['project_name'].nil? + @attributes['project_name'].try(:strip) end def context_name - @attributes['context_name'].strip unless @attributes['context_name'].nil? + @attributes['context_name'].try(:strip) end end From c2c67f1640304df652adafa21212e95f6fb1fb8f Mon Sep 17 00:00:00 2001 From: Reinier Balt Date: Mon, 3 Feb 2014 10:48:21 +0100 Subject: [PATCH 07/24] use new model to handle updating of recurring todos --- app/controllers/recurring_todos_controller.rb | 104 +++++++----------- .../abstract_recurring_todos_builder.rb | 27 ++++- .../abstract_repeat_pattern.rb | 9 +- .../daily_recurring_todos_builder.rb | 7 +- .../monthly_recurring_todos_builder.rb | 10 +- .../recurring_todos_builder.rb | 6 +- .../weekly_recurring_todos_builder.rb | 11 +- .../yearly_recurring_todos_builder.rb | 14 +-- .../recurring_todos_controller_test.rb | 26 +++++ .../abstract_recurring_todos_builder_test.rb | 10 +- .../recurring_todos_builder_test.rb | 33 +++++- .../weekly_recurring_todos_builder_test.rb | 13 +++ 12 files changed, 166 insertions(+), 104 deletions(-) diff --git a/app/controllers/recurring_todos_controller.rb b/app/controllers/recurring_todos_controller.rb index 50b6b8bb..9190d893 100644 --- a/app/controllers/recurring_todos_controller.rb +++ b/app/controllers/recurring_todos_controller.rb @@ -3,7 +3,7 @@ class RecurringTodosController < ApplicationController helper :todos, :recurring_todos append_before_filter :init, :only => [:index, :new, :edit, :create] - append_before_filter :get_recurring_todo_from_param, :only => [:destroy, :toggle_check, :toggle_star, :edit, :update] + append_before_filter :get_recurring_todo_from_param, :only => [:destroy, :toggle_check, :toggle_star, :edit] def index @page_title = t('todos.recurring_actions_title') @@ -43,62 +43,15 @@ class RecurringTodosController < ApplicationController end def update - # TODO: write tests for updating - @recurring_todo.tag_with(params[:edit_recurring_todo_tag_list]) if params[:edit_recurring_todo_tag_list] + @recurring_todo = current_user.recurring_todos.find(params[:id]) + @original_item_context_id = @recurring_todo.context_id @original_item_project_id = @recurring_todo.project_id - # we needed to rename the recurring_period selector in the edit form because - # the form for a new recurring todo and the edit form are on the same page. - # Same goes for start_from and end_date - params['recurring_todo']['recurring_period']=params['recurring_edit_todo']['recurring_period'] - params['recurring_todo']['end_date']=parse_date_per_user_prefs(params['recurring_todo_edit_end_date']) - params['recurring_todo']['start_from']=parse_date_per_user_prefs(params['recurring_todo_edit_start_from']) + updater = RecurringTodos::RecurringTodosBuilder.new(current_user, edit_recurring_todo_params) - # update project - if params['recurring_todo']['project_id'].blank? && !params['project_name'].nil? - if params['project_name'] == 'None' - project = Project.null_object - else - project = current_user.projects.where(:name => params['project_name'].strip) - unless project - project = current_user.projects.build - project.name = params['project_name'].strip - project.save - @new_project_created = true - end - end - params["recurring_todo"]["project_id"] = project.id - end - - # update context - if params['recurring_todo']['context_id'].blank? && params['context_name'].present? - context = current_user.contexts.where(:name => params['context_name'].strip).first - unless context - context = current_user.contexts.build - context.name = params['context_name'].strip - context.save - @new_context_created = true - end - params["recurring_todo"]["context_id"] = context.id - end - - # make sure that we set weekly_return_xxx to empty (space) when they are - # not checked (and thus not present in params["recurring_todo"]) - %w{monday tuesday wednesday thursday friday saturday sunday}.each do |day| - params["recurring_todo"]["weekly_return_"+day]=' ' if params["recurring_todo"]["weekly_return_"+day].nil? - end - - selector_attributes = { - 'recurring_period' => recurring_todo_params['recurring_period'], - 'daily_selector' => recurring_todo_params['daily_selector'], - 'monthly_selector' => recurring_todo_params['monthly_selector'], - 'yearly_selector' => recurring_todo_params['yearly_selector'] - } - - @recurring_todo.assign_attributes(:recurring_period => recurring_todo_params[:recurring_period]) - @recurring_todo.assign_attributes(selector_attributes) - @saved = @recurring_todo.update_attributes recurring_todo_params + @saved = updater.update(@recurring_todo) + @recurring_todo.reload respond_to do |format| format.js @@ -108,16 +61,15 @@ class RecurringTodosController < ApplicationController def create builder = RecurringTodos::RecurringTodosBuilder.new(current_user, all_recurring_todo_params) @saved = builder.save - @recurring_todo = builder.saved_recurring_todo if @saved - @status_message = t('todos.recurring_action_saved') + @recurring_todo = builder.saved_recurring_todo @todo_saved = TodoFromRecurringTodo.new(current_user, @recurring_todo).create.nil? == false - if @todo_saved - @status_message += " / " + t('todos.new_related_todo_created_short') - else - @status_message += " / " + t('todos.new_related_todo_not_created_short') - end + + @status_message = + t('todos.recurring_action_saved') + " / " + + t("todos.new_related_todo_#{@todo_saved ? "" : "not_"}created_short") + @down_count = current_user.recurring_todos.active.count @new_recurring_todo = RecurringTodo.new else @@ -219,12 +171,38 @@ class RecurringTodosController < ApplicationController def all_recurring_todo_params # move context_name, project_name and tag_list into :recurring_todo hash for easier processing - params[:recurring_todo][:context_name] = params[:context_name] unless params[:context_name].blank? - params[:recurring_todo][:project_name] = params[:project_name] unless params[:project_name].blank? - params[:recurring_todo][:tag_list] = params[:tag_list] unless params[:tag_list].blank? + { context_name: :context_name, project_name: :project_name, tag_list: :tag_list}.each do |target,source| + move_into_recurring_todo_param(params, target, source) + end recurring_todo_params end + def edit_recurring_todo_params + # we needed to rename the recurring_period selector in the edit form because + # the form for a new recurring todo and the edit form are on the same page. + # Same goes for start_from and end_date + params['recurring_todo']['recurring_period'] = params['recurring_edit_todo']['recurring_period'] + + { context_name: :context_name, + project_name: :project_name, + tag_list: :edit_recurring_todo_tag_list, + end_date: :recurring_todo_edit_end_date, + start_from: :recurring_todo_edit_start_from}.each do |target,source| + move_into_recurring_todo_param(params, target, source) + end + + # make sure that we set weekly_return_xxx to empty (space) when they are + # not checked (and thus not present in params["recurring_todo"]) + %w{monday tuesday wednesday thursday friday saturday sunday}.each do |day| + params["recurring_todo"]["weekly_return_#{day}"]=' ' if params["recurring_todo"]["weekly_return_#{day}"].nil? + end + params['recurring_todo'] + end + + def move_into_recurring_todo_param(params, target, source) + params[:recurring_todo][target] = params[source] unless params[source].blank? + end + def init @days_of_week = [] 0.upto 6 do |i| diff --git a/app/models/recurring_todos/abstract_recurring_todos_builder.rb b/app/models/recurring_todos/abstract_recurring_todos_builder.rb index e06c31c5..3d9100e9 100644 --- a/app/models/recurring_todos/abstract_recurring_todos_builder.rb +++ b/app/models/recurring_todos/abstract_recurring_todos_builder.rb @@ -5,16 +5,11 @@ module RecurringTodos def initialize(user, attributes) @user = user @attributes = attributes - @filterred_attributes = filter_attributes(attributes) + @filterred_attributes = filter_attributes(@attributes) @saved = false end - def filter_attributes(attributes) - raise Exception.new, "filter_attributes should be overridden" - end - def filter_generic_attributes(attributes) - attributes['tag_list'] = { recurring_period: attributes["recurring_period"], description: attributes['description'], @@ -41,6 +36,16 @@ module RecurringTodos @recurring_todo.project = @filterred_attributes[:project] end + def update(recurring_todo) + @recurring_todo = @pattern.update_recurring_todo(recurring_todo) + @recurring_todo.context = @filterred_attributes[:context] + @recurring_todo.project = @filterred_attributes[:project] + + @saved = @recurring_todo.save + @recurring_todo.tag_with(@filterred_attributes[:tag_list]) if @saved && @filterred_attributes[:tag_list].present? + return @saved + end + def save build @saved = @recurring_todo.save @@ -60,6 +65,16 @@ module RecurringTodos @pattern.attributes end + def attributes_to_filter + raise Exception.new, "attributes_to_filter should be overridden" + end + + def filter_attributes(attributes) + @filterred_attributes = filter_generic_attributes(attributes) + attributes_to_filter.each{|key| @filterred_attributes[key] = attributes[key] if attributes.key?(key)} + @filterred_attributes + end + private def tag_list_or_empty_string(attributes) diff --git a/app/models/recurring_todos/abstract_repeat_pattern.rb b/app/models/recurring_todos/abstract_repeat_pattern.rb index 678e54b0..8ab291ac 100644 --- a/app/models/recurring_todos/abstract_repeat_pattern.rb +++ b/app/models/recurring_todos/abstract_repeat_pattern.rb @@ -3,15 +3,22 @@ module RecurringTodos class AbstractRepeatPattern def initialize(user, attributes) - @attributes = attributes @user = user + @attributes = attributes + @filterred_attributes = nil end def build_recurring_todo @recurring_todo = @user.recurring_todos.build(mapped_attributes) end + def update_recurring_todo(recurring_todo) + recurring_todo.assign_attributes(mapped_attributes) + recurring_todo + end + def mapped_attributes + # should be overwritten to map attributes to activerecord model @attributes end diff --git a/app/models/recurring_todos/daily_recurring_todos_builder.rb b/app/models/recurring_todos/daily_recurring_todos_builder.rb index 48f07836..5e95a0af 100644 --- a/app/models/recurring_todos/daily_recurring_todos_builder.rb +++ b/app/models/recurring_todos/daily_recurring_todos_builder.rb @@ -5,13 +5,12 @@ module RecurringTodos def initialize(user, attributes) super(user, attributes) + @pattern = DailyRepeatPattern.new(user, @filterred_attributes) end - def filter_attributes(attributes) - @filterred_attributes = filter_generic_attributes(attributes) - %w{daily_selector daily_every_x_days}.each{|key| @filterred_attributes[key] = attributes[key] if attributes.key?(key)} - @filterred_attributes + def attributes_to_filter + %w{daily_selector daily_every_x_days} end end diff --git a/app/models/recurring_todos/monthly_recurring_todos_builder.rb b/app/models/recurring_todos/monthly_recurring_todos_builder.rb index a343269e..7be9e230 100644 --- a/app/models/recurring_todos/monthly_recurring_todos_builder.rb +++ b/app/models/recurring_todos/monthly_recurring_todos_builder.rb @@ -7,17 +7,11 @@ module RecurringTodos @pattern = MonthlyRepeatPattern.new(user, @filterred_attributes) end - def filter_attributes(attributes) - @filterred_attributes = filter_generic_attributes(attributes) - + def attributes_to_filter %w{ monthly_selector monthly_every_x_day monthly_every_x_month monthly_every_x_month2 monthly_every_xth_day monthly_day_of_week - }.each do |key| - @filterred_attributes[key] = attributes[key] if attributes.key?(key) - end - - @filterred_attributes + } end end diff --git a/app/models/recurring_todos/recurring_todos_builder.rb b/app/models/recurring_todos/recurring_todos_builder.rb index ccbad04f..4d808fd9 100644 --- a/app/models/recurring_todos/recurring_todos_builder.rb +++ b/app/models/recurring_todos/recurring_todos_builder.rb @@ -19,7 +19,7 @@ module RecurringTodos if %w{daily weekly monthly yearly}.include?(selector) return eval("RecurringTodos::#{selector.capitalize}RecurringTodosBuilder.new(@user, @attributes)") else - raise Exception.new("Unknown recurrence selector (#{selector})") + raise Exception.new("Unknown recurrence selector in recurring_period (#{selector})") end end @@ -27,6 +27,10 @@ module RecurringTodos @builder.build end + def update(recurring_todo) + @builder.update(recurring_todo) + end + def save @project.save if @new_project_created @context.save if @new_context_created diff --git a/app/models/recurring_todos/weekly_recurring_todos_builder.rb b/app/models/recurring_todos/weekly_recurring_todos_builder.rb index 59233b4e..19fa9ae4 100644 --- a/app/models/recurring_todos/weekly_recurring_todos_builder.rb +++ b/app/models/recurring_todos/weekly_recurring_todos_builder.rb @@ -7,16 +7,11 @@ module RecurringTodos @pattern = WeeklyRepeatPattern.new(user, @filterred_attributes) end - def filter_attributes(attributes) - @filterred_attributes = filter_generic_attributes(attributes) - - weekly_attributes = %w{weekly_selector weekly_every_x_week} - %w{monday tuesday wednesday thursday friday saturday sunday}.each{|day| weekly_attributes << "weekly_return_#{day}"} - weekly_attributes.each{|key| @filterred_attributes[key] = attributes[key] if attributes.key?(key)} - - @filterred_attributes + def attributes_to_filter + %w{weekly_selector weekly_every_x_week} + %w{monday tuesday wednesday thursday friday saturday sunday}.map{|day| "weekly_return_#{day}" } end + end end \ No newline at end of file diff --git a/app/models/recurring_todos/yearly_recurring_todos_builder.rb b/app/models/recurring_todos/yearly_recurring_todos_builder.rb index 89cc8a60..7e1e4537 100644 --- a/app/models/recurring_todos/yearly_recurring_todos_builder.rb +++ b/app/models/recurring_todos/yearly_recurring_todos_builder.rb @@ -7,16 +7,10 @@ module RecurringTodos @pattern = YearlyRepeatPattern.new(user, @filterred_attributes) end - def filter_attributes(attributes) - @filterred_attributes = filter_generic_attributes(attributes) - - %w{ yearly_selector yearly_month_of_year yearly_month_of_year2 - yearly_every_x_day yearly_every_xth_day yearly_day_of_week - }.each do |key| - @filterred_attributes[key] = attributes[key] if attributes.key?(key) - end - - @filterred_attributes + def attributes_to_filter + %w{ yearly_selector yearly_month_of_year yearly_month_of_year2 + yearly_every_x_day yearly_every_xth_day yearly_day_of_week + } end end diff --git a/test/controllers/recurring_todos_controller_test.rb b/test/controllers/recurring_todos_controller_test.rb index 0a8c03df..2ee2916d 100644 --- a/test/controllers/recurring_todos_controller_test.rb +++ b/test/controllers/recurring_todos_controller_test.rb @@ -359,4 +359,30 @@ class RecurringTodosControllerTest < ActionController::TestCase assert_equal "completed", rt.state, "repeat pattern should be completed" end + def test_update_recurring_todo + login_as(:admin_user) + rt = recurring_todos(:call_bill_gates_every_day) + current_descr = rt.description + + put :update, + "recurring_todo" => { + "description" => "changed", + "daily_selector" => "daily_every_x_day", + "daily_every_x_days" => "2", + "ends_on" => "no_end_date", + "recurring_target" => "show_from_date" + }, + "recurring_edit_todo" => { + "recurring_period" => rt.recurring_period, + }, + "recurring_todo_edit_start_from" => "2/1/2013", + "end_date" => nil, + "ends_on" => "no_end_date", + "id" => "#{rt.id}", + "context_name" => "library", + format: :js + + assert_equal "changed", rt.reload.description + end + end diff --git a/test/models/recurring_todos/abstract_recurring_todos_builder_test.rb b/test/models/recurring_todos/abstract_recurring_todos_builder_test.rb index 30e2ba1f..21f083dc 100644 --- a/test/models/recurring_todos/abstract_recurring_todos_builder_test.rb +++ b/test/models/recurring_todos/abstract_recurring_todos_builder_test.rb @@ -68,7 +68,7 @@ module RecurringTodos assert_equal "", builder.attributes[:tag_list] end - def test_tags_should_be_saved + def test_tags_should_be_saved_on_create_and_update attributes = { 'recurring_period' => "daily", 'description' => "test", @@ -91,6 +91,14 @@ module RecurringTodos assert !builder.tag_list.present?, "tag list should not be present" assert builder.save, "it should be saved" assert_equal "", builder.saved_recurring_todo.tag_list, "tag list should be empty" + + # tags should be updated + rt = builder.saved_recurring_todo + attributes['tag_list'] = "bar, foo" + updater = RecurringTodosBuilder.new(@admin, attributes) + updater.update(rt) + rt.reload + assert_equal "bar, foo", rt.tag_list end diff --git a/test/models/recurring_todos/recurring_todos_builder_test.rb b/test/models/recurring_todos/recurring_todos_builder_test.rb index fe73634c..fbeff70e 100644 --- a/test/models/recurring_todos/recurring_todos_builder_test.rb +++ b/test/models/recurring_todos/recurring_todos_builder_test.rb @@ -102,7 +102,7 @@ module RecurringTodos end def test_project_is_optional - builder = RecurringTodosBuilder.new(@admin, { + attributes = { 'recurring_period' => "daily", 'description' => "test", 'context_name' => "my new context", @@ -110,13 +110,42 @@ module RecurringTodos 'recurring_target' => 'show_from_date', 'show_always' => true, 'start_from' => '01/01/01', - 'ends_on' => 'no_end_date'}) + 'ends_on' => 'no_end_date'} + + builder = RecurringTodosBuilder.new(@admin, attributes) assert_nil builder.project, "project should not exist" builder.save assert_nil builder.saved_recurring_todo.project end + def test_builder_can_update_description + attributes = { + 'recurring_period' => "daily", + 'description' => "test", + 'context_name' => "my new context", + 'daily_selector' => 'daily_every_work_day', + 'recurring_target' => 'show_from_date', + 'show_always' => true, + 'start_from' => '01/01/01', + 'ends_on' => 'no_end_date'} + + builder = RecurringTodosBuilder.new(@admin, attributes) + builder.save + rt = builder.saved_recurring_todo + + assert_equal "test", rt.description + + attributes['description'] = 'updated' + + updater = RecurringTodosBuilder.new(@admin, attributes) + updater.update(rt) + rt.reload + + assert_equal rt.id, builder.saved_recurring_todo.id + assert_equal "updated", rt.description + end + end end \ No newline at end of file diff --git a/test/models/recurring_todos/weekly_recurring_todos_builder_test.rb b/test/models/recurring_todos/weekly_recurring_todos_builder_test.rb index 76a5286b..1c2873cb 100644 --- a/test/models/recurring_todos/weekly_recurring_todos_builder_test.rb +++ b/test/models/recurring_todos/weekly_recurring_todos_builder_test.rb @@ -30,6 +30,19 @@ module RecurringTodos assert_equal "a repeating todo", result[:description], "description should be preserved" end + def test_attributes_to_filter + attributes = { + 'recurring_period' => 'weekly', + 'description' => 'a repeating todo', # generic + 'weekly_return_monday' => 'm', # weekly specific + } + + w = WeeklyRecurringTodosBuilder.new(@admin, attributes) + assert_equal 9, w.attributes_to_filter.size + assert w.attributes_to_filter.include?('weekly_selector'), "attributes_to_filter should return static attribute weekly_selector" + assert w.attributes_to_filter.include?('weekly_return_monday'), "attributes_to_filter should return generated weekly_return_xyz" + end + end end \ No newline at end of file From 1f36c27af8805931340f5d8146eda723775d110e Mon Sep 17 00:00:00 2001 From: Reinier Balt Date: Mon, 3 Feb 2014 11:10:06 +0100 Subject: [PATCH 08/24] small cleanups --- app/controllers/recurring_todos_controller.rb | 30 +++++-------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/app/controllers/recurring_todos_controller.rb b/app/controllers/recurring_todos_controller.rb index 9190d893..3e18136b 100644 --- a/app/controllers/recurring_todos_controller.rb +++ b/app/controllers/recurring_todos_controller.rb @@ -3,7 +3,7 @@ class RecurringTodosController < ApplicationController helper :todos, :recurring_todos append_before_filter :init, :only => [:index, :new, :edit, :create] - append_before_filter :get_recurring_todo_from_param, :only => [:destroy, :toggle_check, :toggle_star, :edit] + append_before_filter :get_recurring_todo_from_param, :only => [:destroy, :toggle_check, :toggle_star, :edit, :update] def index @page_title = t('todos.recurring_actions_title') @@ -43,14 +43,9 @@ class RecurringTodosController < ApplicationController end def update - @recurring_todo = current_user.recurring_todos.find(params[:id]) - - @original_item_context_id = @recurring_todo.context_id - @original_item_project_id = @recurring_todo.project_id - updater = RecurringTodos::RecurringTodosBuilder.new(current_user, edit_recurring_todo_params) - @saved = updater.update(@recurring_todo) + @recurring_todo.reload respond_to do |format| @@ -64,11 +59,11 @@ class RecurringTodosController < ApplicationController if @saved @recurring_todo = builder.saved_recurring_todo - @todo_saved = TodoFromRecurringTodo.new(current_user, @recurring_todo).create.nil? == false + todo_saved = TodoFromRecurringTodo.new(current_user, @recurring_todo).create.nil? == false @status_message = t('todos.recurring_action_saved') + " / " + - t("todos.new_related_todo_#{@todo_saved ? "" : "not_"}created_short") + t("todos.new_related_todo_#{todo_saved ? "" : "not_"}created_short") @down_count = current_user.recurring_todos.active.count @new_recurring_todo = RecurringTodo.new @@ -102,11 +97,10 @@ class RecurringTodosController < ApplicationController format.html do if @saved notify :notice, t('todos.recurring_deleted_success') - redirect_to :action => 'index' else - notify :error, t('todos.error_deleting_recurring', :description => @recurring_todo.description) - redirect_to :action => 'index' + notify :error, t('todos.error_deleting_recurring', :description => @recurring_todo.description) end + redirect_to :action => 'index' end format.js do @@ -204,16 +198,8 @@ class RecurringTodosController < ApplicationController end def init - @days_of_week = [] - 0.upto 6 do |i| - @days_of_week << [t('date.day_names')[i], i] - end - - @months_of_year = [] - 1.upto 12 do |i| - @months_of_year << [t('date.month_names')[i], i] - end - + @days_of_week = (0..6).map{|i| [t('date.day_names')[i], i] } + @months_of_year = (1..12).map{|i| [t('date.month_names')[i], i] } @xth_day = [[t('common.first'),1],[t('common.second'),2],[t('common.third'),3],[t('common.fourth'),4],[t('common.last'),5]] @projects = current_user.projects.includes(:default_context) @contexts = current_user.contexts From 776a0464651d3caca28dd41b8dee989daba58a56 Mon Sep 17 00:00:00 2001 From: Reinier Balt Date: Fri, 7 Feb 2014 22:55:52 +0100 Subject: [PATCH 09/24] move mapping of form attributes to model attributes from pattern to builder to make pattern independent of form --- app/controllers/recurring_todos_controller.rb | 11 +- .../abstract_recurring_todos_builder.rb | 129 ++++++++++++------ .../abstract_repeat_pattern.rb | 41 +----- .../daily_recurring_todos_builder.rb | 30 +++- .../recurring_todos/daily_repeat_pattern.rb | 31 +---- .../monthly_recurring_todos_builder.rb | 32 ++++- .../recurring_todos/monthly_repeat_pattern.rb | 34 +---- .../weekly_recurring_todos_builder.rb | 26 +++- .../recurring_todos/weekly_repeat_pattern.rb | 22 +-- .../yearly_recurring_todos_builder.rb | 37 ++++- .../recurring_todos/yearly_repeat_pattern.rb | 39 +----- .../recurring_todos_controller_test.rb | 11 +- .../abstract_recurring_todos_builder_test.rb | 39 +++++- .../abstract_repeat_pattern_test.rb | 32 ----- .../daily_recurring_todos_builder_test.rb | 44 ++++++ .../daily_repeat_pattern_test.rb | 44 ------ .../monthly_recurring_todos_builder_test.rb | 60 ++++++++ .../monthly_repeat_pattern_test.rb | 62 +-------- .../weekly_recurring_todos_builder_test.rb | 39 ++++++ .../weekly_repeat_pattern_test.rb | 40 +----- .../yearly_recurring_todos_builder_test.rb | 54 +++++++- .../yearly_repeat_pattern_test.rb | 52 ------- 22 files changed, 463 insertions(+), 446 deletions(-) diff --git a/app/controllers/recurring_todos_controller.rb b/app/controllers/recurring_todos_controller.rb index 3e18136b..20461e05 100644 --- a/app/controllers/recurring_todos_controller.rb +++ b/app/controllers/recurring_todos_controller.rb @@ -77,13 +77,10 @@ class RecurringTodosController < ApplicationController end def destroy + @number_of_todos = @recurring_todo.todos.count + # remove all references to this recurring todo - @todos = @recurring_todo.todos - @number_of_todos = @todos.size - @todos.each do |t| - t.recurring_todo_id = nil - t.save - end + @recurring_todo.clear_todos_association # delete the recurring todo @saved = @recurring_todo.destroy @@ -100,7 +97,7 @@ class RecurringTodosController < ApplicationController else notify :error, t('todos.error_deleting_recurring', :description => @recurring_todo.description) end - redirect_to :action => 'index' + redirect_to :action => 'index' end format.js do diff --git a/app/models/recurring_todos/abstract_recurring_todos_builder.rb b/app/models/recurring_todos/abstract_recurring_todos_builder.rb index 3d9100e9..740926e9 100644 --- a/app/models/recurring_todos/abstract_recurring_todos_builder.rb +++ b/app/models/recurring_todos/abstract_recurring_todos_builder.rb @@ -2,13 +2,69 @@ module RecurringTodos class AbstractRecurringTodosBuilder - def initialize(user, attributes) + attr_reader :mapped_attributes, :pattern + + def initialize(user, attributes, pattern_class) @user = user + @attributes = attributes @filterred_attributes = filter_attributes(@attributes) + @selector = get_selector(selector_key) + @mapped_attributes = map_attributes(@filterred_attributes) + + @pattern = pattern_class.new(user) + @pattern.attributes = @mapped_attributes + @saved = false end + # build does not add tags. For tags, the recurring todos needs to be saved + def build + @recurring_todo = @pattern.build_recurring_todo(@mapped_attributes) + + @recurring_todo.context = @filterred_attributes[:context] + @recurring_todo.project = @filterred_attributes[:project] + end + + def update(recurring_todo) + @recurring_todo = @pattern.update_recurring_todo(recurring_todo, @mapped_attributes) + @recurring_todo.context = @filterred_attributes[:context] + @recurring_todo.project = @filterred_attributes[:project] + + @saved = @recurring_todo.save + @recurring_todo.tag_with(@filterred_attributes[:tag_list]) if @saved && @filterred_attributes[:tag_list].present? + @recurring_todo.reload + + return @saved + end + + def save + build + @saved = @recurring_todo.save + @recurring_todo.tag_with(@filterred_attributes[:tag_list]) if @saved && @filterred_attributes[:tag_list].present? + return @saved + end + + def saved_recurring_todo + raise(Exception.new, @recurring_todo.valid? ? "Recurring todo was not saved yet" : "Recurring todos was not saved because of validation errors") if !@saved + + @recurring_todo + end + + def attributes + @pattern.attributes + end + + def attributes_to_filter + raise Exception.new, "attributes_to_filter should be overridden" + end + + def filter_attributes(attributes) + filterred_attributes = filter_generic_attributes(attributes) + attributes_to_filter.each{|key| filterred_attributes[key] = attributes[key] if attributes.key?(key)} + filterred_attributes + end + def filter_generic_attributes(attributes) { recurring_period: attributes["recurring_period"], @@ -28,53 +84,36 @@ module RecurringTodos } end - # build does not add tags. For tags, the recurring todos needs to be saved - def build - @recurring_todo = @pattern.build_recurring_todo - - @recurring_todo.context = @filterred_attributes[:context] - @recurring_todo.project = @filterred_attributes[:project] - end - - def update(recurring_todo) - @recurring_todo = @pattern.update_recurring_todo(recurring_todo) - @recurring_todo.context = @filterred_attributes[:context] - @recurring_todo.project = @filterred_attributes[:project] - - @saved = @recurring_todo.save - @recurring_todo.tag_with(@filterred_attributes[:tag_list]) if @saved && @filterred_attributes[:tag_list].present? - return @saved - end - - def save - build - @saved = @recurring_todo.save - @recurring_todo.tag_with(@filterred_attributes[:tag_list]) if @saved && @filterred_attributes[:tag_list].present? - return @saved - end - - def saved_recurring_todo - if !@saved - raise Exception.new, @recurring_todo.valid? ? "Recurring todo was not saved yet" : "Recurring todos was not saved because of validation errors" - end - - @recurring_todo - end - - def attributes - @pattern.attributes - end - - def attributes_to_filter - raise Exception.new, "attributes_to_filter should be overridden" - end - - def filter_attributes(attributes) - @filterred_attributes = filter_generic_attributes(attributes) - attributes_to_filter.each{|key| @filterred_attributes[key] = attributes[key] if attributes.key?(key)} + def map_attributes + # should be overwritten by subclasses to map attributes to activerecord model attributes @filterred_attributes end + # helper method to be used in mapped_attributes in subclasses + def map(mapping, key, source_key) + mapping[key] = mapping[source_key] + mapping.except(source_key) + end + + # should return period specific selector like yearly_selector or daily_selector + def selector_key + raise Exception.new, "selector_key should be overridden in subclass of AbstractRecurringTodosBuilder" + end + + def get_selector(key) + return nil if key.nil? + raise Exception.new, "recurrence selector pattern (#{key}) not given" unless @attributes.key?(key) + raise Exception.new, "unknown recurrence selector pattern: '#{@attributes[key]}'" unless valid_selector?(@attributes[key]) + + selector = @attributes[key] + @attributes = @attributes.except(key) + return selector + end + + def valid_selector?(selector) + raise Exception.new, "valid_selector? should be overridden in subclass of AbstractRecurringTodosBuilder" + end + private def tag_list_or_empty_string(attributes) diff --git a/app/models/recurring_todos/abstract_repeat_pattern.rb b/app/models/recurring_todos/abstract_repeat_pattern.rb index 8ab291ac..028d9ebf 100644 --- a/app/models/recurring_todos/abstract_repeat_pattern.rb +++ b/app/models/recurring_todos/abstract_repeat_pattern.rb @@ -2,48 +2,21 @@ module RecurringTodos class AbstractRepeatPattern - def initialize(user, attributes) + attr_accessor :attributes + + def initialize(user) @user = user - @attributes = attributes - @filterred_attributes = nil end - def build_recurring_todo - @recurring_todo = @user.recurring_todos.build(mapped_attributes) + def build_recurring_todo(attributes) + @recurring_todo = @user.recurring_todos.build(attributes) end - def update_recurring_todo(recurring_todo) - recurring_todo.assign_attributes(mapped_attributes) + def update_recurring_todo(recurring_todo, attributes) + recurring_todo.assign_attributes(attributes) recurring_todo end - - def mapped_attributes - # should be overwritten to map attributes to activerecord model - @attributes - end - - def attributes - mapped_attributes - end - def map(mapping, key, source_key) - mapping[key] = mapping[source_key] - mapping.except(source_key) - end - - def get_selector(key) - raise Exception.new, "recurrence selector pattern (#{key}) not given" unless @attributes.key?(key) - raise Exception.new, "unknown recurrence selector pattern: '#{@attributes[key]}'" unless valid_selector?(@attributes[key]) - - selector = @attributes[key] - @attributes = @attributes.except(key) - return selector - end - - def valid_selector?(selector) - raise Exception.new, "valid_selector? should be overridden in subclass of AbstractRepeatPattern" - end - end end \ No newline at end of file diff --git a/app/models/recurring_todos/daily_recurring_todos_builder.rb b/app/models/recurring_todos/daily_recurring_todos_builder.rb index 5e95a0af..6ce83809 100644 --- a/app/models/recurring_todos/daily_recurring_todos_builder.rb +++ b/app/models/recurring_todos/daily_recurring_todos_builder.rb @@ -4,15 +4,39 @@ module RecurringTodos attr_reader :recurring_todo, :pattern def initialize(user, attributes) - super(user, attributes) - - @pattern = DailyRepeatPattern.new(user, @filterred_attributes) + super(user, attributes, DailyRepeatPattern) end def attributes_to_filter %w{daily_selector daily_every_x_days} end + def map_attributes(mapping) + mapping[:only_work_days] = only_work_days?(@selector) + + mapping[:every_other1] = mapping['daily_every_x_days'] + mapping = mapping.except('daily_every_x_days') + + mapping + end + + def only_work_days?(daily_selector) + case daily_selector + when 'daily_every_x_day' + return false + when 'daily_every_work_day' + return true + end + end + + def selector_key + 'daily_selector' + end + + def valid_selector?(selector) + %w{daily_every_x_day daily_every_work_day}.include?(selector) + end + end end \ No newline at end of file diff --git a/app/models/recurring_todos/daily_repeat_pattern.rb b/app/models/recurring_todos/daily_repeat_pattern.rb index 32eaa553..6b742018 100644 --- a/app/models/recurring_todos/daily_repeat_pattern.rb +++ b/app/models/recurring_todos/daily_repeat_pattern.rb @@ -2,41 +2,14 @@ module RecurringTodos class DailyRepeatPattern < AbstractRepeatPattern - def initialize(user, attributes) - super user, attributes - @selector = get_selector('daily_selector') - end - - def mapped_attributes - mapping = @attributes - - mapping[:only_work_days] = only_work_days?(@selector) - - mapping[:every_other1] = mapping['daily_every_x_days'] - mapping = mapping.except('daily_every_x_days') - - mapping + def initialize(user) + super user end def every_x_days @recurring_todo.every_other1 end - private - - def only_work_days?(daily_selector) - case daily_selector - when 'daily_every_x_day' - return false - when 'daily_every_work_day' - return true - end - end - - def valid_selector?(selector) - %w{daily_every_x_day daily_every_work_day}.include?(selector) - end - end end \ No newline at end of file diff --git a/app/models/recurring_todos/monthly_recurring_todos_builder.rb b/app/models/recurring_todos/monthly_recurring_todos_builder.rb index 7be9e230..39aaefb5 100644 --- a/app/models/recurring_todos/monthly_recurring_todos_builder.rb +++ b/app/models/recurring_todos/monthly_recurring_todos_builder.rb @@ -3,8 +3,7 @@ module RecurringTodos class MonthlyRecurringTodosBuilder < AbstractRecurringTodosBuilder def initialize(user, attributes) - super(user, attributes) - @pattern = MonthlyRepeatPattern.new(user, @filterred_attributes) + super(user, attributes, MonthlyRepeatPattern) end def attributes_to_filter @@ -14,6 +13,35 @@ module RecurringTodos } end + def map_attributes(mapping) + mapping = map(mapping, :every_other1, 'monthly_every_x_day') + mapping = map(mapping, :every_other3, 'monthly_every_xth_day') + mapping = map(mapping, :every_count, 'monthly_day_of_week') + + mapping[:every_other2] = mapping[get_every_other2] + mapping = mapping.except('monthly_every_x_month').except('monthly_every_x_month2') + + mapping[:recurrence_selector] = get_recurrence_selector + + mapping + end + + def get_recurrence_selector + @selector=='monthly_every_x_day' ? 0 : 1 + end + + def get_every_other2 + get_recurrence_selector == 0 ? 'monthly_every_x_month' : 'monthly_every_x_month2' + end + + def selector_key + 'monthly_selector' + end + + def valid_selector?(selector) + %w{monthly_every_x_day monthly_every_xth_day}.include?(selector) + end + end end \ No newline at end of file diff --git a/app/models/recurring_todos/monthly_repeat_pattern.rb b/app/models/recurring_todos/monthly_repeat_pattern.rb index 31d754a2..57c0027c 100644 --- a/app/models/recurring_todos/monthly_repeat_pattern.rb +++ b/app/models/recurring_todos/monthly_repeat_pattern.rb @@ -2,24 +2,8 @@ module RecurringTodos class MonthlyRepeatPattern < AbstractRepeatPattern - def initialize(user, attributes) - super user, attributes - @selector = get_selector('monthly_selector') - end - - def mapped_attributes - mapping = @attributes - - mapping = map(mapping, :every_other1, 'monthly_every_x_day') - mapping = map(mapping, :every_other3, 'monthly_every_xth_day') - mapping = map(mapping, :every_count, 'monthly_day_of_week') - - mapping[:every_other2] = mapping[get_every_other2] - mapping = mapping.except('monthly_every_x_month').except('monthly_every_x_month2') - - mapping[:recurrence_selector] = get_recurrence_selector - - mapping + def initialize(user) + super user end def every_x_day? @@ -30,20 +14,6 @@ module RecurringTodos @recurring_todo.recurrence_selector == 1 end - private - - def get_recurrence_selector - @selector=='monthly_every_x_day' ? 0 : 1 - end - - def get_every_other2 - get_recurrence_selector == 0 ? 'monthly_every_x_month' : 'monthly_every_x_month2' - end - - def valid_selector?(selector) - %w{monthly_every_x_day monthly_every_xth_day}.include?(selector) - end - end end \ No newline at end of file diff --git a/app/models/recurring_todos/weekly_recurring_todos_builder.rb b/app/models/recurring_todos/weekly_recurring_todos_builder.rb index 19fa9ae4..46a67055 100644 --- a/app/models/recurring_todos/weekly_recurring_todos_builder.rb +++ b/app/models/recurring_todos/weekly_recurring_todos_builder.rb @@ -3,14 +3,36 @@ module RecurringTodos class WeeklyRecurringTodosBuilder < AbstractRecurringTodosBuilder def initialize(user, attributes) - super(user, attributes) - @pattern = WeeklyRepeatPattern.new(user, @filterred_attributes) + super(user, attributes, WeeklyRepeatPattern) end def attributes_to_filter %w{weekly_selector weekly_every_x_week} + %w{monday tuesday wednesday thursday friday saturday sunday}.map{|day| "weekly_return_#{day}" } end + def map_attributes(mapping) + mapping = map(mapping, :every_other1, 'weekly_every_x_week') + + { monday: 1, tuesday: 2, wednesday: 3, thursday: 4, friday: 5, saturday: 6, sunday: 0 }.each{|day, index| mapping = map_day(mapping, :every_day, "weekly_return_#{day}", index)} + + mapping + end + + def map_day(mapping, key, source_key, index) + mapping[key] ||= ' ' # avoid nil + mapping[source_key] ||= ' ' # avoid nil + + mapping[key] = mapping[key][0, index] + mapping[source_key] + mapping[key][index+1, mapping[key].length] + mapping + end + + def selector_key + nil + end + + def valid_selector?(key) + true + end end diff --git a/app/models/recurring_todos/weekly_repeat_pattern.rb b/app/models/recurring_todos/weekly_repeat_pattern.rb index 2330be7a..484e01e3 100644 --- a/app/models/recurring_todos/weekly_repeat_pattern.rb +++ b/app/models/recurring_todos/weekly_repeat_pattern.rb @@ -2,26 +2,8 @@ module RecurringTodos class WeeklyRepeatPattern < AbstractRepeatPattern - def initialize(user, attributes) - super user, attributes - end - - def mapped_attributes - mapping = @attributes - - mapping = map(mapping, :every_other1, 'weekly_every_x_week') - - { monday: 1, tuesday: 2, wednesday: 3, thursday: 4, friday: 5, saturday: 6, sunday: 0 }.each{|day, index| mapping = map_day(mapping, :every_day, "weekly_return_#{day}", index)} - - mapping - end - - def map_day(mapping, key, source_key, index) - mapping[key] ||= ' ' # avoid nil - mapping[source_key] ||= ' ' # avoid nil - - mapping[key] = mapping[key][0, index] + mapping[source_key] + mapping[key][index+1, mapping[key].length] - mapping + def initialize(user) + super user end end diff --git a/app/models/recurring_todos/yearly_recurring_todos_builder.rb b/app/models/recurring_todos/yearly_recurring_todos_builder.rb index 7e1e4537..d34c49f3 100644 --- a/app/models/recurring_todos/yearly_recurring_todos_builder.rb +++ b/app/models/recurring_todos/yearly_recurring_todos_builder.rb @@ -3,8 +3,7 @@ module RecurringTodos class YearlyRecurringTodosBuilder < AbstractRecurringTodosBuilder def initialize(user, attributes) - super(user, attributes) - @pattern = YearlyRepeatPattern.new(user, @filterred_attributes) + super(user, attributes, YearlyRepeatPattern) end def attributes_to_filter @@ -13,6 +12,40 @@ module RecurringTodos } end + def map_attributes(mapping) + mapping[:recurrence_selector] = get_recurrence_selector + + mapping[:every_other2] = mapping[get_every_other2] + mapping = mapping.except('yearly_month_of_year').except('yearly_month_of_year2') + + mapping = map(mapping, :every_other1, 'yearly_every_x_day') + mapping = map(mapping, :every_other3, 'yearly_every_xth_day') + mapping = map(mapping, :every_count, 'yearly_day_of_week') + + mapping + end + + def selector_key + 'yearly_selector' + end + + def valid_selector?(selector) + %w{yearly_every_x_day yearly_every_xth_day}.include?(selector) + end + + def get_recurrence_selector + @selector=='yearly_every_x_day' ? 0 : 1 + end + + def get_every_other2 + case get_recurrence_selector + when 0 + 'yearly_month_of_year' + when 1 + 'yearly_month_of_year2' + end + end + end end \ No newline at end of file diff --git a/app/models/recurring_todos/yearly_repeat_pattern.rb b/app/models/recurring_todos/yearly_repeat_pattern.rb index d475d497..a9688019 100644 --- a/app/models/recurring_todos/yearly_repeat_pattern.rb +++ b/app/models/recurring_todos/yearly_repeat_pattern.rb @@ -2,43 +2,8 @@ module RecurringTodos class YearlyRepeatPattern < AbstractRepeatPattern - def initialize(user, attributes) - super user, attributes - @selector = get_selector('yearly_selector') - end - - def mapped_attributes - mapping = @attributes - - mapping[:recurrence_selector] = get_recurrence_selector - - mapping[:every_other2] = mapping[get_every_other2] - mapping = mapping.except('yearly_month_of_year').except('yearly_month_of_year2') - - mapping = map(mapping, :every_other1, 'yearly_every_x_day') - mapping = map(mapping, :every_other3, 'yearly_every_xth_day') - mapping = map(mapping, :every_count, 'yearly_day_of_week') - - mapping - end - - private - - def get_recurrence_selector - @selector=='yearly_every_x_day' ? 0 : 1 - end - - def get_every_other2 - case get_recurrence_selector - when 0 - 'yearly_month_of_year' - when 1 - 'yearly_month_of_year2' - end - end - - def valid_selector?(selector) - %w{yearly_every_x_day yearly_every_xth_day}.include?(selector) + def initialize(user) + super user end end diff --git a/test/controllers/recurring_todos_controller_test.rb b/test/controllers/recurring_todos_controller_test.rb index 2ee2916d..b8ce950e 100644 --- a/test/controllers/recurring_todos_controller_test.rb +++ b/test/controllers/recurring_todos_controller_test.rb @@ -9,13 +9,21 @@ class RecurringTodosControllerTest < ActionController::TestCase def test_destroy_recurring_todo login_as(:admin_user) + + rc = RecurringTodo.find(1) + todo = rc.todos.first + xhr :post, :destroy, :id => 1, :_source_view => 'todo' + begin rc = RecurringTodo.find(1) rescue rc = nil end - assert_nil rc + + assert_nil rc, "rc should be deleted" + assert_nil todo.reload.recurring_todo_id, "todo should be unlinked from deleted recurring_todo" + end def test_new_recurring_todo @@ -362,7 +370,6 @@ class RecurringTodosControllerTest < ActionController::TestCase def test_update_recurring_todo login_as(:admin_user) rt = recurring_todos(:call_bill_gates_every_day) - current_descr = rt.description put :update, "recurring_todo" => { diff --git a/test/models/recurring_todos/abstract_recurring_todos_builder_test.rb b/test/models/recurring_todos/abstract_recurring_todos_builder_test.rb index 21f083dc..86efb238 100644 --- a/test/models/recurring_todos/abstract_recurring_todos_builder_test.rb +++ b/test/models/recurring_todos/abstract_recurring_todos_builder_test.rb @@ -5,6 +5,16 @@ module RecurringTodos class AbstractRecurringTodosBuilderTest < ActiveSupport::TestCase fixtures :users + class TestRepeatPattern < AbstractRepeatPattern + def selector_key + 'test' + end + + def valid_selector?(selector) + true + end + end + def setup @admin = users(:admin_user) end @@ -23,7 +33,7 @@ module RecurringTodos } assert_raise(Exception, "should have exception since we are using abstract builder") do - builder = AbstractRecurringTodosBuilder.new(@admin, attributes) + builder = AbstractRecurringTodosBuilder.new(@admin, attributes, DailyRepeatPattern) end end @@ -101,7 +111,6 @@ module RecurringTodos assert_equal "bar, foo", rt.tag_list end - def test_saved_should_raise_exception_on_validation_errors attributes = { 'recurring_period' => "daily", @@ -125,6 +134,32 @@ module RecurringTodos assert_raise(Exception, "should have exception since there is no saved recurring todo"){ builder.saved_recurring_todo } end + def test_map_removes_mapped_key + attributes = { :source => "value"} + + arp = WeeklyRecurringTodosBuilder.new(@admin, attributes) + attributes = arp.map(attributes, :target, :source) + + assert_equal "value", attributes[:target] + assert_nil attributes[:source] + assert !attributes.key?(:source) + end + + def test_get_selector_removes_selector_from_hash + attributes = { :selector => "weekly" } + arp = WeeklyRecurringTodosBuilder.new(@admin, attributes) + + assert "weekly", arp.get_selector(:selector) + assert !arp.attributes.key?(:selector) + end + + def test_get_selector_raises_exception_when_missing_selector + attributes = { } + arp = WeeklyRecurringTodosBuilder.new(@admin, attributes) + + assert_raise(Exception, "should raise exception when recurrence selector is missing"){ arp.get_selector(:selector) } + end + end end \ No newline at end of file diff --git a/test/models/recurring_todos/abstract_repeat_pattern_test.rb b/test/models/recurring_todos/abstract_repeat_pattern_test.rb index aa1067a2..feee55d4 100644 --- a/test/models/recurring_todos/abstract_repeat_pattern_test.rb +++ b/test/models/recurring_todos/abstract_repeat_pattern_test.rb @@ -5,42 +5,10 @@ module RecurringTodos class AbstractRepeatPatternTest < ActiveSupport::TestCase fixtures :users - class TestRepeatPattern < AbstractRepeatPattern - def valid_selector?(selector) - true - end - end - def setup @admin = users(:admin_user) end - def test_map_removes_mapped_key - attributes = { :source => "value"} - - arp = AbstractRepeatPattern.new(@admin, attributes) - attributes = arp.map(attributes, :target, :source) - - assert_equal "value", attributes[:target] - assert_nil attributes[:source] - assert !attributes.key?(:source) - end - - def test_get_selector_removes_selector_from_hash - attributes = { :selector => "weekly" } - arp = TestRepeatPattern.new(@admin, attributes) - - assert "weekly", arp.get_selector(:selector) - assert !arp.attributes.key?(:selector) - end - - def test_get_selector_raises_exception_when_missing_selector - attributes = { } - arp = TestRepeatPattern.new(@admin, attributes) - - assert_raise(Exception, "should raise exception when recurrence selector is missing"){ arp.get_selector(:selector) } - end - end end \ No newline at end of file diff --git a/test/models/recurring_todos/daily_recurring_todos_builder_test.rb b/test/models/recurring_todos/daily_recurring_todos_builder_test.rb index 34e1b192..8e692bac 100644 --- a/test/models/recurring_todos/daily_recurring_todos_builder_test.rb +++ b/test/models/recurring_todos/daily_recurring_todos_builder_test.rb @@ -30,6 +30,50 @@ module RecurringTodos assert_equal "a repeating todo", result[:description], "description should be preserved" end + def test_valid_selector + attributes = { + 'recurring_period' => 'daily' + } + + # should not raise + %w{daily_every_x_day daily_every_work_day}.each do |selector| + attributes['daily_selector'] = selector + DailyRecurringTodosBuilder.new(@admin, attributes) + end + + # should raise + attributes = { + 'recurring_period' => 'daily', + 'daily_selector' => 'wrong value' + } + + # should raise + assert_raise(Exception, "should have exception since daily_selector has wrong value"){ DailyRecurringTodosBuilder.new(@admin, attributes) } + end + + def test_mapping_of_attributes + attributes = { + 'recurring_period' => 'daily', + 'description' => 'a repeating todo', # generic + 'daily_selector' => 'daily_every_x_day', # daily specific --> mapped to only_work_days=false + 'daily_every_x_days' => '5' # mapped to every_other1 + } + + pattern = DailyRecurringTodosBuilder.new(@admin, attributes) + + assert_equal '5', pattern.mapped_attributes[:every_other1], "every_other1 should be set to daily_every_x_days" + assert_equal false, pattern.mapped_attributes[:only_work_days], "only_work_days should be set to false for daily_every_x_day" + + attributes = { + 'recurring_period' => 'daily', + 'description' => 'a repeating todo', # generic + 'daily_selector' => 'daily_every_work_day', # daily specific --> mapped to only_work_days=true + } + + pattern = DailyRecurringTodosBuilder.new(@admin, attributes) + assert_equal true, pattern.mapped_attributes[:only_work_days] + end + end end \ No newline at end of file diff --git a/test/models/recurring_todos/daily_repeat_pattern_test.rb b/test/models/recurring_todos/daily_repeat_pattern_test.rb index 9c91d569..2e00db9b 100644 --- a/test/models/recurring_todos/daily_repeat_pattern_test.rb +++ b/test/models/recurring_todos/daily_repeat_pattern_test.rb @@ -9,50 +9,6 @@ module RecurringTodos @admin = users(:admin_user) end - def test_valid_selector - attributes = { - 'recurring_period' => 'daily' - } - - # should not raise - %w{daily_every_x_day daily_every_work_day}.each do |selector| - attributes['daily_selector'] = selector - DailyRepeatPattern.new(@admin, attributes) - end - - # should raise - attributes = { - 'recurring_period' => 'daily', - 'daily_selector' => 'wrong value' - } - - # should raise - assert_raise(Exception, "should have exception since daily_selector has wrong value"){ DailyRepeatPattern.new(@admin, attributes) } - end - - def test_mapping_of_attributes - attributes = { - 'recurring_period' => 'daily', - 'description' => 'a repeating todo', # generic - 'daily_selector' => 'daily_every_x_day', # daily specific --> mapped to only_work_days=false - 'daily_every_x_days' => '5' # mapped to every_other1 - } - - pattern = DailyRepeatPattern.new(@admin, attributes) - - assert_equal '5', pattern.mapped_attributes[:every_other1], "every_other1 should be set to daily_every_x_days" - assert_equal false, pattern.mapped_attributes[:only_work_days], "only_work_days should be set to false for daily_every_x_day" - - attributes = { - 'recurring_period' => 'daily', - 'description' => 'a repeating todo', # generic - 'daily_selector' => 'daily_every_work_day', # daily specific --> mapped to only_work_days=true - } - - pattern = DailyRepeatPattern.new(@admin, attributes) - assert_equal true, pattern.mapped_attributes[:only_work_days] - end - end end \ No newline at end of file diff --git a/test/models/recurring_todos/monthly_recurring_todos_builder_test.rb b/test/models/recurring_todos/monthly_recurring_todos_builder_test.rb index 8f0ceeeb..acb0da99 100644 --- a/test/models/recurring_todos/monthly_recurring_todos_builder_test.rb +++ b/test/models/recurring_todos/monthly_recurring_todos_builder_test.rb @@ -30,6 +30,66 @@ module RecurringTodos assert_equal 5, result[:every_other1], "should be preserved" end + def test_valid_selector + attributes = { + 'recurring_period' => 'monthly' + } + + # should not raise + %w{monthly_every_x_day monthly_every_xth_day}.each do |selector| + attributes['monthly_selector'] = selector + MonthlyRecurringTodosBuilder.new(@admin, attributes) + end + + # should raise + attributes = { + 'recurring_period' => 'monthly', + 'monthly_selector' => 'wrong value' + } + + # should raise + assert_raise(Exception, "should have exception since monthly_selector has wrong value"){ MonthlyRecurringTodosBuilder.new(@admin, attributes) } + end + + def test_mapping_of_attributes + attributes = { + 'recurring_period' => 'monthly', + 'description' => 'a repeating todo', # generic + 'monthly_selector' => 'monthly_every_x_day', # monthly specific + 'monthly_every_x_day' => '5', # mapped to :every_other1 + 'monthly_every_xth_day' => '7', # mapped to :every_other3 + 'monthly_day_of_week' => 3, # mapped to :every_count + 'monthly_every_x_month' => '10', # mapped to :every_other2 + 'monthly_every_x_month2' => '20' # not mapped + } + + builder = MonthlyRecurringTodosBuilder.new(@admin, attributes) + assert_equal 0, builder.mapped_attributes[:recurrence_selector], "selector should be 0 for monthly_every_x_day" + assert_equal '5', builder.mapped_attributes[:every_other1], "every_other1 should be set to monthly_every_x_days" + assert_equal '10', builder.mapped_attributes[:every_other2], "every_other2 should be set to monthly_every_x_month when selector is monthly_every_x_day (=0)" + assert_equal '7', builder.mapped_attributes[:every_other3], "every_other3 should be set to monthly_every_xth_day" + assert_equal 3, builder.mapped_attributes[:every_count], "every_count should be set to monthly_day_of_week" + + builder.build + assert builder.pattern.every_x_day?, "every_x_day? should say true for selector monthly_every_x_day" + + attributes = { + 'recurring_period' => 'monthly', + 'description' => 'a repeating todo', # generic + 'monthly_selector' => 'monthly_every_xth_day', # monthly specific + 'monthly_every_x_day' => '5', # mapped to :every_other1 + 'monthly_every_x_month' => '10', # not mapped + 'monthly_every_x_month2' => '20' # mapped to :every_other2 + } + + builder = MonthlyRecurringTodosBuilder.new(@admin, attributes) + assert_equal 1, builder.mapped_attributes[:recurrence_selector], "selector should be 1 for monthly_every_xth_day" + assert_equal '20', builder.mapped_attributes[:every_other2], "every_other2 should be set to monthly_every_x_month2 when selector is monthly_every_xth_day (=0)" + + builder.build + assert builder.pattern.every_xth_day?, "every_xth_day? should say true for selector monthly_every_xth_day" + end + end end \ No newline at end of file diff --git a/test/models/recurring_todos/monthly_repeat_pattern_test.rb b/test/models/recurring_todos/monthly_repeat_pattern_test.rb index 6e963ff0..e481eace 100644 --- a/test/models/recurring_todos/monthly_repeat_pattern_test.rb +++ b/test/models/recurring_todos/monthly_repeat_pattern_test.rb @@ -8,67 +8,7 @@ module RecurringTodos def setup @admin = users(:admin_user) end - - def test_valid_selector - attributes = { - 'recurring_period' => 'monthly' - } - - # should not raise - %w{monthly_every_x_day monthly_every_xth_day}.each do |selector| - attributes['monthly_selector'] = selector - MonthlyRepeatPattern.new(@admin, attributes) - end - - # should raise - attributes = { - 'recurring_period' => 'monthly', - 'monthly_selector' => 'wrong value' - } - - # should raise - assert_raise(Exception, "should have exception since monthly_selector has wrong value"){ MonthlyRepeatPattern.new(@admin, attributes) } - end - - def test_mapping_of_attributes - attributes = { - 'recurring_period' => 'monthly', - 'description' => 'a repeating todo', # generic - 'monthly_selector' => 'monthly_every_x_day', # monthly specific - 'monthly_every_x_day' => '5', # mapped to :every_other1 - 'monthly_every_xth_day' => '7', # mapped to :every_other3 - 'monthly_day_of_week' => 3, # mapped to :every_count - 'monthly_every_x_month' => '10', # mapped to :every_other2 - 'monthly_every_x_month2' => '20' # not mapped - } - - pattern = MonthlyRepeatPattern.new(@admin, attributes) - assert_equal 0, pattern.mapped_attributes[:recurrence_selector], "selector should be 0 for monthly_every_x_day" - assert_equal '5', pattern.mapped_attributes[:every_other1], "every_other1 should be set to monthly_every_x_days" - assert_equal '10', pattern.mapped_attributes[:every_other2], "every_other2 should be set to monthly_every_x_month when selector is monthly_every_x_day (=0)" - assert_equal '7', pattern.mapped_attributes[:every_other3], "every_other3 should be set to monthly_every_xth_day" - assert_equal 3, pattern.mapped_attributes[:every_count], "every_count should be set to monthly_day_of_week" - - pattern.build_recurring_todo - assert pattern.every_x_day?, "every_x_day? should say true for selector monthly_every_x_day" - - attributes = { - 'recurring_period' => 'monthly', - 'description' => 'a repeating todo', # generic - 'monthly_selector' => 'monthly_every_xth_day', # monthly specific - 'monthly_every_x_day' => '5', # mapped to :every_other1 - 'monthly_every_x_month' => '10', # not mapped - 'monthly_every_x_month2' => '20' # mapped to :every_other2 - } - - pattern = MonthlyRepeatPattern.new(@admin, attributes) - assert_equal 1, pattern.mapped_attributes[:recurrence_selector], "selector should be 1 for monthly_every_xth_day" - assert_equal '20', pattern.mapped_attributes[:every_other2], "every_other2 should be set to monthly_every_x_month2 when selector is monthly_every_xth_day (=0)" - - pattern.build_recurring_todo - assert pattern.every_xth_day?, "every_xth_day? should say true for selector monthly_every_xth_day" - end - + end end \ No newline at end of file diff --git a/test/models/recurring_todos/weekly_recurring_todos_builder_test.rb b/test/models/recurring_todos/weekly_recurring_todos_builder_test.rb index 1c2873cb..e4a715ad 100644 --- a/test/models/recurring_todos/weekly_recurring_todos_builder_test.rb +++ b/test/models/recurring_todos/weekly_recurring_todos_builder_test.rb @@ -43,6 +43,45 @@ module RecurringTodos assert w.attributes_to_filter.include?('weekly_return_monday'), "attributes_to_filter should return generated weekly_return_xyz" end + def test_mapping_of_attributes + attributes = { + 'recurring_period' => 'weekly', + 'description' => 'a repeating todo', # generic + 'weekly_every_x_week' => '5', # mapped to every_other1 + 'weekly_return_monday' => 'm' + } + + pattern = WeeklyRecurringTodosBuilder.new(@admin, attributes) + + assert_equal '5', pattern.mapped_attributes[:every_other1], "every_other1 should be set to weekly_every_x_week" + assert_equal ' m ', pattern.mapped_attributes[:every_day], "weekly_return_ should be mapped to :every_day in format 'smtwtfs'" + end + + def test_map_day + attributes = { + 'recurring_period' => 'weekly', + 'description' => 'a repeating todo', # generic + 'weekly_every_x_week' => '5' # mapped to every_other1 + } + + pattern = WeeklyRecurringTodosBuilder.new(@admin, attributes) + assert_equal ' ', pattern.mapped_attributes[:every_day], "all days should be empty in :every_day" + + # add all days + { sunday: 's', monday: 'm', tuesday: 't', wednesday: 'w', thursday: 't', friday: 'f', saturday: 's' }.each do |day, short| + attributes["weekly_return_#{day}"] = short + end + + pattern = WeeklyRecurringTodosBuilder.new(@admin, attributes) + assert_equal 'smtwtfs', pattern.mapped_attributes[:every_day], "all days should be filled in :every_day" + + # remove wednesday + attributes = attributes.except('weekly_return_wednesday') + pattern = WeeklyRecurringTodosBuilder.new(@admin, attributes) + assert_equal 'smt tfs', pattern.mapped_attributes[:every_day], "only wednesday should be empty in :every_day" + end + + end end \ No newline at end of file diff --git a/test/models/recurring_todos/weekly_repeat_pattern_test.rb b/test/models/recurring_todos/weekly_repeat_pattern_test.rb index 45d713e9..b69f17d2 100644 --- a/test/models/recurring_todos/weekly_repeat_pattern_test.rb +++ b/test/models/recurring_todos/weekly_repeat_pattern_test.rb @@ -8,45 +8,7 @@ module RecurringTodos def setup @admin = users(:admin_user) end - - def test_mapping_of_attributes - attributes = { - 'recurring_period' => 'weekly', - 'description' => 'a repeating todo', # generic - 'weekly_every_x_week' => '5', # mapped to every_other1 - 'weekly_return_monday' => 'm' - } - - pattern = WeeklyRepeatPattern.new(@admin, attributes) - - assert_equal '5', pattern.mapped_attributes[:every_other1], "every_other1 should be set to weekly_every_x_week" - assert_equal ' m ', pattern.mapped_attributes[:every_day], "weekly_return_ should be mapped to :every_day in format 'smtwtfs'" - end - - def test_map_day - attributes = { - 'recurring_period' => 'weekly', - 'description' => 'a repeating todo', # generic - 'weekly_every_x_week' => '5' # mapped to every_other1 - } - - pattern = WeeklyRepeatPattern.new(@admin, attributes) - assert_equal ' ', pattern.mapped_attributes[:every_day], "all days should be empty in :every_day" - - # add all days - { sunday: 's', monday: 'm', tuesday: 't', wednesday: 'w', thursday: 't', friday: 'f', saturday: 's' }.each do |day, short| - attributes["weekly_return_#{day}"] = short - end - - pattern = WeeklyRepeatPattern.new(@admin, attributes) - assert_equal 'smtwtfs', pattern.mapped_attributes[:every_day], "all days should be filled in :every_day" - - # remove wednesday - attributes = attributes.except('weekly_return_wednesday') - pattern = WeeklyRepeatPattern.new(@admin, attributes) - assert_equal 'smt tfs', pattern.mapped_attributes[:every_day], "only wednesday should be empty in :every_day" - end - + end end \ No newline at end of file diff --git a/test/models/recurring_todos/yearly_recurring_todos_builder_test.rb b/test/models/recurring_todos/yearly_recurring_todos_builder_test.rb index 2579336e..6b821cc8 100644 --- a/test/models/recurring_todos/yearly_recurring_todos_builder_test.rb +++ b/test/models/recurring_todos/yearly_recurring_todos_builder_test.rb @@ -24,13 +24,65 @@ module RecurringTodos } result = RecurringTodosBuilder.new(@admin, attributes).attributes - + assert_nil result['bla_bla'], "bla_bla should be filtered" assert_nil result[:bla_bla], "bla_bla should be filtered" assert_equal '1', result[:every_other2], "yearly attributes should be preserved" assert_equal "a repeating todo", result[:description], "description should be preserved" end + def test_valid_selector + attributes = { + 'recurring_period' => 'yearly' + } + + # should not raise + %w{yearly_every_x_day yearly_every_xth_day}.each do |selector| + attributes['yearly_selector'] = selector + YearlyRecurringTodosBuilder.new(@admin, attributes) + end + + # should raise + attributes = { + 'recurring_period' => 'yearly', + 'yearly_selector' => 'wrong value' + } + + # should raise + assert_raise(Exception, "should have exception since yearly_selector has wrong value"){ YearlyRecurringTodosBuilder.new(@admin, attributes) } + end + + def test_mapping_of_attributes + attributes = { + 'recurring_period' => 'yearly', + 'description' => 'a repeating todo', # generic + 'yearly_selector' => 'yearly_every_x_day', # yearly specific + 'yearly_every_x_day' => '5', # mapped to every_other1 + 'yearly_every_xth_day' => '7', # mapped to every_other3 + 'yearly_day_of_week' => '3', # mapped to every_count + 'yearly_month_of_year' => '1', # mapped to evert_other2 because yearly_selector is yearly_every_x_day + 'yearly_month_of_year2' => '2' # ignored because yearly_selector is yearly_every_x_day + } + + pattern = YearlyRecurringTodosBuilder.new(@admin, attributes) + + assert_equal '5', pattern.mapped_attributes[:every_other1], "every_other1 should be set to yearly_every_x_day" + assert_equal '1', pattern.mapped_attributes[:every_other2], "every_other2 should be set to yearly_month_of_year because selector is yearly_every_x_day" + assert_equal '7', pattern.mapped_attributes[:every_other3], "every_other3 should be set to yearly_every_xth_day" + assert_equal '3', pattern.mapped_attributes[:every_count], "every_count should be set to yearly_day_of_week" + + attributes = { + 'recurring_period' => 'yearly', + 'description' => 'a repeating todo', # generic + 'yearly_selector' => 'yearly_every_xth_day', # daily specific --> mapped to only_work_days=false + 'yearly_month_of_year' => '1', # ignored because yearly_selector is yearly_every_xth_day + 'yearly_month_of_year2' => '2' # mapped to evert_other2 because yearly_selector is yearly_every_xth_day + } + + pattern = YearlyRecurringTodosBuilder.new(@admin, attributes) + assert_equal '2', pattern.mapped_attributes[:every_other2], "every_other2 should be set to yearly_month_of_year2 because selector is yearly_every_xth_day" + end + end end \ No newline at end of file diff --git a/test/models/recurring_todos/yearly_repeat_pattern_test.rb b/test/models/recurring_todos/yearly_repeat_pattern_test.rb index 4836f57a..fd8b1150 100644 --- a/test/models/recurring_todos/yearly_repeat_pattern_test.rb +++ b/test/models/recurring_todos/yearly_repeat_pattern_test.rb @@ -9,58 +9,6 @@ module RecurringTodos @admin = users(:admin_user) end - def test_valid_selector - attributes = { - 'recurring_period' => 'yearly' - } - - # should not raise - %w{yearly_every_x_day yearly_every_xth_day}.each do |selector| - attributes['yearly_selector'] = selector - YearlyRepeatPattern.new(@admin, attributes) - end - - # should raise - attributes = { - 'recurring_period' => 'yearly', - 'yearly_selector' => 'wrong value' - } - - # should raise - assert_raise(Exception, "should have exception since yearly_selector has wrong value"){ YearlyRepeatPattern.new(@admin, attributes) } - end - - def test_mapping_of_attributes - attributes = { - 'recurring_period' => 'yearly', - 'description' => 'a repeating todo', # generic - 'yearly_selector' => 'yearly_every_x_day', # yearly specific - 'yearly_every_x_day' => '5', # mapped to every_other1 - 'yearly_every_xth_day' => '7', # mapped to every_other3 - 'yearly_day_of_week' => '3', # mapped to every_count - 'yearly_month_of_year' => '1', # mapped to evert_other2 because yearly_selector is yearly_every_x_day - 'yearly_month_of_year2' => '2' # ignored because yearly_selector is yearly_every_x_day - } - - pattern = YearlyRepeatPattern.new(@admin, attributes) - - assert_equal '5', pattern.mapped_attributes[:every_other1], "every_other1 should be set to yearly_every_x_day" - assert_equal '1', pattern.mapped_attributes[:every_other2], "every_other2 should be set to yearly_month_of_year because selector is yearly_every_x_day" - assert_equal '7', pattern.mapped_attributes[:every_other3], "every_other3 should be set to yearly_every_xth_day" - assert_equal '3', pattern.mapped_attributes[:every_count], "every_count should be set to yearly_day_of_week" - - attributes = { - 'recurring_period' => 'yearly', - 'description' => 'a repeating todo', # generic - 'yearly_selector' => 'yearly_every_xth_day', # daily specific --> mapped to only_work_days=false - 'yearly_month_of_year' => '1', # ignored because yearly_selector is yearly_every_xth_day - 'yearly_month_of_year2' => '2' # mapped to evert_other2 because yearly_selector is yearly_every_xth_day - } - - pattern = YearlyRepeatPattern.new(@admin, attributes) - assert_equal '2', pattern.mapped_attributes[:every_other2], "every_other2 should be set to yearly_month_of_year2 because selector is yearly_every_xth_day" - end - end end \ No newline at end of file From 29b815e998db2fb9a39f8a2cf9d36f3d7b7105ca Mon Sep 17 00:00:00 2001 From: Reinier Balt Date: Sat, 8 Feb 2014 11:51:09 +0100 Subject: [PATCH 10/24] move model validation into pattern classes and link from recurring_todo model --- app/models/recurring_todo.rb | 87 ++++--------------- .../abstract_repeat_pattern.rb | 72 ++++++++++++++- .../recurring_todos/daily_repeat_pattern.rb | 12 ++- .../recurring_todos/monthly_repeat_pattern.rb | 41 ++++++++- .../recurring_todos/weekly_repeat_pattern.rb | 21 +++++ .../recurring_todos/yearly_repeat_pattern.rb | 41 +++++++++ test/models/recurring_todo_test.rb | 4 +- 7 files changed, 201 insertions(+), 77 deletions(-) diff --git a/app/models/recurring_todo.rb b/app/models/recurring_todo.rb index 1a87c305..25eb512a 100644 --- a/app/models/recurring_todo.rb +++ b/app/models/recurring_todo.rb @@ -29,86 +29,31 @@ class RecurringTodo < ActiveRecord::Base validates_length_of :description, :maximum => 100 validates_length_of :notes, :maximum => 60000, :allow_nil => true - validate :period_specific_validations - validate :starts_and_ends_on_validations - validate :set_recurrence_on_validations + validate :period_validation + validate :pattern_specific_validations - def period_specific_validations - if %W[daily weekly monthly yearly].include?(recurring_period) - self.send("validate_#{recurring_period}") + def pattern_specific_validations + if pattern + pattern.validate else - errors.add(:recurring_period, "is an unknown recurrence pattern: '#{recurring_period}'") + errors[:recurring_todo] << "Invalid recurrence period '#{recurring_period}'" end end - def validate_daily - if (!only_work_days) && daily_every_x_days.blank? - errors[:base] << "Every other nth day may not be empty for recurrence setting" + def pattern + if valid_period? + @pattern = eval("RecurringTodos::#{recurring_period.capitalize}RepeatPattern.new(user)") + @pattern.build_from_recurring_todo(self) end + @pattern end - def validate_weekly - if weekly_every_x_week.blank? - errors[:base] << "Every other nth week may not be empty for recurrence setting" - end - something_set = false - %w{sunday monday tuesday wednesday thursday friday saturday}.each { |day| something_set ||= self.send("on_#{day}") } - errors[:base] << "You must specify at least one day on which the todo recurs" unless something_set + def valid_period? + %W[daily weekly monthly yearly].include?(recurring_period) end - def validate_monthly - case recurrence_selector - when 0 # 'monthly_every_x_day' - errors[:base] << "The day of the month may not be empty for recurrence setting" if monthly_every_x_day.blank? - errors[:base] << "Every other nth month may not be empty for recurrence setting" if monthly_every_x_month.blank? - when 1 # 'monthly_every_xth_day' - errors[:base] <<"Every other nth month may not be empty for recurrence setting" if monthly_every_x_month2.blank? - errors[:base] <<"The nth day of the month may not be empty for recurrence setting" if monthly_every_xth_day.blank? - errors[:base] <<"The day of the month may not be empty for recurrence setting" if monthly_day_of_week.blank? - else - raise Exception.new, "unexpected value of recurrence selector '#{recurrence_selector}'" - end - end - - def validate_yearly - case recurrence_selector - when 0 # 'yearly_every_x_day' - errors[:base] << "The month of the year may not be empty for recurrence setting" if yearly_month_of_year.blank? - errors[:base] << "The day of the month may not be empty for recurrence setting" if yearly_every_x_day.blank? - when 1 # 'yearly_every_xth_day' - errors[:base] << "The month of the year may not be empty for recurrence setting" if yearly_month_of_year2.blank? - errors[:base] << "The nth day of the month may not be empty for recurrence setting" if yearly_every_xth_day.blank? - errors[:base] << "The day of the week may not be empty for recurrence setting" if yearly_day_of_week.blank? - else - raise Exception.new, "unexpected value of recurrence selector '#{recurrence_selector}'" - end - end - - def starts_and_ends_on_validations - errors[:base] << "The start date needs to be filled in" if start_from.blank? - case ends_on - when 'ends_on_number_of_times' - errors[:base] << "The number of recurrences needs to be filled in for 'Ends on'" if number_of_occurences.blank? - when "ends_on_end_date" - errors[:base] << "The end date needs to be filled in for 'Ends on'" if end_date.blank? - else - errors[:base] << "The end of the recurrence is not selected" unless ends_on == "no_end_date" - end - end - - def set_recurrence_on_validations - # show always or x days before due date. x not null - case target - when 'show_from_date' - # no validations - when 'due_date' - errors[:base] << "Please select when to show the action" if show_always.nil? - unless show_always - errors[:base] << "Please fill in the number of days to show the todo before the due date" if show_from_delta.blank? - end - else - raise Exception.new, "unexpected value of recurrence target selector '#{target}'" - end + def period_validation + errors.add(:recurring_period, "is an unknown recurrence pattern: '#{recurring_period}'") unless valid_period? end # the following recurrence patterns can be stored: @@ -627,7 +572,7 @@ class RecurringTodo < ActiveRecord::Base def remove_from_project! self.project = nil self.save - end + end def clear_todos_association unless todos.nil? diff --git a/app/models/recurring_todos/abstract_repeat_pattern.rb b/app/models/recurring_todos/abstract_repeat_pattern.rb index 028d9ebf..5c7ac546 100644 --- a/app/models/recurring_todos/abstract_repeat_pattern.rb +++ b/app/models/recurring_todos/abstract_repeat_pattern.rb @@ -8,6 +8,30 @@ module RecurringTodos @user = user end + def start_from + get :start_from + end + + def end_date + get :end_date + end + + def ends_on + get :ends_on + end + + def target + get :target + end + + def show_always + get :show_always + end + + def show_from_delta + get :show_from_delta + end + def build_recurring_todo(attributes) @recurring_todo = @user.recurring_todos.build(attributes) end @@ -16,7 +40,53 @@ module RecurringTodos recurring_todo.assign_attributes(attributes) recurring_todo end - + + def build_from_recurring_todo(recurring_todo) + @recurring_todo = recurring_todo + @attributes = recurring_todo.attributes + end + + def validate + starts_and_ends_on_validations + set_recurrence_on_validations + end + + def starts_and_ends_on_validations + errors[:base] << "The start date needs to be filled in" if start_from.blank? + case ends_on + when 'ends_on_number_of_times' + errors[:base] << "The number of recurrences needs to be filled in for 'Ends on'" if number_of_occurences.blank? + when "ends_on_end_date" + errors[:base] << "The end date needs to be filled in for 'Ends on'" if end_date.blank? + else + errors[:base] << "The end of the recurrence is not selected" unless ends_on == "no_end_date" + end + end + + def set_recurrence_on_validations + # show always or x days before due date. x not null + case target + when 'show_from_date' + # no validations + when 'due_date' + errors[:base] << "Please select when to show the action" if show_always.nil? + unless show_always + errors[:base] << "Please fill in the number of days to show the todo before the due date" if show_from_delta.blank? + end + else + raise Exception.new, "unexpected value of recurrence target selector '#{target}'" + end + end + + def errors + @recurring_todo.errors + end + + def get attribute + # handle attribute as symbol and as string + @attributes[attribute] || @attributes[attribute.to_s] + end + end end \ No newline at end of file diff --git a/app/models/recurring_todos/daily_repeat_pattern.rb b/app/models/recurring_todos/daily_repeat_pattern.rb index 6b742018..87f14350 100644 --- a/app/models/recurring_todos/daily_repeat_pattern.rb +++ b/app/models/recurring_todos/daily_repeat_pattern.rb @@ -7,9 +7,19 @@ module RecurringTodos end def every_x_days - @recurring_todo.every_other1 + get :every_other1 end + def only_work_days? + get :only_work_days + end + + def validate + super + errors[:base] << "Every other nth day may not be empty for this daily recurrence setting" if (!only_work_days?) && every_x_days.blank? + end + + end end \ No newline at end of file diff --git a/app/models/recurring_todos/monthly_repeat_pattern.rb b/app/models/recurring_todos/monthly_repeat_pattern.rb index 57c0027c..703849a8 100644 --- a/app/models/recurring_todos/monthly_repeat_pattern.rb +++ b/app/models/recurring_todos/monthly_repeat_pattern.rb @@ -6,12 +6,49 @@ module RecurringTodos super user end + def recurrence_selector + get :recurrence_selector + end + def every_x_day? - @recurring_todo.recurrence_selector == 0 + get(:recurrence_selector) == 0 end def every_xth_day? - @recurring_todo.recurrence_selector == 1 + get(:recurrence_selector) == 1 + end + + def every_x_month + # in case monthly pattern is every day x, return every_other2 otherwise + # return a default value + get(:recurrence_selector) == 0 ? get(:every_other2) : 1 + end + + def every_x_month2 + # in case monthly pattern is every xth day, return every_other2 otherwise + # return a default value + get(:recurrence_selector) == 1 ? get(:every_other2) : 1 + end + + def every_xth_day(default=nil) + get(:every_other3) || default + end + + def day_of_week + get :every_count + end + + def validate + super + case recurrence_selector + when 0 # 'monthly_every_x_day' + errors[:base] << "Every other nth month may not be empty for recurrence setting" if every_x_month.blank? + when 1 # 'every_xth_day' + errors[:base] <<"Every other nth month may not be empty for recurrence setting" if every_x_month2.blank? + errors[:base] <<"The day of the month may not be empty for recurrence setting" if day_of_week.blank? + else + raise Exception.new, "unexpected value of recurrence selector '#{recurrence_selector}'" + end end end diff --git a/app/models/recurring_todos/weekly_repeat_pattern.rb b/app/models/recurring_todos/weekly_repeat_pattern.rb index 484e01e3..dbe5e65d 100644 --- a/app/models/recurring_todos/weekly_repeat_pattern.rb +++ b/app/models/recurring_todos/weekly_repeat_pattern.rb @@ -6,6 +6,27 @@ module RecurringTodos super user end + def every_x_week + get :every_other1 + end + + { monday: 1, tuesday: 2, wednesday: 3, thursday: 4, friday: 5, saturday: 6, sunday: 0 }.each do |day, number| + define_method("on_#{day}") do + on_xday number + end + end + + def on_xday(n) + get(:every_day) && get(:every_day)[n, 1] != ' ' + end + + def validate + super + errors[:base] << "Every other nth week may not be empty for weekly recurrence setting" if every_x_week.blank? + something_set = %w{sunday monday tuesday wednesday thursday friday saturday}.inject(false) { |set, day| set || self.send("on_#{day}") } + errors[:base] << "You must specify at least one day on which the todo recurs" unless something_set + end + end end \ No newline at end of file diff --git a/app/models/recurring_todos/yearly_repeat_pattern.rb b/app/models/recurring_todos/yearly_repeat_pattern.rb index a9688019..27a34ce0 100644 --- a/app/models/recurring_todos/yearly_repeat_pattern.rb +++ b/app/models/recurring_todos/yearly_repeat_pattern.rb @@ -6,6 +6,47 @@ module RecurringTodos super user end + def recurrence_selector + get :recurrence_selector + end + + def month_of_year + get :every_other2 + end + + def every_x_day + get :every_other1 + end + + def every_xth_day + get :every_other3 + end + + def day_of_week + get :every_count + end + + def month_of_year2 + # if recurrence pattern is every xth day in a month, return month otherwise + # return a default value + get(:recurrence_selector) == 1 ? get(:every_other2) : Time.zone.now.month + end + + def validate + super + case recurrence_selector + when 0 # 'yearly_every_x_day' + errors[:base] << "The month of the year may not be empty for recurrence setting" if month_of_year.blank? + errors[:base] << "The day of the month may not be empty for recurrence setting" if every_x_day.blank? + when 1 # 'yearly_every_xth_day' + errors[:base] << "The month of the year may not be empty for recurrence setting" if month_of_year2.blank? + errors[:base] << "The nth day of the month may not be empty for recurrence setting" if every_xth_day.blank? + errors[:base] << "The day of the week may not be empty for recurrence setting" if day_of_week.blank? + else + raise "unexpected value of recurrence selector '#{recurrence_selector}'" + end + end + end end \ No newline at end of file diff --git a/test/models/recurring_todo_test.rb b/test/models/recurring_todo_test.rb index a1bd2d1e..18ee005d 100644 --- a/test/models/recurring_todo_test.rb +++ b/test/models/recurring_todo_test.rb @@ -277,7 +277,7 @@ class RecurringTodoTest < ActiveSupport::TestCase def test_toggle_completion assert @yearly.active? - assert @yearly.toggle_completion! + assert @yearly.toggle_completion!, "toggle of completion should succeed" assert @yearly.completed? # entering completed state should set completed_at @@ -329,7 +329,7 @@ class RecurringTodoTest < ActiveSupport::TestCase assert_raise(Exception){ @every_month.valid? } @yearly.recurrence_selector = 99 - assert_raise(Exception){ @yearly.valid? } + assert_raise(RuntimeError){ @yearly.valid? } end def test_every_n_the_day_must_be_filled From bad91e8d10d1cdd82ce7f200cc7658d6f43ea443 Mon Sep 17 00:00:00 2001 From: Reinier Balt Date: Sat, 8 Feb 2014 12:26:49 +0100 Subject: [PATCH 11/24] make validations a bit more dry --- .../abstract_repeat_pattern.rb | 20 ++++++++++++------- .../recurring_todos/monthly_repeat_pattern.rb | 7 ++++--- .../recurring_todos/weekly_repeat_pattern.rb | 2 +- .../recurring_todos/yearly_repeat_pattern.rb | 10 +++++----- 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/app/models/recurring_todos/abstract_repeat_pattern.rb b/app/models/recurring_todos/abstract_repeat_pattern.rb index 5c7ac546..c063ea45 100644 --- a/app/models/recurring_todos/abstract_repeat_pattern.rb +++ b/app/models/recurring_todos/abstract_repeat_pattern.rb @@ -46,18 +46,26 @@ module RecurringTodos @attributes = recurring_todo.attributes end + def validate_not_blank(object, msg) + errors[:base] << msg if object.blank? + end + + def validate_not_nil(object, msg) + errors[:base] << msg if object.nil? + end + def validate starts_and_ends_on_validations set_recurrence_on_validations end def starts_and_ends_on_validations - errors[:base] << "The start date needs to be filled in" if start_from.blank? + validate_not_blank(start_from, "The start date needs to be filled in") case ends_on when 'ends_on_number_of_times' - errors[:base] << "The number of recurrences needs to be filled in for 'Ends on'" if number_of_occurences.blank? + validate_not_blank(number_of_occurences, "The number of recurrences needs to be filled in for 'Ends on'") when "ends_on_end_date" - errors[:base] << "The end date needs to be filled in for 'Ends on'" if end_date.blank? + validate_not_blank(end_date, "The end date needs to be filled in for 'Ends on'") else errors[:base] << "The end of the recurrence is not selected" unless ends_on == "no_end_date" end @@ -69,10 +77,8 @@ module RecurringTodos when 'show_from_date' # no validations when 'due_date' - errors[:base] << "Please select when to show the action" if show_always.nil? - unless show_always - errors[:base] << "Please fill in the number of days to show the todo before the due date" if show_from_delta.blank? - end + validate_not_nil(show_always, "Please select when to show the action") + validate_not_blank(show_from_delta, "Please fill in the number of days to show the todo before the due date") unless show_always else raise Exception.new, "unexpected value of recurrence target selector '#{target}'" end diff --git a/app/models/recurring_todos/monthly_repeat_pattern.rb b/app/models/recurring_todos/monthly_repeat_pattern.rb index 703849a8..3561ba60 100644 --- a/app/models/recurring_todos/monthly_repeat_pattern.rb +++ b/app/models/recurring_todos/monthly_repeat_pattern.rb @@ -40,12 +40,13 @@ module RecurringTodos def validate super + case recurrence_selector when 0 # 'monthly_every_x_day' - errors[:base] << "Every other nth month may not be empty for recurrence setting" if every_x_month.blank? + validate_not_blank(every_x_month, "Every other nth month may not be empty for recurrence setting") when 1 # 'every_xth_day' - errors[:base] <<"Every other nth month may not be empty for recurrence setting" if every_x_month2.blank? - errors[:base] <<"The day of the month may not be empty for recurrence setting" if day_of_week.blank? + validate_not_blank(every_x_month2, "Every other nth month may not be empty for recurrence setting") + validate_not_blank(day_of_week, "The day of the month may not be empty for recurrence setting") else raise Exception.new, "unexpected value of recurrence selector '#{recurrence_selector}'" end diff --git a/app/models/recurring_todos/weekly_repeat_pattern.rb b/app/models/recurring_todos/weekly_repeat_pattern.rb index dbe5e65d..721907a8 100644 --- a/app/models/recurring_todos/weekly_repeat_pattern.rb +++ b/app/models/recurring_todos/weekly_repeat_pattern.rb @@ -22,7 +22,7 @@ module RecurringTodos def validate super - errors[:base] << "Every other nth week may not be empty for weekly recurrence setting" if every_x_week.blank? + validate_not_blank(every_x_week, "Every other nth week may not be empty for weekly recurrence setting") something_set = %w{sunday monday tuesday wednesday thursday friday saturday}.inject(false) { |set, day| set || self.send("on_#{day}") } errors[:base] << "You must specify at least one day on which the todo recurs" unless something_set end diff --git a/app/models/recurring_todos/yearly_repeat_pattern.rb b/app/models/recurring_todos/yearly_repeat_pattern.rb index 27a34ce0..948b4dca 100644 --- a/app/models/recurring_todos/yearly_repeat_pattern.rb +++ b/app/models/recurring_todos/yearly_repeat_pattern.rb @@ -36,12 +36,12 @@ module RecurringTodos super case recurrence_selector when 0 # 'yearly_every_x_day' - errors[:base] << "The month of the year may not be empty for recurrence setting" if month_of_year.blank? - errors[:base] << "The day of the month may not be empty for recurrence setting" if every_x_day.blank? + validate_not_blank(month_of_year, "The month of the year may not be empty for recurrence setting") + validate_not_blank(every_x_day, "The day of the month may not be empty for recurrence setting") when 1 # 'yearly_every_xth_day' - errors[:base] << "The month of the year may not be empty for recurrence setting" if month_of_year2.blank? - errors[:base] << "The nth day of the month may not be empty for recurrence setting" if every_xth_day.blank? - errors[:base] << "The day of the week may not be empty for recurrence setting" if day_of_week.blank? + validate_not_blank(month_of_year2, "The month of the year may not be empty for recurrence setting") + validate_not_blank(every_xth_day, "The nth day of the month may not be empty for recurrence setting") + validate_not_blank(day_of_week, "The day of the week may not be empty for recurrence setting") else raise "unexpected value of recurrence selector '#{recurrence_selector}'" end From 59a29c664a2b1013a0c5aa353fa182ee840a7eb4 Mon Sep 17 00:00:00 2001 From: Reinier Balt Date: Sun, 9 Feb 2014 21:48:52 +0100 Subject: [PATCH 12/24] move form specific accessors out of model --- app/models/recurring_todo.rb | 152 ------------------ .../abstract_recurring_todos_builder.rb | 80 ++++----- .../abstract_repeat_pattern.rb | 15 +- .../daily_recurring_todos_builder.rb | 19 +-- .../monthly_recurring_todos_builder.rb | 10 +- .../recurring_todos_builder.rb | 69 +------- .../weekly_recurring_todos_builder.rb | 6 +- .../yearly_recurring_todos_builder.rb | 19 +-- lib/tracks/attribute_handler.rb | 104 ++++++++++++ test/models/recurring_todo_test.rb | 30 ++-- .../abstract_recurring_todos_builder_test.rb | 22 +-- .../daily_recurring_todos_builder_test.rb | 32 ++-- .../monthly_recurring_todos_builder_test.rb | 38 ++--- .../recurring_todos_builder_test.rb | 4 +- .../weekly_recurring_todos_builder_test.rb | 32 ++-- .../yearly_recurring_todos_builder_test.rb | 32 ++-- 16 files changed, 279 insertions(+), 385 deletions(-) create mode 100644 lib/tracks/attribute_handler.rb diff --git a/app/models/recurring_todo.rb b/app/models/recurring_todo.rb index 25eb512a..9fa34198 100644 --- a/app/models/recurring_todo.rb +++ b/app/models/recurring_todo.rb @@ -78,36 +78,6 @@ class RecurringTodo < ActiveRecord::Base # choosing between both options is done on recurrence_selector where 0 is # for first type and 1 for second type - # DAILY - - def daily_selector=(selector) - case selector - when 'daily_every_x_day' - only_work_days = false - when 'daily_every_work_day' - only_work_days = true - else - raise Exception.new, "unknown daily recurrence pattern: '#{selector}'" - end - end - - def daily_every_x_days=(x) - self.every_other1 = x if recurring_period=='daily' - end - - def daily_every_x_days - every_other1 - end - - # WEEKLY - - def weekly_every_x_week=(x) - self.every_other1 = x if recurring_period=='weekly' - end - - def weekly_every_x_week - self.every_other1 - end def switch_week_day(day, position) self.every_day = ' ' if self.every_day.nil? @@ -118,128 +88,6 @@ class RecurringTodo < ActiveRecord::Base define_method("weekly_return_#{day}=") do |selector| switch_week_day(selector, number) if recurring_period=='weekly' end - - define_method("on_#{day}") do - on_xday number - end - end - - def on_xday(n) - every_day && every_day[n, 1] != ' ' - end - - # MONTHLY - - def monthly_selector=(selector) - self.recurrence_selector = ( (selector=='monthly_every_x_day') ? 0 : 1) if recurring_period=='monthly' - end - - def monthly_every_x_day=(x) - self.every_other1 = x if recurring_period=='monthly' - end - - def monthly_every_x_day - self.every_other1 - end - - def is_monthly_every_x_day - recurring_period == 'monthly' && self.recurrence_selector == 0 - end - - def is_monthly_every_xth_day - recurring_period == 'monthly' && self.recurrence_selector == 1 - end - - def monthly_every_x_month=(x) - self.every_other2 = x if recurring_period=='monthly' && recurrence_selector == 0 - end - - def monthly_every_x_month - # in case monthly pattern is every day x, return every_other2 otherwise - # return a default value - self.recurrence_selector == 0 ? self.every_other2 : 1 - end - - def monthly_every_x_month2=(x) - self.every_other2 = x if recurring_period=='monthly' && recurrence_selector == 1 - end - - def monthly_every_x_month2 - # in case monthly pattern is every xth day, return every_other2 otherwise - # return a default value - self.recurrence_selector == 1 ? self.every_other2 : 1 - end - - def monthly_every_xth_day=(x) - self.every_other3 = x if recurring_period=='monthly' - end - - def monthly_every_xth_day(default=nil) - self.every_other3 || default - end - - def monthly_day_of_week=(dow) - self.every_count = dow if recurring_period=='monthly' - end - - def monthly_day_of_week - self.every_count - end - - # YEARLY - - def yearly_selector=(selector) - self.recurrence_selector = ( (selector=='yearly_every_x_day') ? 0 : 1) if recurring_period=='yearly' - end - - def yearly_month_of_year=(moy) - self.every_other2 = moy if self.recurring_period=='yearly' && self.recurrence_selector == 0 - end - - def yearly_month_of_year - # if recurrence pattern is every x day in a month, return month otherwise - # return a default value - self.recurrence_selector == 0 ? self.every_other2 : Time.zone.now.month - end - - def yearly_month_of_year2=(moy) - self.every_other2 = moy if self.recurring_period=='yearly' && self.recurrence_selector == 1 - end - - def yearly_month_of_year2 - # if recurrence pattern is every xth day in a month, return month otherwise - # return a default value - self.recurrence_selector == 1 ? self.every_other2 : Time.zone.now.month - end - - def yearly_every_x_day=(x) - self.every_other1 = x if recurring_period=='yearly' - end - - def yearly_every_x_day - self.every_other1 - end - - def yearly_every_xth_day=(x) - self.every_other3 = x if recurring_period=='yearly' - end - - def yearly_every_xth_day - self.every_other3 - end - - def yearly_day_of_week=(dow) - self.every_count=dow if recurring_period=='yearly' - end - - def yearly_day_of_week - self.every_count - end - - # target - - def recurring_target=(t) - self.target = t end def recurring_target_as_text diff --git a/app/models/recurring_todos/abstract_recurring_todos_builder.rb b/app/models/recurring_todos/abstract_recurring_todos_builder.rb index 740926e9..e2d610e7 100644 --- a/app/models/recurring_todos/abstract_recurring_todos_builder.rb +++ b/app/models/recurring_todos/abstract_recurring_todos_builder.rb @@ -5,34 +5,34 @@ module RecurringTodos attr_reader :mapped_attributes, :pattern def initialize(user, attributes, pattern_class) - @user = user - - @attributes = attributes - @filterred_attributes = filter_attributes(@attributes) - @selector = get_selector(selector_key) - @mapped_attributes = map_attributes(@filterred_attributes) - - @pattern = pattern_class.new(user) - @pattern.attributes = @mapped_attributes - + @user = user @saved = false + + @attributes = attributes + @selector = get_selector(selector_key) + @filterred_attributes = filter_attributes(@attributes) + @mapped_attributes = map_attributes(@filterred_attributes) + + @pattern = pattern_class.new(user) + @pattern.attributes = @mapped_attributes + end # build does not add tags. For tags, the recurring todos needs to be saved def build @recurring_todo = @pattern.build_recurring_todo(@mapped_attributes) - @recurring_todo.context = @filterred_attributes[:context] - @recurring_todo.project = @filterred_attributes[:project] + @recurring_todo.context = @filterred_attributes.get(:context) + @recurring_todo.project = @filterred_attributes.get(:project) end def update(recurring_todo) @recurring_todo = @pattern.update_recurring_todo(recurring_todo, @mapped_attributes) - @recurring_todo.context = @filterred_attributes[:context] - @recurring_todo.project = @filterred_attributes[:project] + @recurring_todo.context = @filterred_attributes.get(:context) + @recurring_todo.project = @filterred_attributes.get(:project) @saved = @recurring_todo.save - @recurring_todo.tag_with(@filterred_attributes[:tag_list]) if @saved && @filterred_attributes[:tag_list].present? + @recurring_todo.tag_with(@filterred_attributes.get(:tag_list)) if @saved && @filterred_attributes.get(:tag_list).present? @recurring_todo.reload return @saved @@ -41,12 +41,12 @@ module RecurringTodos def save build @saved = @recurring_todo.save - @recurring_todo.tag_with(@filterred_attributes[:tag_list]) if @saved && @filterred_attributes[:tag_list].present? + @recurring_todo.tag_with(@filterred_attributes.get(:tag_list)) if @saved && @filterred_attributes.get(:tag_list).present? return @saved end def saved_recurring_todo - raise(Exception.new, @recurring_todo.valid? ? "Recurring todo was not saved yet" : "Recurring todos was not saved because of validation errors") if !@saved + raise(Exception.new, @recurring_todo.valid? ? "Recurring todo was not saved yet" : "Recurring todos was not saved because of validation errors") unless @saved @recurring_todo end @@ -61,27 +61,29 @@ module RecurringTodos def filter_attributes(attributes) filterred_attributes = filter_generic_attributes(attributes) - attributes_to_filter.each{|key| filterred_attributes[key] = attributes[key] if attributes.key?(key)} + attributes_to_filter.each{|key| filterred_attributes.set(key, attributes.get(key)) if attributes.key?(key)} filterred_attributes end def filter_generic_attributes(attributes) - { - recurring_period: attributes["recurring_period"], - description: attributes['description'], - notes: attributes['notes'], + return Tracks::AttributeHandler.new(@user, { + recurring_period: attributes.get(:recurring_period), + description: attributes.get(:description), + notes: attributes.get(:notes), tag_list: tag_list_or_empty_string(attributes), - start_from: attributes['start_from'], - end_date: attributes['end_date'], - ends_on: attributes['ends_on'], - show_always: attributes['show_always'], - target: attributes['target'], - project: attributes[:project], - context: attributes[:context], - target: attributes['recurring_target'], - show_from_delta: attributes['recurring_show_days_before'], - show_always: attributes['recurring_show_always'] - } + start_from: attributes.get(:start_from), + end_date: attributes.get(:end_date), + ends_on: attributes.get(:ends_on), + show_always: attributes.get(:show_always), + target: attributes.get(:target), + project: attributes.get(:project), + context: attributes.get(:context), + project_id: attributes.get(:project_id), + context_id: attributes.get(:context_id), + target: attributes.get(:recurring_target), + show_from_delta: attributes.get(:recurring_show_days_before), + show_always: attributes.get(:recurring_show_always) + }) end def map_attributes @@ -91,7 +93,7 @@ module RecurringTodos # helper method to be used in mapped_attributes in subclasses def map(mapping, key, source_key) - mapping[key] = mapping[source_key] + mapping.set(key, mapping.get(source_key)) mapping.except(source_key) end @@ -102,10 +104,12 @@ module RecurringTodos def get_selector(key) return nil if key.nil? - raise Exception.new, "recurrence selector pattern (#{key}) not given" unless @attributes.key?(key) - raise Exception.new, "unknown recurrence selector pattern: '#{@attributes[key]}'" unless valid_selector?(@attributes[key]) - selector = @attributes[key] + raise Exception.new, "recurrence selector pattern (#{key}) not given" unless @attributes.selector_key_present?(key) + selector = @attributes.get(key) + + raise Exception.new, "unknown recurrence selector pattern: '#{selector}'" unless valid_selector?(selector) + @attributes = @attributes.except(key) return selector end @@ -118,7 +122,7 @@ module RecurringTodos def tag_list_or_empty_string(attributes) # avoid nil - attributes['tag_list'].blank? ? "" : attributes['tag_list'].strip + attributes.get(:tag_list).blank? ? "" : attributes.get(:tag_list).strip end end diff --git a/app/models/recurring_todos/abstract_repeat_pattern.rb b/app/models/recurring_todos/abstract_repeat_pattern.rb index c063ea45..cf2f6a76 100644 --- a/app/models/recurring_todos/abstract_repeat_pattern.rb +++ b/app/models/recurring_todos/abstract_repeat_pattern.rb @@ -32,18 +32,18 @@ module RecurringTodos get :show_from_delta end - def build_recurring_todo(attributes) - @recurring_todo = @user.recurring_todos.build(attributes) + def build_recurring_todo(attribute_handler) + @recurring_todo = @user.recurring_todos.build(attribute_handler.attributes) end - def update_recurring_todo(recurring_todo, attributes) - recurring_todo.assign_attributes(attributes) + def update_recurring_todo(recurring_todo, attribute_handler) + recurring_todo.assign_attributes(attribute_handler.attributes) recurring_todo end def build_from_recurring_todo(recurring_todo) @recurring_todo = recurring_todo - @attributes = recurring_todo.attributes + @attributes = Tracks::AttributeHandler.new(@user, recurring_todo.attributes) end def validate_not_blank(object, msg) @@ -88,9 +88,8 @@ module RecurringTodos @recurring_todo.errors end - def get attribute - # handle attribute as symbol and as string - @attributes[attribute] || @attributes[attribute.to_s] + def get(attribute) + @attributes.get attribute end end diff --git a/app/models/recurring_todos/daily_recurring_todos_builder.rb b/app/models/recurring_todos/daily_recurring_todos_builder.rb index 6ce83809..fb8a3289 100644 --- a/app/models/recurring_todos/daily_recurring_todos_builder.rb +++ b/app/models/recurring_todos/daily_recurring_todos_builder.rb @@ -12,25 +12,18 @@ module RecurringTodos end def map_attributes(mapping) - mapping[:only_work_days] = only_work_days?(@selector) - - mapping[:every_other1] = mapping['daily_every_x_days'] - mapping = mapping.except('daily_every_x_days') - - mapping + mapping.set(:only_work_days, only_work_days?(@selector)) + mapping.set(:every_other1, mapping.get(:daily_every_x_days)) + mapping.except(:daily_every_x_days) end def only_work_days?(daily_selector) - case daily_selector - when 'daily_every_x_day' - return false - when 'daily_every_work_day' - return true - end + { 'daily_every_x_day' => false, + 'daily_every_work_day' => true}[daily_selector] end def selector_key - 'daily_selector' + :daily_selector end def valid_selector?(selector) diff --git a/app/models/recurring_todos/monthly_recurring_todos_builder.rb b/app/models/recurring_todos/monthly_recurring_todos_builder.rb index 39aaefb5..a70cd257 100644 --- a/app/models/recurring_todos/monthly_recurring_todos_builder.rb +++ b/app/models/recurring_todos/monthly_recurring_todos_builder.rb @@ -18,12 +18,10 @@ module RecurringTodos mapping = map(mapping, :every_other3, 'monthly_every_xth_day') mapping = map(mapping, :every_count, 'monthly_day_of_week') - mapping[:every_other2] = mapping[get_every_other2] - mapping = mapping.except('monthly_every_x_month').except('monthly_every_x_month2') + mapping.set(:recurrence_selector, get_recurrence_selector) - mapping[:recurrence_selector] = get_recurrence_selector - - mapping + mapping.set(:every_other2, mapping.get(get_every_other2)) + mapping.except('monthly_every_x_month').except('monthly_every_x_month2') end def get_recurrence_selector @@ -35,7 +33,7 @@ module RecurringTodos end def selector_key - 'monthly_selector' + :monthly_selector end def valid_selector?(selector) diff --git a/app/models/recurring_todos/recurring_todos_builder.rb b/app/models/recurring_todos/recurring_todos_builder.rb index 4d808fd9..569baa3d 100644 --- a/app/models/recurring_todos/recurring_todos_builder.rb +++ b/app/models/recurring_todos/recurring_todos_builder.rb @@ -6,20 +6,20 @@ module RecurringTodos def initialize (user, attributes) @user = user - @attributes = attributes + @attributes = Tracks::AttributeHandler.new(@user, attributes) parse_dates parse_project parse_context - @builder = create_builder(attributes['recurring_period']) + @builder = create_builder(@attributes.get(:recurring_period)) end def create_builder(selector) if %w{daily weekly monthly yearly}.include?(selector) return eval("RecurringTodos::#{selector.capitalize}RecurringTodosBuilder.new(@user, @attributes)") else - raise Exception.new("Unknown recurrence selector in recurring_period (#{selector})") + raise Exception.new("Unknown recurrence selector in :recurring_period (#{selector})") end end @@ -53,72 +53,15 @@ module RecurringTodos private def parse_dates - %w{end_date start_from}.each {|date| @attributes[date] = @user.prefs.parse_date(@attributes[date])} + %w{end_date start_from}.each {|date| @attributes.parse_date date } end def parse_project - @project, @new_project_created = parse(:project, @user.projects, project_name) + @project, @new_project_created = @attributes.parse_collection(:project, @user.projects, @attributes.project_name) end def parse_context - @context, @new_context_created = parse(:context, @user.contexts, context_name) - end - - def parse(object_type, relation, name) - object = nil - new_object_created = false - - if specified_by_name?(object_type) - # find or create context or project by given name - object, new_object_created = find_or_create_by_name(relation, name) - else - # find context or project by its id - object = attribute_with_id_of(object_type).present? ? relation.find(attribute_with_id_of(object_type)) : nil - end - @attributes[object_type] = object - return object, new_object_created - end - - def attribute_with_id_of(object_type) - map = { project: 'project_id', context: 'context_id' } - @attributes[map[object_type]] - end - - def find_or_create_by_name(relation, name) - new_object_created = false - - object = relation.where(:name => name).first - unless object - object = relation.build(:name => name) - new_object_created = true - end - - return object, new_object_created - end - - def specified_by_name?(object_type) - self.send("#{object_type}_specified_by_name?") - end - - def project_specified_by_name? - return false if @attributes['project_id'].present? - return false if project_name.blank? - return false if project_name == 'None' - true - end - - def context_specified_by_name? - return false if @attributes['context_id'].present? - return false if context_name.blank? - true - end - - def project_name - @attributes['project_name'].try(:strip) - end - - def context_name - @attributes['context_name'].try(:strip) + @context, @new_context_created = @attributes.parse_collection(:context, @user.contexts, @attributes.context_name) end end diff --git a/app/models/recurring_todos/weekly_recurring_todos_builder.rb b/app/models/recurring_todos/weekly_recurring_todos_builder.rb index 46a67055..17ec13ba 100644 --- a/app/models/recurring_todos/weekly_recurring_todos_builder.rb +++ b/app/models/recurring_todos/weekly_recurring_todos_builder.rb @@ -19,10 +19,10 @@ module RecurringTodos end def map_day(mapping, key, source_key, index) - mapping[key] ||= ' ' # avoid nil - mapping[source_key] ||= ' ' # avoid nil + mapping.set_if_nil(key, ' ') # avoid nil + mapping.set_if_nil(source_key, ' ') # avoid nil - mapping[key] = mapping[key][0, index] + mapping[source_key] + mapping[key][index+1, mapping[key].length] + mapping.set(key, mapping.get(key)[0, index] + mapping.get(source_key) + mapping.get(key)[index+1, mapping.get(key).length]) mapping end diff --git a/app/models/recurring_todos/yearly_recurring_todos_builder.rb b/app/models/recurring_todos/yearly_recurring_todos_builder.rb index d34c49f3..091509d6 100644 --- a/app/models/recurring_todos/yearly_recurring_todos_builder.rb +++ b/app/models/recurring_todos/yearly_recurring_todos_builder.rb @@ -13,24 +13,22 @@ module RecurringTodos end def map_attributes(mapping) - mapping[:recurrence_selector] = get_recurrence_selector - - mapping[:every_other2] = mapping[get_every_other2] - mapping = mapping.except('yearly_month_of_year').except('yearly_month_of_year2') + mapping.set(:recurrence_selector, get_recurrence_selector) + mapping.set(:every_other2, mapping.get(get_every_other2)) mapping = map(mapping, :every_other1, 'yearly_every_x_day') mapping = map(mapping, :every_other3, 'yearly_every_xth_day') mapping = map(mapping, :every_count, 'yearly_day_of_week') - mapping + mapping.except(:yearly_month_of_year).except(:yearly_month_of_year2) end def selector_key - 'yearly_selector' + :yearly_selector end def valid_selector?(selector) - %w{yearly_every_x_day yearly_every_xth_day}.include?(selector) + %w{yearly_every_x_day yearly_every_xth_day}.include?(selector.to_s) end def get_recurrence_selector @@ -38,12 +36,7 @@ module RecurringTodos end def get_every_other2 - case get_recurrence_selector - when 0 - 'yearly_month_of_year' - when 1 - 'yearly_month_of_year2' - end + { 0 => :yearly_month_of_year, 1 => :yearly_month_of_year2 }[get_recurrence_selector] end end diff --git a/lib/tracks/attribute_handler.rb b/lib/tracks/attribute_handler.rb new file mode 100644 index 00000000..c9901c93 --- /dev/null +++ b/lib/tracks/attribute_handler.rb @@ -0,0 +1,104 @@ +module Tracks + + class AttributeHandler + attr_reader :attributes + + def initialize(user, attributes) + @user = user + @orig_attributes = attributes + @attributes = normalize(attributes) + end + + def get(attribute) + @attributes[attribute.to_sym] + end + + def set(key, value) + @attributes[key.to_sym] = value + end + + def set_if_nil(key, value) + @attributes[key.to_sym] ||= value + end + + def except(key) + AttributeHandler.new(@user, @attributes.except(key.to_sym)) + end + + def key?(key) + @attributes.key?(key.to_sym) + end + + def selector_key_present?(key) + @attributes.key?(key.to_sym) + end + + def parse_date(date) + set(date, @user.prefs.parse_date(get(date))) + end + + def parse_collection(object_type, relation, name) + object = nil + new_object_created = false + + if specified_by_name?(object_type) + # find or create context or project by given name + object, new_object_created = find_or_create_by_name(relation, name) + else + # find context or project by its id + object = attribute_with_id_of(object_type).present? ? relation.find(attribute_with_id_of(object_type)) : nil + end + @attributes[object_type] = object + return object, new_object_created + end + + def attribute_with_id_of(object_type) + map = { project: 'project_id', context: 'context_id' } + get map[object_type] + end + + def find_or_create_by_name(relation, name) + new_object_created = false + + object = relation.where(:name => name).first + unless object + object = relation.build(:name => name) + new_object_created = true + end + + return object, new_object_created + end + + def specified_by_name?(object_type) + self.send("#{object_type}_specified_by_name?") + end + + def project_specified_by_name? + return false if get(:project_id).present? + return false if project_name.blank? + return false if project_name == 'None' + true + end + + def context_specified_by_name? + return false if get(:context_id).present? + return false if context_name.blank? + true + end + + def project_name + get(:project_name).try(:strip) + end + + def context_name + get(:context_name).try(:strip) + end + + def normalize(attributes) + # make sure the hash keys are all symbols + Hash[attributes.map{|k,v| [k.to_sym,v]}] + end + + end + +end \ No newline at end of file diff --git a/test/models/recurring_todo_test.rb b/test/models/recurring_todo_test.rb index 18ee005d..9279d5f1 100644 --- a/test/models/recurring_todo_test.rb +++ b/test/models/recurring_todo_test.rb @@ -352,9 +352,9 @@ class RecurringTodoTest < ActiveSupport::TestCase end def test_set_every_n_days_from_form_input - todo = RecurringTodo.new({ + builder = RecurringTodos::RecurringTodosBuilder.new(users(:admin_user), { :description => "Task every 2 days", - :context => Context.first, + :context_id => Context.first.id, :recurring_target => "show_from_date", :start_from => "01/01/01", :ends_on => "no_end_date", @@ -362,14 +362,17 @@ class RecurringTodoTest < ActiveSupport::TestCase :daily_selector => "daily_every_x_day", :daily_every_x_days => 2, }) + builder.save + todo = builder.saved_recurring_todo + assert todo.valid?, todo.errors.full_messages assert_equal 2, todo.every_other1 end def test_set_every_n_weeks_from_form_input - todo = RecurringTodo.new({ + builder = RecurringTodos::RecurringTodosBuilder.new(users(:admin_user), { :description => "Task every 3 weeks", - :context => Context.first, + :context_id => Context.first.id, :recurring_target => "show_from_date", :start_from => "01/01/01", :ends_on => "no_end_date", @@ -377,15 +380,18 @@ class RecurringTodoTest < ActiveSupport::TestCase :weekly_every_x_week => 3, :weekly_return_monday => "m", }) + builder.save + todo = builder.saved_recurring_todo + assert todo.valid?, todo.errors.full_messages assert_equal 3, todo.every_other1 - assert todo.on_monday + assert todo.pattern.on_monday end def test_set_every_n_months_from_form_input - todo = RecurringTodo.new({ + builder = RecurringTodos::RecurringTodosBuilder.new(users(:admin_user), { :description => "Task every 4 months", - :context => Context.first, + :context_id => Context.first.id, :recurring_target => "show_from_date", :start_from => "01/01/01", :ends_on => "no_end_date", @@ -394,14 +400,17 @@ class RecurringTodoTest < ActiveSupport::TestCase :monthly_every_x_day => 1, :monthly_every_x_month => 4, }) + builder.save + todo = builder.saved_recurring_todo + assert todo.valid?, todo.errors.full_messages assert_equal 4, todo.every_other2 end def test_set_yearly_from_form_input - todo = RecurringTodo.new({ + builder = RecurringTodos::RecurringTodosBuilder.new(users(:admin_user), { :description => "Task every year in May", - :context => Context.first, + :context_id => Context.first.id, :recurring_target => "show_from_date", :start_from => "01/01/01", :ends_on => "no_end_date", @@ -410,6 +419,9 @@ class RecurringTodoTest < ActiveSupport::TestCase :yearly_every_x_day => 15, :yearly_month_of_year => 5, }) + builder.save + todo = builder.saved_recurring_todo + assert todo.valid?, todo.errors.full_messages assert_equal 5, todo.every_other2 end diff --git a/test/models/recurring_todos/abstract_recurring_todos_builder_test.rb b/test/models/recurring_todos/abstract_recurring_todos_builder_test.rb index 86efb238..c2ac5575 100644 --- a/test/models/recurring_todos/abstract_recurring_todos_builder_test.rb +++ b/test/models/recurring_todos/abstract_recurring_todos_builder_test.rb @@ -20,7 +20,7 @@ module RecurringTodos end def test_filter_attributes_should_throw_exception - attributes = { + attributes = Tracks::AttributeHandler.new(@admin, { 'recurring_period' => "daily", 'description' => "test", 'tag_list' => "tag, this, that", @@ -30,7 +30,7 @@ module RecurringTodos 'show_always' => true, 'start_from' => '01/01/01', 'ends_on' => 'no_end_date' - } + }) assert_raise(Exception, "should have exception since we are using abstract builder") do builder = AbstractRecurringTodosBuilder.new(@admin, attributes, DailyRepeatPattern) @@ -46,7 +46,7 @@ module RecurringTodos } builder = RecurringTodosBuilder.new(@admin, attributes) - assert_equal "tag, this, that", builder.attributes[:tag_list] + assert_equal "tag, this, that", builder.attributes.get(:tag_list) # given attributes without tag_list attributes = { @@ -55,7 +55,7 @@ module RecurringTodos } builder = RecurringTodosBuilder.new(@admin, attributes) - assert_equal "", builder.attributes[:tag_list] + assert_equal "", builder.attributes.get(:tag_list) # given attributes with nil tag_list attributes = { @@ -65,7 +65,7 @@ module RecurringTodos } builder = RecurringTodosBuilder.new(@admin, attributes) - assert_equal "", builder.attributes[:tag_list] + assert_equal "", builder.attributes.get(:tag_list) # given attributes with empty tag_list ==> should be stripped attributes = { @@ -75,7 +75,7 @@ module RecurringTodos } builder = RecurringTodosBuilder.new(@admin, attributes) - assert_equal "", builder.attributes[:tag_list] + assert_equal "", builder.attributes.get(:tag_list) end def test_tags_should_be_saved_on_create_and_update @@ -135,18 +135,18 @@ module RecurringTodos end def test_map_removes_mapped_key - attributes = { :source => "value"} + attributes = Tracks::AttributeHandler.new(@admin, { :source => "value"}) arp = WeeklyRecurringTodosBuilder.new(@admin, attributes) attributes = arp.map(attributes, :target, :source) - assert_equal "value", attributes[:target] - assert_nil attributes[:source] + assert_equal "value", attributes.get(:target) + assert_nil attributes.get(:source) assert !attributes.key?(:source) end def test_get_selector_removes_selector_from_hash - attributes = { :selector => "weekly" } + attributes = Tracks::AttributeHandler.new(@admin, { :selector => "weekly" }) arp = WeeklyRecurringTodosBuilder.new(@admin, attributes) assert "weekly", arp.get_selector(:selector) @@ -154,7 +154,7 @@ module RecurringTodos end def test_get_selector_raises_exception_when_missing_selector - attributes = { } + attributes = Tracks::AttributeHandler.new(@admin, { }) arp = WeeklyRecurringTodosBuilder.new(@admin, attributes) assert_raise(Exception, "should raise exception when recurrence selector is missing"){ arp.get_selector(:selector) } diff --git a/test/models/recurring_todos/daily_recurring_todos_builder_test.rb b/test/models/recurring_todos/daily_recurring_todos_builder_test.rb index 8e692bac..b6463c9b 100644 --- a/test/models/recurring_todos/daily_recurring_todos_builder_test.rb +++ b/test/models/recurring_todos/daily_recurring_todos_builder_test.rb @@ -24,54 +24,54 @@ module RecurringTodos result = RecurringTodosBuilder.new(@admin, attributes).attributes - assert_nil result['bla_bla'], "bla_bla should be filtered" - assert_nil result[:bla_bla], "bla_bla should be filtered" - assert_equal false, result[:only_work_days], "daily attributes should be preserved" - assert_equal "a repeating todo", result[:description], "description should be preserved" + assert_nil result.get('bla_bla'), "bla_bla should be filtered" + assert_nil result.get(:bla_bla), "bla_bla should be filtered" + assert_equal false, result.get(:only_work_days), "daily attributes should be preserved" + assert_equal "a repeating todo", result.get(:description), "description should be preserved" end def test_valid_selector - attributes = { + attributes = Tracks::AttributeHandler.new(@admin, { 'recurring_period' => 'daily' - } + }) # should not raise %w{daily_every_x_day daily_every_work_day}.each do |selector| - attributes['daily_selector'] = selector + attributes.set('daily_selector', selector) DailyRecurringTodosBuilder.new(@admin, attributes) end # should raise - attributes = { + attributes = Tracks::AttributeHandler.new(@admin, { 'recurring_period' => 'daily', 'daily_selector' => 'wrong value' - } + }) # should raise assert_raise(Exception, "should have exception since daily_selector has wrong value"){ DailyRecurringTodosBuilder.new(@admin, attributes) } end def test_mapping_of_attributes - attributes = { + attributes = Tracks::AttributeHandler.new(@admin, { 'recurring_period' => 'daily', 'description' => 'a repeating todo', # generic 'daily_selector' => 'daily_every_x_day', # daily specific --> mapped to only_work_days=false 'daily_every_x_days' => '5' # mapped to every_other1 - } + }) pattern = DailyRecurringTodosBuilder.new(@admin, attributes) - assert_equal '5', pattern.mapped_attributes[:every_other1], "every_other1 should be set to daily_every_x_days" - assert_equal false, pattern.mapped_attributes[:only_work_days], "only_work_days should be set to false for daily_every_x_day" + assert_equal '5', pattern.mapped_attributes.get(:every_other1), "every_other1 should be set to daily_every_x_days" + assert_equal false, pattern.mapped_attributes.get(:only_work_days), "only_work_days should be set to false for daily_every_x_day" - attributes = { + attributes = Tracks::AttributeHandler.new(@admin, { 'recurring_period' => 'daily', 'description' => 'a repeating todo', # generic 'daily_selector' => 'daily_every_work_day', # daily specific --> mapped to only_work_days=true - } + }) pattern = DailyRecurringTodosBuilder.new(@admin, attributes) - assert_equal true, pattern.mapped_attributes[:only_work_days] + assert_equal true, pattern.mapped_attributes.get(:only_work_days) end end diff --git a/test/models/recurring_todos/monthly_recurring_todos_builder_test.rb b/test/models/recurring_todos/monthly_recurring_todos_builder_test.rb index acb0da99..f02299ca 100644 --- a/test/models/recurring_todos/monthly_recurring_todos_builder_test.rb +++ b/test/models/recurring_todos/monthly_recurring_todos_builder_test.rb @@ -25,34 +25,34 @@ module RecurringTodos result = RecurringTodosBuilder.new(@admin, attributes).attributes - assert_nil result['bla_bla'], "bla_bla should be filtered" - assert_nil result[:bla_bla], "bla_bla should be filtered" - assert_equal 5, result[:every_other1], "should be preserved" + assert_nil result.get('bla_bla'), "bla_bla should be filtered" + assert_nil result.get(:bla_bla), "bla_bla should be filtered" + assert_equal 5, result.get(:every_other1), "should be preserved" end def test_valid_selector - attributes = { + attributes = Tracks::AttributeHandler.new(@admin, { 'recurring_period' => 'monthly' - } + }) # should not raise %w{monthly_every_x_day monthly_every_xth_day}.each do |selector| - attributes['monthly_selector'] = selector + attributes.set('monthly_selector', selector) MonthlyRecurringTodosBuilder.new(@admin, attributes) end # should raise - attributes = { + attributes = Tracks::AttributeHandler.new(@admin, { 'recurring_period' => 'monthly', 'monthly_selector' => 'wrong value' - } + }) # should raise assert_raise(Exception, "should have exception since monthly_selector has wrong value"){ MonthlyRecurringTodosBuilder.new(@admin, attributes) } end def test_mapping_of_attributes - attributes = { + attributes = Tracks::AttributeHandler.new(@admin, { 'recurring_period' => 'monthly', 'description' => 'a repeating todo', # generic 'monthly_selector' => 'monthly_every_x_day', # monthly specific @@ -61,30 +61,30 @@ module RecurringTodos 'monthly_day_of_week' => 3, # mapped to :every_count 'monthly_every_x_month' => '10', # mapped to :every_other2 'monthly_every_x_month2' => '20' # not mapped - } + }) builder = MonthlyRecurringTodosBuilder.new(@admin, attributes) - assert_equal 0, builder.mapped_attributes[:recurrence_selector], "selector should be 0 for monthly_every_x_day" - assert_equal '5', builder.mapped_attributes[:every_other1], "every_other1 should be set to monthly_every_x_days" - assert_equal '10', builder.mapped_attributes[:every_other2], "every_other2 should be set to monthly_every_x_month when selector is monthly_every_x_day (=0)" - assert_equal '7', builder.mapped_attributes[:every_other3], "every_other3 should be set to monthly_every_xth_day" - assert_equal 3, builder.mapped_attributes[:every_count], "every_count should be set to monthly_day_of_week" + assert_equal 0, builder.mapped_attributes.get(:recurrence_selector), "selector should be 0 for monthly_every_x_day" + assert_equal '5', builder.mapped_attributes.get(:every_other1), "every_other1 should be set to monthly_every_x_days" + assert_equal '10', builder.mapped_attributes.get(:every_other2), "every_other2 should be set to monthly_every_x_month when selector is monthly_every_x_day (=0)" + assert_equal '7', builder.mapped_attributes.get(:every_other3), "every_other3 should be set to monthly_every_xth_day" + assert_equal 3, builder.mapped_attributes.get(:every_count), "every_count should be set to monthly_day_of_week" builder.build assert builder.pattern.every_x_day?, "every_x_day? should say true for selector monthly_every_x_day" - attributes = { + attributes = Tracks::AttributeHandler.new(@admin, { 'recurring_period' => 'monthly', 'description' => 'a repeating todo', # generic 'monthly_selector' => 'monthly_every_xth_day', # monthly specific 'monthly_every_x_day' => '5', # mapped to :every_other1 'monthly_every_x_month' => '10', # not mapped 'monthly_every_x_month2' => '20' # mapped to :every_other2 - } + }) builder = MonthlyRecurringTodosBuilder.new(@admin, attributes) - assert_equal 1, builder.mapped_attributes[:recurrence_selector], "selector should be 1 for monthly_every_xth_day" - assert_equal '20', builder.mapped_attributes[:every_other2], "every_other2 should be set to monthly_every_x_month2 when selector is monthly_every_xth_day (=0)" + assert_equal 1, builder.mapped_attributes.get(:recurrence_selector), "selector should be 1 for monthly_every_xth_day" + assert_equal '20', builder.mapped_attributes.get(:every_other2), "every_other2 should be set to monthly_every_x_month2 when selector is monthly_every_xth_day (=0)" builder.build assert builder.pattern.every_xth_day?, "every_xth_day? should say true for selector monthly_every_xth_day" diff --git a/test/models/recurring_todos/recurring_todos_builder_test.rb b/test/models/recurring_todos/recurring_todos_builder_test.rb index fbeff70e..e72e4633 100644 --- a/test/models/recurring_todos/recurring_todos_builder_test.rb +++ b/test/models/recurring_todos/recurring_todos_builder_test.rb @@ -35,8 +35,8 @@ module RecurringTodos 'end_date' => '05/05/05' }) - assert builder.attributes[:start_from].is_a?(ActiveSupport::TimeWithZone), "Dates should be parsed to ActiveSupport::TimeWithZone class" - assert builder.attributes[:end_date].is_a?(ActiveSupport::TimeWithZone), "Dates should be parsed to ActiveSupport::TimeWithZone class" + assert builder.attributes.get(:start_from).is_a?(ActiveSupport::TimeWithZone), "Dates should be parsed to ActiveSupport::TimeWithZone class" + assert builder.attributes.get(:end_date).is_a?(ActiveSupport::TimeWithZone), "Dates should be parsed to ActiveSupport::TimeWithZone class" end def test_exisisting_project_is_used diff --git a/test/models/recurring_todos/weekly_recurring_todos_builder_test.rb b/test/models/recurring_todos/weekly_recurring_todos_builder_test.rb index e4a715ad..963e9b37 100644 --- a/test/models/recurring_todos/weekly_recurring_todos_builder_test.rb +++ b/test/models/recurring_todos/weekly_recurring_todos_builder_test.rb @@ -24,18 +24,18 @@ module RecurringTodos result = RecurringTodosBuilder.new(@admin, attributes).attributes - assert_nil result['bla_bla'], "bla_bla should be filtered" - assert_nil result[:bla_bla], "bla_bla should be filtered" - assert_equal ' m ', result[:every_day], "weekly attributes should be preserved" - assert_equal "a repeating todo", result[:description], "description should be preserved" + assert_nil result.get('bla_bla'), "bla_bla should be filtered" + assert_nil result.get(:bla_bla), "bla_bla should be filtered" + assert_equal ' m ', result.get(:every_day), "weekly attributes should be preserved" + assert_equal "a repeating todo", result.get(:description), "description should be preserved" end def test_attributes_to_filter - attributes = { + attributes = Tracks::AttributeHandler.new(@admin, { 'recurring_period' => 'weekly', 'description' => 'a repeating todo', # generic 'weekly_return_monday' => 'm', # weekly specific - } + }) w = WeeklyRecurringTodosBuilder.new(@admin, attributes) assert_equal 9, w.attributes_to_filter.size @@ -44,41 +44,41 @@ module RecurringTodos end def test_mapping_of_attributes - attributes = { + attributes = Tracks::AttributeHandler.new(@admin, { 'recurring_period' => 'weekly', 'description' => 'a repeating todo', # generic 'weekly_every_x_week' => '5', # mapped to every_other1 'weekly_return_monday' => 'm' - } + }) pattern = WeeklyRecurringTodosBuilder.new(@admin, attributes) - assert_equal '5', pattern.mapped_attributes[:every_other1], "every_other1 should be set to weekly_every_x_week" - assert_equal ' m ', pattern.mapped_attributes[:every_day], "weekly_return_ should be mapped to :every_day in format 'smtwtfs'" + assert_equal '5', pattern.mapped_attributes.get(:every_other1), "every_other1 should be set to weekly_every_x_week" + assert_equal ' m ', pattern.mapped_attributes.get(:every_day), "weekly_return_ should be mapped to :every_day in format 'smtwtfs'" end def test_map_day - attributes = { + attributes = Tracks::AttributeHandler.new(@admin, { 'recurring_period' => 'weekly', 'description' => 'a repeating todo', # generic 'weekly_every_x_week' => '5' # mapped to every_other1 - } + }) pattern = WeeklyRecurringTodosBuilder.new(@admin, attributes) - assert_equal ' ', pattern.mapped_attributes[:every_day], "all days should be empty in :every_day" + assert_equal ' ', pattern.mapped_attributes.get(:every_day), "all days should be empty in :every_day" # add all days { sunday: 's', monday: 'm', tuesday: 't', wednesday: 'w', thursday: 't', friday: 'f', saturday: 's' }.each do |day, short| - attributes["weekly_return_#{day}"] = short + attributes.set("weekly_return_#{day}", short) end pattern = WeeklyRecurringTodosBuilder.new(@admin, attributes) - assert_equal 'smtwtfs', pattern.mapped_attributes[:every_day], "all days should be filled in :every_day" + assert_equal 'smtwtfs', pattern.mapped_attributes.get(:every_day), "all days should be filled in :every_day" # remove wednesday attributes = attributes.except('weekly_return_wednesday') pattern = WeeklyRecurringTodosBuilder.new(@admin, attributes) - assert_equal 'smt tfs', pattern.mapped_attributes[:every_day], "only wednesday should be empty in :every_day" + assert_equal 'smt tfs', pattern.mapped_attributes.get(:every_day), "only wednesday should be empty in :every_day" end diff --git a/test/models/recurring_todos/yearly_recurring_todos_builder_test.rb b/test/models/recurring_todos/yearly_recurring_todos_builder_test.rb index 6b821cc8..51db4860 100644 --- a/test/models/recurring_todos/yearly_recurring_todos_builder_test.rb +++ b/test/models/recurring_todos/yearly_recurring_todos_builder_test.rb @@ -25,28 +25,28 @@ module RecurringTodos result = RecurringTodosBuilder.new(@admin, attributes).attributes - assert_nil result['bla_bla'], "bla_bla should be filtered" - assert_nil result[:bla_bla], "bla_bla should be filtered" - assert_equal '1', result[:every_other2], "yearly attributes should be preserved" - assert_equal "a repeating todo", result[:description], "description should be preserved" + assert_nil result.get('bla_bla'), "bla_bla should be filtered" + assert_nil result.get(:bla_bla), "bla_bla should be filtered" + assert_equal '1', result.get(:every_other2), "yearly attributes should be preserved" + assert_equal "a repeating todo", result.get(:description), "description should be preserved" end def test_valid_selector - attributes = { + attributes = Tracks::AttributeHandler.new(@admin, { 'recurring_period' => 'yearly' - } + }) # should not raise %w{yearly_every_x_day yearly_every_xth_day}.each do |selector| - attributes['yearly_selector'] = selector + attributes.set(:yearly_selector, selector) YearlyRecurringTodosBuilder.new(@admin, attributes) end # should raise - attributes = { + attributes = Tracks::AttributeHandler.new(@admin, { 'recurring_period' => 'yearly', 'yearly_selector' => 'wrong value' - } + }) # should raise assert_raise(Exception, "should have exception since yearly_selector has wrong value"){ YearlyRecurringTodosBuilder.new(@admin, attributes) } @@ -64,12 +64,12 @@ module RecurringTodos 'yearly_month_of_year2' => '2' # ignored because yearly_selector is yearly_every_x_day } - pattern = YearlyRecurringTodosBuilder.new(@admin, attributes) + pattern = YearlyRecurringTodosBuilder.new(@admin, Tracks::AttributeHandler.new(@admin, attributes)) - assert_equal '5', pattern.mapped_attributes[:every_other1], "every_other1 should be set to yearly_every_x_day" - assert_equal '1', pattern.mapped_attributes[:every_other2], "every_other2 should be set to yearly_month_of_year because selector is yearly_every_x_day" - assert_equal '7', pattern.mapped_attributes[:every_other3], "every_other3 should be set to yearly_every_xth_day" - assert_equal '3', pattern.mapped_attributes[:every_count], "every_count should be set to yearly_day_of_week" + assert_equal '5', pattern.mapped_attributes.get(:every_other1), "every_other1 should be set to yearly_every_x_day" + assert_equal '1', pattern.mapped_attributes.get(:every_other2), "every_other2 should be set to yearly_month_of_year because selector is yearly_every_x_day" + assert_equal '7', pattern.mapped_attributes.get(:every_other3), "every_other3 should be set to yearly_every_xth_day" + assert_equal '3', pattern.mapped_attributes.get(:every_count), "every_count should be set to yearly_day_of_week" attributes = { 'recurring_period' => 'yearly', @@ -79,8 +79,8 @@ module RecurringTodos 'yearly_month_of_year2' => '2' # mapped to evert_other2 because yearly_selector is yearly_every_xth_day } - pattern = YearlyRecurringTodosBuilder.new(@admin, attributes) - assert_equal '2', pattern.mapped_attributes[:every_other2], "every_other2 should be set to yearly_month_of_year2 because selector is yearly_every_xth_day" + pattern = YearlyRecurringTodosBuilder.new(@admin, Tracks::AttributeHandler.new(@admin, attributes)) + assert_equal '2', pattern.mapped_attributes.get(:every_other2), "every_other2 should be set to yearly_month_of_year2 because selector is yearly_every_xth_day" end end From b23338eaa29cccb79c67c75dc8f6d8640047d2f8 Mon Sep 17 00:00:00 2001 From: Reinier Balt Date: Mon, 10 Feb 2014 11:45:25 +0100 Subject: [PATCH 13/24] fix updating of recurring todo and create a form helper for filling the recurring todo edit form --- .../recurring_todos/form_helper.rb | 50 ++++++++++ app/controllers/recurring_todos_controller.rb | 21 ++-- app/models/recurring_todo.rb | 8 -- .../abstract_recurring_todos_builder.rb | 21 +++- .../abstract_repeat_pattern.rb | 4 +- .../recurring_todos/monthly_repeat_pattern.rb | 8 ++ .../recurring_todos_builder.rb | 4 +- app/views/recurring_todos/_edit_form.html.erb | 96 +++++++++---------- lib/tracks/attribute_handler.rb | 26 ++++- 9 files changed, 166 insertions(+), 72 deletions(-) create mode 100644 app/controllers/recurring_todos/form_helper.rb diff --git a/app/controllers/recurring_todos/form_helper.rb b/app/controllers/recurring_todos/form_helper.rb new file mode 100644 index 00000000..0a81f08d --- /dev/null +++ b/app/controllers/recurring_todos/form_helper.rb @@ -0,0 +1,50 @@ +module RecurringTodos + + class FormHelper + + def initialize(recurring_todo) + @recurring_todo = recurring_todo + end + + def create_pattern(pattern_class) + pattern = pattern_class.new(@recurring_todo.user) + pattern.build_from_recurring_todo(@recurring_todo) + pattern + end + + def daily_pattern + @daily_pattern ||= create_pattern(DailyRepeatPattern) + end + + def weekly_pattern + @weekly_pattern ||= create_pattern(WeeklyRepeatPattern) + end + + def monthly_pattern + @monthly_pattern ||= create_pattern(MonthlyRepeatPattern) + end + + def yearly_pattern + @yearly_pattern ||= create_pattern(YearlyRepeatPattern) + end + + def method_missing(method, *args) + if method.to_s =~ /^daily_(.+)$/ + daily_pattern.send($1, *args) + elsif method.to_s =~ /^weekly_(.+)$/ + weekly_pattern.send($1, *args) + elsif method.to_s =~ /^monthly_(.+)$/ + monthly_pattern.send($1, *args) + elsif method.to_s =~ /^yearly_(.+)$/ + yearly_pattern.send($1, *args) + elsif method.to_s =~ /^on_(.+)$/ # on_monday, on_tuesday, etc. + weekly_pattern.send(method, *args) + else + # no match, let @recurring_todo handle it, or fail + @recurring_todo.send(method, *args) + end + end + + end + +end \ No newline at end of file diff --git a/app/controllers/recurring_todos_controller.rb b/app/controllers/recurring_todos_controller.rb index 20461e05..a15f6922 100644 --- a/app/controllers/recurring_todos_controller.rb +++ b/app/controllers/recurring_todos_controller.rb @@ -37,13 +37,15 @@ class RecurringTodosController < ApplicationController end def edit + @form_helper = RecurringTodos::FormHelper.new(@recurring_todo) + respond_to do |format| format.js end end def update - updater = RecurringTodos::RecurringTodosBuilder.new(current_user, edit_recurring_todo_params) + updater = RecurringTodos::RecurringTodosBuilder.new(current_user, update_recurring_todo_params) @saved = updater.update(@recurring_todo) @recurring_todo.reload @@ -162,23 +164,29 @@ class RecurringTodosController < ApplicationController def all_recurring_todo_params # move context_name, project_name and tag_list into :recurring_todo hash for easier processing - { context_name: :context_name, project_name: :project_name, tag_list: :tag_list}.each do |target,source| + { + context_name: :context_name, + project_name: :project_name, + tag_list: :tag_list + }.each do |target,source| move_into_recurring_todo_param(params, target, source) end recurring_todo_params end - def edit_recurring_todo_params + def update_recurring_todo_params # we needed to rename the recurring_period selector in the edit form because # the form for a new recurring todo and the edit form are on the same page. # Same goes for start_from and end_date params['recurring_todo']['recurring_period'] = params['recurring_edit_todo']['recurring_period'] - { context_name: :context_name, + { + context_name: :context_name, project_name: :project_name, tag_list: :edit_recurring_todo_tag_list, end_date: :recurring_todo_edit_end_date, - start_from: :recurring_todo_edit_start_from}.each do |target,source| + start_from: :recurring_todo_edit_start_from + }.each do |target,source| move_into_recurring_todo_param(params, target, source) end @@ -187,7 +195,8 @@ class RecurringTodosController < ApplicationController %w{monday tuesday wednesday thursday friday saturday sunday}.each do |day| params["recurring_todo"]["weekly_return_#{day}"]=' ' if params["recurring_todo"]["weekly_return_#{day}"].nil? end - params['recurring_todo'] + + recurring_todo_params end def move_into_recurring_todo_param(params, target, source) diff --git a/app/models/recurring_todo.rb b/app/models/recurring_todo.rb index 9fa34198..b731e697 100644 --- a/app/models/recurring_todo.rb +++ b/app/models/recurring_todo.rb @@ -101,14 +101,6 @@ class RecurringTodo < ActiveRecord::Base end end - def recurring_show_days_before=(days) - self.show_from_delta=days - end - - def recurring_show_always=(value) - self.show_always=value - end - def daily_recurrence_pattern if only_work_days I18n.t("todos.recurrence.pattern.on_work_days") diff --git a/app/models/recurring_todos/abstract_recurring_todos_builder.rb b/app/models/recurring_todos/abstract_recurring_todos_builder.rb index e2d610e7..fd61bcc4 100644 --- a/app/models/recurring_todos/abstract_recurring_todos_builder.rb +++ b/app/models/recurring_todos/abstract_recurring_todos_builder.rb @@ -22,15 +22,10 @@ module RecurringTodos def build @recurring_todo = @pattern.build_recurring_todo(@mapped_attributes) - @recurring_todo.context = @filterred_attributes.get(:context) - @recurring_todo.project = @filterred_attributes.get(:project) end def update(recurring_todo) @recurring_todo = @pattern.update_recurring_todo(recurring_todo, @mapped_attributes) - @recurring_todo.context = @filterred_attributes.get(:context) - @recurring_todo.project = @filterred_attributes.get(:project) - @saved = @recurring_todo.save @recurring_todo.tag_with(@filterred_attributes.get(:tag_list)) if @saved && @filterred_attributes.get(:tag_list).present? @recurring_todo.reload @@ -45,6 +40,22 @@ module RecurringTodos return @saved end + def save_collection(collection, collection_id) + # save object (project or context) and add its id to @mapped_attributes and remove the object from the attributes + object = @mapped_attributes.get(collection) + object.save + @mapped_attributes.set(collection_id, object.id) + @mapped_attributes.except(collection) + end + + def save_project + save_collection(:project, :project_id) + end + + def save_context + save_collection(:context, :context_id) + end + def saved_recurring_todo raise(Exception.new, @recurring_todo.valid? ? "Recurring todo was not saved yet" : "Recurring todos was not saved because of validation errors") unless @saved diff --git a/app/models/recurring_todos/abstract_repeat_pattern.rb b/app/models/recurring_todos/abstract_repeat_pattern.rb index cf2f6a76..93bc3fef 100644 --- a/app/models/recurring_todos/abstract_repeat_pattern.rb +++ b/app/models/recurring_todos/abstract_repeat_pattern.rb @@ -33,11 +33,11 @@ module RecurringTodos end def build_recurring_todo(attribute_handler) - @recurring_todo = @user.recurring_todos.build(attribute_handler.attributes) + @recurring_todo = @user.recurring_todos.build(attribute_handler.safe_attributes) end def update_recurring_todo(recurring_todo, attribute_handler) - recurring_todo.assign_attributes(attribute_handler.attributes) + recurring_todo.assign_attributes(attribute_handler.safe_attributes) recurring_todo end diff --git a/app/models/recurring_todos/monthly_repeat_pattern.rb b/app/models/recurring_todos/monthly_repeat_pattern.rb index 3561ba60..aa901b4a 100644 --- a/app/models/recurring_todos/monthly_repeat_pattern.rb +++ b/app/models/recurring_todos/monthly_repeat_pattern.rb @@ -14,10 +14,18 @@ module RecurringTodos get(:recurrence_selector) == 0 end + def every_x_day + get(:every_other1) + end + def every_xth_day? get(:recurrence_selector) == 1 end + def every_xth_day + get :every_other2 + end + def every_x_month # in case monthly pattern is every day x, return every_other2 otherwise # return a default value diff --git a/app/models/recurring_todos/recurring_todos_builder.rb b/app/models/recurring_todos/recurring_todos_builder.rb index 569baa3d..dd26e637 100644 --- a/app/models/recurring_todos/recurring_todos_builder.rb +++ b/app/models/recurring_todos/recurring_todos_builder.rb @@ -32,8 +32,8 @@ module RecurringTodos end def save - @project.save if @new_project_created - @context.save if @new_context_created + @builder.save_project if @new_project_created + @builder.save_context if @new_context_created return @builder.save end diff --git a/app/views/recurring_todos/_edit_form.html.erb b/app/views/recurring_todos/_edit_form.html.erb index dfb94095..21c4ab24 100644 --- a/app/views/recurring_todos/_edit_form.html.erb +++ b/app/views/recurring_todos/_edit_form.html.erb @@ -5,91 +5,91 @@
<%= - text_field_tag( "recurring_todo[description]", @recurring_todo.description, "size" => 30, "maxlength" => 100, :id => "edit_recurring_todo_description") -%> + text_field_tag( "recurring_todo[description]", @form_helper.description, "size" => 30, "maxlength" => 100, :id => "edit_recurring_todo_description") -%> <%= - text_area_tag( "recurring_todo[notes]", @recurring_todo.notes, {:cols => 29, :rows => 6}) -%> + text_area_tag( "recurring_todo[notes]", @form_helper.notes, {:cols => 29, :rows => 6}) -%> - " /> + " /> - + - <%= text_field_tag "edit_recurring_todo_tag_list", @recurring_todo.tag_list, :size => 30 -%> + <%= text_field_tag "edit_recurring_todo_tag_list", @form_helper.tag_list, :size => 30 -%>

- <%= radio_button_tag('recurring_edit_todo[recurring_period]', 'daily', @recurring_todo.recurring_period == 'daily')%> <%= t('todos.recurrence.daily') %>
- <%= radio_button_tag('recurring_edit_todo[recurring_period]', 'weekly', @recurring_todo.recurring_period == 'weekly')%> <%= t('todos.recurrence.weekly') %>
- <%= radio_button_tag('recurring_edit_todo[recurring_period]', 'monthly', @recurring_todo.recurring_period == 'monthly')%> <%= t('todos.recurrence.monthly') %>
- <%= radio_button_tag('recurring_edit_todo[recurring_period]', 'yearly', @recurring_todo.recurring_period == 'yearly')%> <%= t('todos.recurrence.yearly') %>
+ <%= radio_button_tag('recurring_edit_todo[recurring_period]', 'daily', @form_helper.recurring_period == 'daily')%> <%= t('todos.recurrence.daily') %>
+ <%= radio_button_tag('recurring_edit_todo[recurring_period]', 'weekly', @form_helper.recurring_period == 'weekly')%> <%= t('todos.recurrence.weekly') %>
+ <%= radio_button_tag('recurring_edit_todo[recurring_period]', 'monthly', @form_helper.recurring_period == 'monthly')%> <%= t('todos.recurrence.monthly') %>
+ <%= radio_button_tag('recurring_edit_todo[recurring_period]', 'yearly', @form_helper.recurring_period == 'yearly')%> <%= t('todos.recurrence.yearly') %>

<%= - text_field_tag("recurring_todo_edit_start_from", format_date(@recurring_todo.start_from), "size" => 12, "class" => "Date", "autocomplete" => "off") %>
+ text_field_tag("recurring_todo_edit_start_from", format_date(@form_helper.start_from), "size" => 12, "class" => "Date", "autocomplete" => "off") %>


- <%= radio_button_tag('recurring_todo[ends_on]', 'no_end_date', @recurring_todo.ends_on == 'no_end_date')%> <%= t('todos.recurrence.no_end_date') %>
- <%= radio_button_tag('recurring_todo[ends_on]', 'ends_on_number_of_times', @recurring_todo.ends_on == 'ends_on_number_of_times')%> + <%= radio_button_tag('recurring_todo[ends_on]', 'no_end_date', @form_helper.ends_on == 'no_end_date')%> <%= t('todos.recurrence.no_end_date') %>
+ <%= radio_button_tag('recurring_todo[ends_on]', 'ends_on_number_of_times', @form_helper.ends_on == 'ends_on_number_of_times')%> <%= raw t('todos.recurrence.ends_on_number_times', :number => text_field( :recurring_todo, :number_of_occurences, "size" => 3)) %>
- <%= radio_button_tag('recurring_todo[ends_on]', 'ends_on_end_date', @recurring_todo.ends_on == 'ends_on_end_date')%> - <%= raw t('todos.recurrence.ends_on_date', :date => text_field_tag('recurring_todo_edit_end_date', format_date(@recurring_todo.end_date), "size" => 12, "class" => "Date", "autocomplete" => "off")) %>
+ <%= radio_button_tag('recurring_todo[ends_on]', 'ends_on_end_date', @form_helper.ends_on == 'ends_on_end_date')%> + <%= raw t('todos.recurrence.ends_on_date', :date => text_field_tag('recurring_todo_edit_end_date', format_date(@form_helper.end_date), "size" => 12, "class" => "Date", "autocomplete" => "off")) %>
-
+

- <%= radio_button_tag('recurring_todo[daily_selector]', 'daily_every_x_day', !@recurring_todo.only_work_days)%> - <%= raw t('todos.recurrence.daily_every_number_day', :number=> text_field_tag( 'recurring_todo[daily_every_x_days]', @recurring_todo.daily_every_x_days, {"size" => 3})) %>
- <%= radio_button_tag('recurring_todo[daily_selector]', 'daily_every_work_day', @recurring_todo.only_work_days)%> <%= t('todos.recurrence.every_work_day') %>
+ <%= radio_button_tag('recurring_todo[daily_selector]', 'daily_every_x_day', !@form_helper.only_work_days)%> + <%= raw t('todos.recurrence.daily_every_number_day', :number=> text_field_tag( 'recurring_todo[daily_every_x_days]', @form_helper.daily_every_x_days, {"size" => 3})) %>
+ <%= radio_button_tag('recurring_todo[daily_selector]', 'daily_every_work_day', @form_helper.only_work_days)%> <%= t('todos.recurrence.every_work_day') %>
-
+

- <%= raw t('todos.recurrence.weekly_every_number_week', :number => text_field_tag('recurring_todo[weekly_every_x_week]', @recurring_todo.weekly_every_x_week, {"size" => 3})) %>
- <%= check_box_tag('recurring_todo[weekly_return_monday]', 'm', @recurring_todo.on_monday ) %> <%= t('date.day_names')[1] %> - <%= check_box_tag('recurring_todo[weekly_return_tuesday]', 't', @recurring_todo.on_tuesday) %> <%= t('date.day_names')[2] %> - <%= check_box_tag('recurring_todo[weekly_return_wednesday]', 'w', @recurring_todo.on_wednesday) %> <%= t('date.day_names')[3] %> - <%= check_box_tag('recurring_todo[weekly_return_thursday]', 't', @recurring_todo.on_thursday) %> <%= t('date.day_names')[4] %>
- <%= check_box_tag('recurring_todo[weekly_return_friday]', 'f', @recurring_todo.on_friday) %> <%= t('date.day_names')[5] %> - <%= check_box_tag('recurring_todo[weekly_return_saturday]', 's', @recurring_todo.on_saturday) %> <%= t('date.day_names')[6] %> - <%= check_box_tag('recurring_todo[weekly_return_sunday]', 's', @recurring_todo.on_sunday) %> <%= t('date.day_names')[0] %>
+ <%= raw t('todos.recurrence.weekly_every_number_week', :number => text_field_tag('recurring_todo[weekly_every_x_week]', @form_helper.weekly_every_x_week, {"size" => 3})) %>
+ <%= check_box_tag('recurring_todo[weekly_return_monday]', 'm', @form_helper.on_monday ) %> <%= t('date.day_names')[1] %> + <%= check_box_tag('recurring_todo[weekly_return_tuesday]', 't', @form_helper.on_tuesday) %> <%= t('date.day_names')[2] %> + <%= check_box_tag('recurring_todo[weekly_return_wednesday]', 'w', @form_helper.on_wednesday) %> <%= t('date.day_names')[3] %> + <%= check_box_tag('recurring_todo[weekly_return_thursday]', 't', @form_helper.on_thursday) %> <%= t('date.day_names')[4] %>
+ <%= check_box_tag('recurring_todo[weekly_return_friday]', 'f', @form_helper.on_friday) %> <%= t('date.day_names')[5] %> + <%= check_box_tag('recurring_todo[weekly_return_saturday]', 's', @form_helper.on_saturday) %> <%= t('date.day_names')[6] %> + <%= check_box_tag('recurring_todo[weekly_return_sunday]', 's', @form_helper.on_sunday) %> <%= t('date.day_names')[0] %>
-
+

- <%= radio_button_tag('recurring_todo[monthly_selector]', 'monthly_every_x_day', @recurring_todo.is_monthly_every_x_day || @recurring_todo.recurring_period == 'weekly')%> + <%= radio_button_tag('recurring_todo[monthly_selector]', 'monthly_every_x_day', @form_helper.monthly_every_x_day? || @form_helper.recurring_period == 'weekly')%> <%= raw t('todos.recurrence.day_x_on_every_x_month', - :day => text_field_tag('recurring_todo[monthly_every_x_day]', @recurring_todo.monthly_every_x_day, {"size" => 3}), - :month => text_field_tag('recurring_todo[monthly_every_x_month]', @recurring_todo.monthly_every_x_month, {"size" => 3})) %>
- <%= radio_button_tag('recurring_todo[monthly_selector]', 'monthly_every_xth_day', @recurring_todo.is_monthly_every_xth_day)%> + :day => text_field_tag('recurring_todo[monthly_every_x_day]', @form_helper.monthly_every_x_day, {"size" => 3}), + :month => text_field_tag('recurring_todo[monthly_every_x_month]', @form_helper.monthly_every_x_month, {"size" => 3})) %>
+ <%= radio_button_tag('recurring_todo[monthly_selector]', 'monthly_every_xth_day', @form_helper.monthly_every_xth_day?)%> <%= raw t('todos.recurrence.monthly_every_xth_day', - :day => select_tag('recurring_todo[monthly_every_xth_day]', options_for_select(@xth_day, @xth_day[@recurring_todo.monthly_every_xth_day(1)-1][1])), - :day_of_week => select_tag('recurring_todo[monthly_day_of_week]' , options_for_select(@days_of_week, @recurring_todo.monthly_day_of_week)), - :month => text_field_tag('recurring_todo[monthly_every_x_month2]', @recurring_todo.monthly_every_x_month2, {"size" => 3})) %>
+ :day => select_tag('recurring_todo[monthly_every_xth_day]', options_for_select(@xth_day, @xth_day[@form_helper.monthly_every_xth_day(1)-1][1])), + :day_of_week => select_tag('recurring_todo[monthly_day_of_week]' , options_for_select(@days_of_week, @form_helper.monthly_day_of_week)), + :month => text_field_tag('recurring_todo[monthly_every_x_month2]', @form_helper.monthly_every_x_month2, {"size" => 3})) %>
-
+

- <%= radio_button_tag('recurring_todo[yearly_selector]', 'yearly_every_x_day', @recurring_todo.recurrence_selector == 0)%> + <%= radio_button_tag('recurring_todo[yearly_selector]', 'yearly_every_x_day', @form_helper.recurrence_selector == 0)%> <%= raw t('todos.recurrence.yearly_every_x_day', - :month => select_tag('recurring_todo[yearly_month_of_year]', options_for_select(@months_of_year, @recurring_todo.yearly_month_of_year)), - :day => text_field_tag('recurring_todo[yearly_every_x_day]', @recurring_todo.yearly_every_x_day, "size" => 3)) %>
- <%= radio_button_tag('recurring_todo[yearly_selector]', 'yearly_every_xth_day', @recurring_todo.recurrence_selector == 1)%> + :month => select_tag('recurring_todo[yearly_month_of_year]', options_for_select(@months_of_year, @form_helper.yearly_month_of_year)), + :day => text_field_tag('recurring_todo[yearly_every_x_day]', @form_helper.yearly_every_x_day, "size" => 3)) %>
+ <%= radio_button_tag('recurring_todo[yearly_selector]', 'yearly_every_xth_day', @form_helper.recurrence_selector == 1)%> <%= raw t('todos.recurrence.yearly_every_xth_day', - :day => select_tag('recurring_todo[yearly_every_xth_day]', options_for_select(@xth_day, @recurring_todo.yearly_every_xth_day)), - :day_of_week => select_tag('recurring_todo[yearly_day_of_week]', options_for_select(@days_of_week, @recurring_todo.yearly_day_of_week)), - :month => select_tag('recurring_todo[yearly_month_of_year2]', options_for_select(@months_of_year, @recurring_todo.yearly_month_of_year2))) %>
+ :day => select_tag('recurring_todo[yearly_every_xth_day]', options_for_select(@xth_day, @form_helper.yearly_every_xth_day)), + :day_of_week => select_tag('recurring_todo[yearly_day_of_week]', options_for_select(@days_of_week, @form_helper.yearly_day_of_week)), + :month => select_tag('recurring_todo[yearly_month_of_year2]', options_for_select(@months_of_year, @form_helper.yearly_month_of_year2))) %>

- <%= radio_button_tag('recurring_todo[recurring_target]', 'due_date', @recurring_todo.target == 'due_date')%> <%= t('todos.recurrence.recurrence_on.due_date') %>. <%= t('todos.recurrence.recurrence_on.show_options') %>: - <%= radio_button_tag('recurring_todo[recurring_show_always]', '1', @recurring_todo.show_always?)%> <%= t('todos.recurrence.recurrence_on.show_always') %> - <%= radio_button_tag('recurring_todo[recurring_show_always]', '0', !@recurring_todo.show_always?)%> - <%= raw t('todos.recurrence.recurrence_on.show_days_before', :days => text_field_tag( 'recurring_todo[recurring_show_days_before]', @recurring_todo.show_from_delta, {"size" => 3})) %>
- <%= radio_button_tag('recurring_todo[recurring_target]', 'show_from_date', @recurring_todo.target == 'show_from_date')%> <%= t('todos.recurrence.recurrence_on.from_tickler') %>
+ <%= radio_button_tag('recurring_todo[recurring_target]', 'due_date', @form_helper.target == 'due_date')%> <%= t('todos.recurrence.recurrence_on.due_date') %>. <%= t('todos.recurrence.recurrence_on.show_options') %>: + <%= radio_button_tag('recurring_todo[recurring_show_always]', '1', @form_helper.show_always?)%> <%= t('todos.recurrence.recurrence_on.show_always') %> + <%= radio_button_tag('recurring_todo[recurring_show_always]', '0', !@form_helper.show_always?)%> + <%= raw t('todos.recurrence.recurrence_on.show_days_before', :days => text_field_tag( 'recurring_todo[recurring_show_days_before]', @form_helper.show_from_delta, {"size" => 3})) %>
+ <%= radio_button_tag('recurring_todo[recurring_target]', 'show_from_date', @form_helper.target == 'show_from_date')%> <%= t('todos.recurrence.recurrence_on.from_tickler') %>

<% end %> diff --git a/lib/tracks/attribute_handler.rb b/lib/tracks/attribute_handler.rb index c9901c93..c944b001 100644 --- a/lib/tracks/attribute_handler.rb +++ b/lib/tracks/attribute_handler.rb @@ -42,8 +42,9 @@ module Tracks new_object_created = false if specified_by_name?(object_type) - # find or create context or project by given name object, new_object_created = find_or_create_by_name(relation, name) + # put id of object in @attributes, i.e. set :project_id to project.id + @attributes[object_type.to_s + "_id"] = object.id unless new_object_created else # find context or project by its id object = attribute_with_id_of(object_type).present? ? relation.find(attribute_with_id_of(object_type)) : nil @@ -99,6 +100,29 @@ module Tracks Hash[attributes.map{|k,v| [k.to_sym,v]}] end + def safe_attributes + ActionController::Parameters.new(attributes).permit( + :context, :project, + # model attributes + :context_id, :project_id, :description, :notes, :state, :start_from, + :ends_on, :end_date, :number_of_occurences, :occurences_count, :target, + :show_from_delta, :recurring_period, :recurrence_selector, :every_other1, + :every_other2, :every_other3, :every_day, :only_work_days, :every_count, + :weekday, :show_always, :context_name, :project_name, :tag_list, + # form attributes + :recurring_period, :daily_selector, :monthly_selector, :yearly_selector, + :recurring_target, :daily_every_x_days, :monthly_day_of_week, + :monthly_every_x_day, :monthly_every_x_month2, :monthly_every_x_month, + :monthly_every_xth_day, :recurring_show_days_before, + :recurring_show_always, :weekly_every_x_week, :weekly_return_monday, + :yearly_day_of_week, :yearly_every_x_day, :yearly_every_xth_day, + :yearly_month_of_year2, :yearly_month_of_year, + # derived attributes + :weekly_return_monday, :weekly_return_tuesday, :weekly_return_wednesday, + :weekly_return_thursday, :weekly_return_friday, :weekly_return_saturday, :weekly_return_sunday + ) + end + end end \ No newline at end of file From 5de96d7eda96112963b75f90608301038dac4b2a Mon Sep 17 00:00:00 2001 From: Reinier Balt Date: Mon, 10 Feb 2014 19:39:39 +0100 Subject: [PATCH 14/24] add some more tests --- .../abstract_recurring_todos_builder.rb | 79 ++++++++------- .../abstract_repeat_pattern.rb | 8 +- .../recurring_todos_builder.rb | 21 ++-- lib/tracks/attribute_handler.rb | 18 +++- test/models/attribute_handler_test.rb | 97 +++++++++++++++++++ .../abstract_recurring_todos_builder_test.rb | 26 ++--- .../abstract_repeat_pattern_test.rb | 87 +++++++++++++++++ .../recurring_todos_builder_test.rb | 10 +- 8 files changed, 281 insertions(+), 65 deletions(-) create mode 100644 test/models/attribute_handler_test.rb diff --git a/app/models/recurring_todos/abstract_recurring_todos_builder.rb b/app/models/recurring_todos/abstract_recurring_todos_builder.rb index fd61bcc4..d3a4ecaa 100644 --- a/app/models/recurring_todos/abstract_recurring_todos_builder.rb +++ b/app/models/recurring_todos/abstract_recurring_todos_builder.rb @@ -21,31 +21,16 @@ module RecurringTodos # build does not add tags. For tags, the recurring todos needs to be saved def build @recurring_todo = @pattern.build_recurring_todo(@mapped_attributes) - end def update(recurring_todo) @recurring_todo = @pattern.update_recurring_todo(recurring_todo, @mapped_attributes) - @saved = @recurring_todo.save - @recurring_todo.tag_with(@filterred_attributes.get(:tag_list)) if @saved && @filterred_attributes.get(:tag_list).present? - @recurring_todo.reload - - return @saved + save_recurring_todo end def save build - @saved = @recurring_todo.save - @recurring_todo.tag_with(@filterred_attributes.get(:tag_list)) if @saved && @filterred_attributes.get(:tag_list).present? - return @saved - end - - def save_collection(collection, collection_id) - # save object (project or context) and add its id to @mapped_attributes and remove the object from the attributes - object = @mapped_attributes.get(collection) - object.save - @mapped_attributes.set(collection_id, object.id) - @mapped_attributes.except(collection) + save_recurring_todo end def save_project @@ -71,29 +56,31 @@ module RecurringTodos end def filter_attributes(attributes) + # get pattern independend attributes filterred_attributes = filter_generic_attributes(attributes) - attributes_to_filter.each{|key| filterred_attributes.set(key, attributes.get(key)) if attributes.key?(key)} + # append pattern specific attributes + attributes_to_filter.each{|key| filterred_attributes[key]= attributes[key] if attributes.key?(key)} + filterred_attributes end def filter_generic_attributes(attributes) return Tracks::AttributeHandler.new(@user, { - recurring_period: attributes.get(:recurring_period), - description: attributes.get(:description), - notes: attributes.get(:notes), + recurring_period: attributes[:recurring_period], + description: attributes[:description], + notes: attributes[:notes], tag_list: tag_list_or_empty_string(attributes), - start_from: attributes.get(:start_from), - end_date: attributes.get(:end_date), - ends_on: attributes.get(:ends_on), - show_always: attributes.get(:show_always), - target: attributes.get(:target), - project: attributes.get(:project), - context: attributes.get(:context), - project_id: attributes.get(:project_id), - context_id: attributes.get(:context_id), - target: attributes.get(:recurring_target), - show_from_delta: attributes.get(:recurring_show_days_before), - show_always: attributes.get(:recurring_show_always) + start_from: attributes[:start_from], + end_date: attributes[:end_date], + ends_on: attributes[:ends_on], + target: attributes[:target], + project: attributes[:project], + context: attributes[:context], + project_id: attributes[:project_id], + context_id: attributes[:context_id], + target: attributes[:recurring_target], + show_from_delta: attributes[:recurring_show_days_before], + show_always: attributes[:recurring_show_always] }) end @@ -103,8 +90,9 @@ module RecurringTodos end # helper method to be used in mapped_attributes in subclasses + # changes name of key from source_key to key def map(mapping, key, source_key) - mapping.set(key, mapping.get(source_key)) + mapping[key] = mapping[source_key] mapping.except(source_key) end @@ -117,7 +105,7 @@ module RecurringTodos return nil if key.nil? raise Exception.new, "recurrence selector pattern (#{key}) not given" unless @attributes.selector_key_present?(key) - selector = @attributes.get(key) + selector = @attributes[key] raise Exception.new, "unknown recurrence selector pattern: '#{selector}'" unless valid_selector?(selector) @@ -131,9 +119,28 @@ module RecurringTodos private + def save_recurring_todo + @saved = @recurring_todo.save + save_tags if @saved + return @saved + end + + def save_tags + @recurring_todo.tag_with(@filterred_attributes[:tag_list]) if @filterred_attributes[:tag_list].present? + @recurring_todo.reload + end + + def save_collection(collection, collection_id) + # save object (project or context) and add its id to @mapped_attributes and remove the object from the attributes + object = @mapped_attributes[collection] + object.save + @mapped_attributes[collection_id] = object.id + @mapped_attributes.except(collection) + end + def tag_list_or_empty_string(attributes) # avoid nil - attributes.get(:tag_list).blank? ? "" : attributes.get(:tag_list).strip + attributes[:tag_list].blank? ? "" : attributes[:tag_list].strip end end diff --git a/app/models/recurring_todos/abstract_repeat_pattern.rb b/app/models/recurring_todos/abstract_repeat_pattern.rb index 93bc3fef..0c7ad29a 100644 --- a/app/models/recurring_todos/abstract_repeat_pattern.rb +++ b/app/models/recurring_todos/abstract_repeat_pattern.rb @@ -46,6 +46,10 @@ module RecurringTodos @attributes = Tracks::AttributeHandler.new(@user, recurring_todo.attributes) end + def valid? + @recurring_todo.valid? + end + def validate_not_blank(object, msg) errors[:base] << msg if object.blank? end @@ -80,7 +84,7 @@ module RecurringTodos validate_not_nil(show_always, "Please select when to show the action") validate_not_blank(show_from_delta, "Please fill in the number of days to show the todo before the due date") unless show_always else - raise Exception.new, "unexpected value of recurrence target selector '#{target}'" + errors[:base] << "Unexpected value of recurrence target selector '#{target}'" end end @@ -89,7 +93,7 @@ module RecurringTodos end def get(attribute) - @attributes.get attribute + @attributes[attribute] end end diff --git a/app/models/recurring_todos/recurring_todos_builder.rb b/app/models/recurring_todos/recurring_todos_builder.rb index dd26e637..ed8dd9cf 100644 --- a/app/models/recurring_todos/recurring_todos_builder.rb +++ b/app/models/recurring_todos/recurring_todos_builder.rb @@ -12,15 +12,12 @@ module RecurringTodos parse_project parse_context - @builder = create_builder(@attributes.get(:recurring_period)) + @builder = create_builder(@attributes[:recurring_period]) end def create_builder(selector) - if %w{daily weekly monthly yearly}.include?(selector) - return eval("RecurringTodos::#{selector.capitalize}RecurringTodosBuilder.new(@user, @attributes)") - else - raise Exception.new("Unknown recurrence selector in :recurring_period (#{selector})") - end + raise "Unknown recurrence selector in :recurring_period (#{selector})" unless valid_selector? selector + eval("RecurringTodos::#{selector.capitalize}RecurringTodosBuilder.new(@user, @attributes)") end def build @@ -50,18 +47,26 @@ module RecurringTodos @builder.attributes end + def pattern + @builder.pattern + end + private + def valid_selector?(selector) + %w{daily weekly monthly yearly}.include?(selector) + end + def parse_dates %w{end_date start_from}.each {|date| @attributes.parse_date date } end def parse_project - @project, @new_project_created = @attributes.parse_collection(:project, @user.projects, @attributes.project_name) + @project, @new_project_created = @attributes.parse_collection(:project, @user.projects) end def parse_context - @context, @new_context_created = @attributes.parse_collection(:context, @user.contexts, @attributes.context_name) + @context, @new_context_created = @attributes.parse_collection(:context, @user.contexts) end end diff --git a/lib/tracks/attribute_handler.rb b/lib/tracks/attribute_handler.rb index c944b001..add42787 100644 --- a/lib/tracks/attribute_handler.rb +++ b/lib/tracks/attribute_handler.rb @@ -13,6 +13,10 @@ module Tracks @attributes[attribute.to_sym] end + def [](attribute) + get attribute + end + def set(key, value) @attributes[key.to_sym] = value end @@ -21,6 +25,10 @@ module Tracks @attributes[key.to_sym] ||= value end + def []=(attribute, value) + set attribute, value + end + def except(key) AttributeHandler.new(@user, @attributes.except(key.to_sym)) end @@ -30,19 +38,19 @@ module Tracks end def selector_key_present?(key) - @attributes.key?(key.to_sym) + key?(key) end def parse_date(date) set(date, @user.prefs.parse_date(get(date))) end - def parse_collection(object_type, relation, name) + def parse_collection(object_type, relation) object = nil new_object_created = false if specified_by_name?(object_type) - object, new_object_created = find_or_create_by_name(relation, name) + object, new_object_created = find_or_create_by_name(relation, object_name(object_type)) # put id of object in @attributes, i.e. set :project_id to project.id @attributes[object_type.to_s + "_id"] = object.id unless new_object_created else @@ -53,6 +61,10 @@ module Tracks return object, new_object_created end + def object_name(object_type) + send("#{object_type}_name") + end + def attribute_with_id_of(object_type) map = { project: 'project_id', context: 'context_id' } get map[object_type] diff --git a/test/models/attribute_handler_test.rb b/test/models/attribute_handler_test.rb new file mode 100644 index 00000000..54d6bddd --- /dev/null +++ b/test/models/attribute_handler_test.rb @@ -0,0 +1,97 @@ +require_relative '../test_helper' + +class AttributeHandlerTest < ActiveSupport::TestCase + fixtures :users + + def test_setting_attributes + h = Tracks::AttributeHandler.new(nil, {}) + + h.set('test', '123') + h['other']='one' + assert_equal '123', h.attributes[:test], ":test should be added" + assert_nil h.attributes['test'], "string should be converted to symbol" + assert_equal 'one', h[:other], ":other should be added as symbol using []=" + + assert_nil h.attributes[:new] + h.set_if_nil(:new, 'value') + assert_equal 'value', h.attributes[:new], "value should be set for new key" + h.set_if_nil(:new, 'other') + assert_equal 'value', h.attributes[:new], "value should not be set for existing key" + + h.attributes[:empty] = nil + h.set_if_nil(:empty, "test") + assert_equal "test", h.attributes[:empty], "nil value should be overwritten" + end + + def test_getting_attributes + h = Tracks::AttributeHandler.new(nil, { :get => "me"} ) + assert h.key?(:get), "attributehandler should have key :get" + assert h.key?('get'), "attributehandler should have key :get" + assert_equal "me", h.attributes[:get], "attributehandler should have key :get" + assert_equal "me", h.get('get'), "key should be converted to symbol" + assert_equal "me", h[:get], "AttributeHandler should act like hash" + end + + def test_removing_attributes + h = Tracks::AttributeHandler.new(nil, { :i_am => "here"} ) + assert h.key?(:i_am) + + h.except(:i_am) + assert h.key?(:i_am), "AttributeHandler should be immutable" + + h2 = h.except("i_am") + assert !h2.key?(:i_am), "key as symbol should be removed" + end + + def test_project_specified_by_name + h = Tracks::AttributeHandler.new(nil, { } ) + + assert !h.project_specified_by_name?, "project is not specified by id or by name" + + h[:project_id]=4 + assert !h.project_specified_by_name?, "project is specified by id, not by name" + + h = h.except(:project_id) + h[:project_name] = "A project" + assert h.project_specified_by_name?, "project is specified by name" + + h[:project_name] = "None" + assert !h.project_specified_by_name?, "None is special token to specify nil-project" + end + + def test_context_specified_by_name + h = Tracks::AttributeHandler.new(nil, { } ) + assert !h.context_specified_by_name?, "context is not specified by id or by name" + + h["context_id"] = 4 + assert !h.context_specified_by_name?, "context is specified by id, not by name" + + h = h.except(:context_id) + h[:context_name] = "A context" + assert h.context_specified_by_name?, "context is specified by name" + end + + def test_parse_collection + admin = users(:admin_user) + project = admin.projects.first + h = Tracks::AttributeHandler.new(admin, { "project_id" => project.id } ) + + parsed_project, new_project_created = h.parse_collection(:project, admin.projects) + assert !new_project_created, "should find existing project" + assert_equal project.id, parsed_project.id, "it should find the project" + + h = Tracks::AttributeHandler.new(admin, { "project_name" => project.name } ) + + parsed_project, new_project_created = h.parse_collection(:project, admin.projects) + assert !new_project_created, "should find existing project" + assert_equal project.id, parsed_project.id, "it should find the project" + + h = Tracks::AttributeHandler.new(admin, { "project_name" => "new project" } ) + + parsed_project, new_project_created = h.parse_collection(:project, admin.projects) + assert new_project_created, "should detect that no project exist with that name" + assert_equal "new project", parsed_project.name, "it should return a new project" + assert !parsed_project.persisted?, "new project should not be persisted (yet)" + end + +end \ No newline at end of file diff --git a/test/models/recurring_todos/abstract_recurring_todos_builder_test.rb b/test/models/recurring_todos/abstract_recurring_todos_builder_test.rb index c2ac5575..5c051eb5 100644 --- a/test/models/recurring_todos/abstract_recurring_todos_builder_test.rb +++ b/test/models/recurring_todos/abstract_recurring_todos_builder_test.rb @@ -46,7 +46,7 @@ module RecurringTodos } builder = RecurringTodosBuilder.new(@admin, attributes) - assert_equal "tag, this, that", builder.attributes.get(:tag_list) + assert_equal "tag, this, that", builder.attributes[:tag_list] # given attributes without tag_list attributes = { @@ -55,7 +55,7 @@ module RecurringTodos } builder = RecurringTodosBuilder.new(@admin, attributes) - assert_equal "", builder.attributes.get(:tag_list) + assert_equal "", builder.attributes[:tag_list] # given attributes with nil tag_list attributes = { @@ -65,7 +65,7 @@ module RecurringTodos } builder = RecurringTodosBuilder.new(@admin, attributes) - assert_equal "", builder.attributes.get(:tag_list) + assert_equal "", builder.attributes[:tag_list] # given attributes with empty tag_list ==> should be stripped attributes = { @@ -75,7 +75,7 @@ module RecurringTodos } builder = RecurringTodosBuilder.new(@admin, attributes) - assert_equal "", builder.attributes.get(:tag_list) + assert_equal "", builder.attributes[:tag_list] end def test_tags_should_be_saved_on_create_and_update @@ -137,27 +137,27 @@ module RecurringTodos def test_map_removes_mapped_key attributes = Tracks::AttributeHandler.new(@admin, { :source => "value"}) - arp = WeeklyRecurringTodosBuilder.new(@admin, attributes) - attributes = arp.map(attributes, :target, :source) + a_builder = WeeklyRecurringTodosBuilder.new(@admin, attributes) + attributes = a_builder.map(attributes, :target, :source) - assert_equal "value", attributes.get(:target) - assert_nil attributes.get(:source) + assert_equal "value", attributes[:target] + assert_nil attributes[:source] assert !attributes.key?(:source) end def test_get_selector_removes_selector_from_hash attributes = Tracks::AttributeHandler.new(@admin, { :selector => "weekly" }) - arp = WeeklyRecurringTodosBuilder.new(@admin, attributes) + a_builder = WeeklyRecurringTodosBuilder.new(@admin, attributes) - assert "weekly", arp.get_selector(:selector) - assert !arp.attributes.key?(:selector) + assert "weekly", a_builder.get_selector(:selector) + assert !a_builder.attributes.key?(:selector) end def test_get_selector_raises_exception_when_missing_selector attributes = Tracks::AttributeHandler.new(@admin, { }) - arp = WeeklyRecurringTodosBuilder.new(@admin, attributes) + a_builder = WeeklyRecurringTodosBuilder.new(@admin, attributes) - assert_raise(Exception, "should raise exception when recurrence selector is missing"){ arp.get_selector(:selector) } + assert_raise(Exception, "should raise exception when recurrence selector is missing"){ a_builder.get_selector(:selector) } end end diff --git a/test/models/recurring_todos/abstract_repeat_pattern_test.rb b/test/models/recurring_todos/abstract_repeat_pattern_test.rb index feee55d4..ce3792c8 100644 --- a/test/models/recurring_todos/abstract_repeat_pattern_test.rb +++ b/test/models/recurring_todos/abstract_repeat_pattern_test.rb @@ -9,6 +9,93 @@ module RecurringTodos @admin = users(:admin_user) end + def test_pattern_builds_from_existing_recurring_todo + rt = @admin.recurring_todos.first + + pattern = rt.pattern + assert pattern.is_a?(DailyRepeatPattern), "recurring todo should have daily pattern" + end + + def test_validation_on_due_date + attributes = { + 'recurring_period' => 'weekly', + 'recurring_target' => 'due_date', + 'description' => 'a repeating todo', # generic + 'weekly_return_monday' => 'm', # weekly specific + 'ends_on' => 'ends_on_end_date', + 'end_date' => Time.zone.now + 1.week, + 'context_id' => @admin.contexts.first.id, + 'start_from' => Time.zone.now - 1.week, + 'weekly_every_x_week' => 1, + } + + pattern = create_pattern(attributes) + assert !pattern.valid?, "should fail because show_always and show_from_delta are not there" + + attributes['recurring_show_always'] = false + pattern = create_pattern(attributes) + assert !pattern.valid?, "should fail because show_from_delta is not there" + + attributes[:recurring_show_days_before] = 5 + pattern = create_pattern(attributes) + assert pattern.valid?, "should be valid:" + pattern.errors.full_messages.to_s + end + + def test_validation_on_start_date + attributes = { + 'recurring_period' => 'weekly', + 'recurring_target' => 'due_date', + 'description' => 'a repeating todo', # generic + 'weekly_return_monday' => 'm', # weekly specific + 'ends_on' => 'ends_on_end_date', + 'context_id' => @admin.contexts.first.id, + 'end_date' => Time.zone.now + 1.week, + 'weekly_every_x_week' => 1, + 'recurring_show_always' => false, + 'recurring_show_days_before' => 5, + } + pattern = create_pattern(attributes) + assert !pattern.valid?, "should be not valid because start_from is empty" + + attributes['start_from'] = Time.zone.now - 1.week + pattern = create_pattern(attributes) + assert pattern.valid?, "should be valid: " + pattern.errors.full_messages.to_s + end + + def test_validation_on_end_date + attributes = { + 'recurring_period' => 'weekly', + 'recurring_target' => 'due_date', + 'description' => 'a repeating todo', # generic + 'weekly_return_monday' => 'm', # weekly specific + 'ends_on' => 'invalid_value', + 'context_id' => @admin.contexts.first.id, + 'start_from' => Time.zone.now - 1.week, + 'weekly_every_x_week' => 1, + 'recurring_show_always' => false, + 'recurring_show_days_before' => 5, + } + + pattern = create_pattern(attributes) + assert !pattern.valid? + + attributes['ends_on']='ends_on_end_date' + pattern = create_pattern(attributes) + assert !pattern.valid?, "should not be valid, because end_date is not supplied" + + attributes['end_date']= Time.zone.now + 1.week + pattern = create_pattern(attributes) + assert pattern.valid?, "should be valid" + end + + private + + def create_pattern(attributes) + builder = RecurringTodosBuilder.new(@admin, attributes) + builder.build + builder.pattern + end + end end \ No newline at end of file diff --git a/test/models/recurring_todos/recurring_todos_builder_test.rb b/test/models/recurring_todos/recurring_todos_builder_test.rb index e72e4633..a5771f86 100644 --- a/test/models/recurring_todos/recurring_todos_builder_test.rb +++ b/test/models/recurring_todos/recurring_todos_builder_test.rb @@ -10,7 +10,11 @@ module RecurringTodos end def test_create_builder_needs_selector - assert_raise(Exception){ builder = RecurringTodosBuilder.new(@admin, {}) } + assert_raise(RuntimeError){ builder = RecurringTodosBuilder.new(@admin, {}) } + end + + def test_create_builder_needs_valid_selector + assert_raise(RuntimeError){ builder = RecurringTodosBuilder.new(@admin, { 'recurring_period' => 'wrong_value'}) } end def test_create_builder_uses_selector @@ -35,8 +39,8 @@ module RecurringTodos 'end_date' => '05/05/05' }) - assert builder.attributes.get(:start_from).is_a?(ActiveSupport::TimeWithZone), "Dates should be parsed to ActiveSupport::TimeWithZone class" - assert builder.attributes.get(:end_date).is_a?(ActiveSupport::TimeWithZone), "Dates should be parsed to ActiveSupport::TimeWithZone class" + assert builder.attributes[:start_from].is_a?(ActiveSupport::TimeWithZone), "Dates should be parsed to ActiveSupport::TimeWithZone class" + assert builder.attributes[:end_date ].is_a?(ActiveSupport::TimeWithZone), "Dates should be parsed to ActiveSupport::TimeWithZone class" end def test_exisisting_project_is_used From a7807a4b665e30a440de68bc409f6e924166f0c2 Mon Sep 17 00:00:00 2001 From: Reinier Balt Date: Mon, 10 Feb 2014 22:30:06 +0100 Subject: [PATCH 15/24] dry form_helper using meta programming. not sure if this improves readability, but codeclimate should be happier... --- .../recurring_todos/form_helper.rb | 29 ++++++++++--------- .../recurring_todos/form_helper_test.rb | 24 +++++++++++++++ 2 files changed, 40 insertions(+), 13 deletions(-) create mode 100644 test/models/recurring_todos/form_helper_test.rb diff --git a/app/controllers/recurring_todos/form_helper.rb b/app/controllers/recurring_todos/form_helper.rb index 0a81f08d..a4f078b3 100644 --- a/app/controllers/recurring_todos/form_helper.rb +++ b/app/controllers/recurring_todos/form_helper.rb @@ -4,6 +4,16 @@ module RecurringTodos def initialize(recurring_todo) @recurring_todo = recurring_todo + + @method_map = { + # delegate daily_xxx to daily_pattern.xxx + "daily" => {prefix: "", method: daily_pattern}, + "weekly" => {prefix: "", method: weekly_pattern}, + "monthly" => {prefix: "", method: monthly_pattern}, + "yearly" => {prefix: "", method: yearly_pattern}, + # delegate on_xxx to weekly_pattern.on_xxx + "on" => {prefix: "on_", method: weekly_pattern} + } end def create_pattern(pattern_class) @@ -29,20 +39,13 @@ module RecurringTodos end def method_missing(method, *args) - if method.to_s =~ /^daily_(.+)$/ - daily_pattern.send($1, *args) - elsif method.to_s =~ /^weekly_(.+)$/ - weekly_pattern.send($1, *args) - elsif method.to_s =~ /^monthly_(.+)$/ - monthly_pattern.send($1, *args) - elsif method.to_s =~ /^yearly_(.+)$/ - yearly_pattern.send($1, *args) - elsif method.to_s =~ /^on_(.+)$/ # on_monday, on_tuesday, etc. - weekly_pattern.send(method, *args) - else - # no match, let @recurring_todo handle it, or fail - @recurring_todo.send(method, *args) + # delegate daily_xxx to daily_pattern, weekly_xxx to weekly_pattern, etc. + if method.to_s =~ /^([^_]+)_(.+)$/ + return @method_map[$1][:method].send(@method_map[$1][:prefix]+$2, *args) unless @method_map[$1].nil? end + + # no match, let @recurring_todo handle it, or fail + @recurring_todo.send(method, *args) end end diff --git a/test/models/recurring_todos/form_helper_test.rb b/test/models/recurring_todos/form_helper_test.rb new file mode 100644 index 00000000..80ae0c0c --- /dev/null +++ b/test/models/recurring_todos/form_helper_test.rb @@ -0,0 +1,24 @@ +require_relative '../../test_helper' + +module RecurringTodos + + class AttributeHandlerTest < ActiveSupport::TestCase + fixtures :users + + def test_method_missing + rt = users(:admin_user).recurring_todos.first + rt.every_other1 = 42 + rt.every_day = 'smtwtfs' + rt.save + + h = FormHelper.new(rt) + + assert_equal 42, h.daily_every_x_days, "should be passed to DailyRepeatPattern" + assert_equal 42, h.weekly_every_x_week, "should be passed to WeeklyRepeatPattern" + assert_equal 42, h.monthly_every_x_day, "should be passed to MonthlyRepeatPattern" + assert_equal 42, h.yearly_every_x_day, "should be passed to YearlyRepeatPattern" + assert h.on_monday, "should be passed to WeeklyRepeatPattern" + end + end + +end \ No newline at end of file From cbdbb792a5379d76cdb01f4ead1f65ed72b5ae2e Mon Sep 17 00:00:00 2001 From: Reinier Balt Date: Sun, 23 Feb 2014 15:13:53 +0100 Subject: [PATCH 16/24] add basis tests for patterns and remove doubles from recurring_todo_test.rb --- .../abstract_recurring_todos_builder.rb | 4 + .../recurring_todos/monthly_repeat_pattern.rb | 2 +- .../recurring_todos_builder.rb | 4 + test/fixtures/recurring_todos.yml | 2 +- test/models/recurring_todo_test.rb | 107 +----------------- .../daily_repeat_pattern_test.rb | 25 +++- .../monthly_repeat_pattern_test.rb | 93 ++++++++++++++- .../weekly_repeat_pattern_test.rb | 31 +++++ .../yearly_repeat_pattern_test.rb | 55 +++++++++ 9 files changed, 212 insertions(+), 111 deletions(-) diff --git a/app/models/recurring_todos/abstract_recurring_todos_builder.rb b/app/models/recurring_todos/abstract_recurring_todos_builder.rb index d3a4ecaa..67b55aef 100644 --- a/app/models/recurring_todos/abstract_recurring_todos_builder.rb +++ b/app/models/recurring_todos/abstract_recurring_todos_builder.rb @@ -51,6 +51,10 @@ module RecurringTodos @pattern.attributes end + def errors + @recurring_todo.try(:errors) + end + def attributes_to_filter raise Exception.new, "attributes_to_filter should be overridden" end diff --git a/app/models/recurring_todos/monthly_repeat_pattern.rb b/app/models/recurring_todos/monthly_repeat_pattern.rb index aa901b4a..0fa1f6f2 100644 --- a/app/models/recurring_todos/monthly_repeat_pattern.rb +++ b/app/models/recurring_todos/monthly_repeat_pattern.rb @@ -52,7 +52,7 @@ module RecurringTodos case recurrence_selector when 0 # 'monthly_every_x_day' validate_not_blank(every_x_month, "Every other nth month may not be empty for recurrence setting") - when 1 # 'every_xth_day' + when 1 # 'monthly_every_xth_day' validate_not_blank(every_x_month2, "Every other nth month may not be empty for recurrence setting") validate_not_blank(day_of_week, "The day of the month may not be empty for recurrence setting") else diff --git a/app/models/recurring_todos/recurring_todos_builder.rb b/app/models/recurring_todos/recurring_todos_builder.rb index ed8dd9cf..56bbcaf5 100644 --- a/app/models/recurring_todos/recurring_todos_builder.rb +++ b/app/models/recurring_todos/recurring_todos_builder.rb @@ -51,6 +51,10 @@ module RecurringTodos @builder.pattern end + def errors + @builder.errors + end + private def valid_selector?(selector) diff --git a/test/fixtures/recurring_todos.yml b/test/fixtures/recurring_todos.yml index ebbb188f..b1c4cf70 100644 --- a/test/fixtures/recurring_todos.yml +++ b/test/fixtures/recurring_todos.yml @@ -92,7 +92,7 @@ call_bill_gates_every_week: end_date: ~ number_of_occurences: ~ target: due_date - show_from_delta: ~ + show_from_delta: 5 recurring_period: weekly recurrence_selector: ~ show_always: false diff --git a/test/models/recurring_todo_test.rb b/test/models/recurring_todo_test.rb index 9279d5f1..54fbbb3b 100644 --- a/test/models/recurring_todo_test.rb +++ b/test/models/recurring_todo_test.rb @@ -320,109 +320,4 @@ class RecurringTodoTest < ActiveSupport::TestCase assert_equal true, @every_day.continues_recurring?(@in_three_days) assert_equal 0, @every_day.occurences_count end - - def test_invalid_recurring_period_will_not_save - @every_day.recurring_period = 'invalid' - assert !@every_day.valid? - - @every_month.recurrence_selector = 99 - assert_raise(Exception){ @every_month.valid? } - - @yearly.recurrence_selector = 99 - assert_raise(RuntimeError){ @yearly.valid? } - end - - def test_every_n_the_day_must_be_filled - @every_day.every_other1 = nil - assert !@every_day.valid? - end - - def test_every_n_week_must_be_filled - @every_week.every_other1 = nil - assert !@every_week.valid? - end - - def test_every_n_month_must_be_filled - @every_month.every_other1 = nil - @every_month.every_other2 = nil - assert !@every_month.valid? - - @every_month.recurrence_selector = 0 - assert !@every_month.valid? - end - - def test_set_every_n_days_from_form_input - builder = RecurringTodos::RecurringTodosBuilder.new(users(:admin_user), { - :description => "Task every 2 days", - :context_id => Context.first.id, - :recurring_target => "show_from_date", - :start_from => "01/01/01", - :ends_on => "no_end_date", - :recurring_period => "daily", - :daily_selector => "daily_every_x_day", - :daily_every_x_days => 2, - }) - builder.save - todo = builder.saved_recurring_todo - - assert todo.valid?, todo.errors.full_messages - assert_equal 2, todo.every_other1 - end - - def test_set_every_n_weeks_from_form_input - builder = RecurringTodos::RecurringTodosBuilder.new(users(:admin_user), { - :description => "Task every 3 weeks", - :context_id => Context.first.id, - :recurring_target => "show_from_date", - :start_from => "01/01/01", - :ends_on => "no_end_date", - :recurring_period => "weekly", - :weekly_every_x_week => 3, - :weekly_return_monday => "m", - }) - builder.save - todo = builder.saved_recurring_todo - - assert todo.valid?, todo.errors.full_messages - assert_equal 3, todo.every_other1 - assert todo.pattern.on_monday - end - - def test_set_every_n_months_from_form_input - builder = RecurringTodos::RecurringTodosBuilder.new(users(:admin_user), { - :description => "Task every 4 months", - :context_id => Context.first.id, - :recurring_target => "show_from_date", - :start_from => "01/01/01", - :ends_on => "no_end_date", - :recurring_period => "monthly", - :monthly_selector => "monthly_every_x_day", - :monthly_every_x_day => 1, - :monthly_every_x_month => 4, - }) - builder.save - todo = builder.saved_recurring_todo - - assert todo.valid?, todo.errors.full_messages - assert_equal 4, todo.every_other2 - end - - def test_set_yearly_from_form_input - builder = RecurringTodos::RecurringTodosBuilder.new(users(:admin_user), { - :description => "Task every year in May", - :context_id => Context.first.id, - :recurring_target => "show_from_date", - :start_from => "01/01/01", - :ends_on => "no_end_date", - :recurring_period => "yearly", - :yearly_selector => "yearly_every_x_day", - :yearly_every_x_day => 15, - :yearly_month_of_year => 5, - }) - builder.save - todo = builder.saved_recurring_todo - - assert todo.valid?, todo.errors.full_messages - assert_equal 5, todo.every_other2 - end -end +end \ No newline at end of file diff --git a/test/models/recurring_todos/daily_repeat_pattern_test.rb b/test/models/recurring_todos/daily_repeat_pattern_test.rb index 2e00db9b..173171c8 100644 --- a/test/models/recurring_todos/daily_repeat_pattern_test.rb +++ b/test/models/recurring_todos/daily_repeat_pattern_test.rb @@ -7,7 +7,30 @@ module RecurringTodos def setup @admin = users(:admin_user) - end + end + + def test_daily_attributes + rt = @admin.recurring_todos.first + + assert_equal rt.every_other1, rt.pattern.every_x_days + assert_equal rt.only_work_days, rt.pattern.only_work_days? + end + + def test_validate + rt = @admin.recurring_todos.first + assert rt.valid?, "rt should be valid at start" + + rt.every_other1 = nil + rt.only_work_days = false + assert !rt.valid?, "every_x_days should not be empty then only_work_days==false" + + rt.only_work_days = true + assert rt.valid?, "every_x_days may have any value for only_work_days==true" + + rt.only_work_days = false + rt.every_other1 = 2 + assert rt.valid?, "should be valid again" + end end diff --git a/test/models/recurring_todos/monthly_repeat_pattern_test.rb b/test/models/recurring_todos/monthly_repeat_pattern_test.rb index e481eace..a8c0994d 100644 --- a/test/models/recurring_todos/monthly_repeat_pattern_test.rb +++ b/test/models/recurring_todos/monthly_repeat_pattern_test.rb @@ -7,8 +7,97 @@ module RecurringTodos def setup @admin = users(:admin_user) - end - + end + + def test_attribute_mapping + builder = RecurringTodosBuilder.new(@admin, { + 'recurring_period' => 'monthly', + 'description' => 'a repeating todo', # generic + 'recurring_period' => 'monthly', + 'recurring_target' => 'show_from_date', + 'ends_on' => 'ends_on_end_date', + 'end_date' => Time.zone.now + 1.week, + 'start_from' => Time.zone.now, + 'context_name' => @admin.contexts.first.name, + 'monthly_selector' => 'monthly_every_x_day', + 'monthly_every_xth_day' => 1, + 'monthly_day_of_week' => 2, + 'monthly_every_x_month' => 3 + }) + + assert builder.save, "should save: #{builder.errors.full_messages}" + rt = builder.saved_recurring_todo + + assert builder.pattern.is_a?(MonthlyRepeatPattern), "should be monthly pattern, but is #{builder.pattern.class}" + assert builder.pattern.every_x_day?, "should be true for monthly_every_x_day" + assert 1, rt.recurrence_selector + + assert_equal 1, builder.pattern.every_xth_day, "pattern should map every_other2 to every_xth_day from monthly_every_xth_day" + assert_equal 1, rt.every_other3 + + assert_equal 2, builder.pattern.day_of_week, "pattern should map every_count to day_of_week from monthly_day_of_week" + assert_equal 2, rt.every_count + end + + def test_every_x_month + builder = RecurringTodosBuilder.new(@admin, { + 'recurring_period' => 'monthly', + 'description' => 'a repeating todo', # generic + 'recurring_period' => 'monthly', + 'recurring_target' => 'show_from_date', + 'ends_on' => 'ends_on_end_date', + 'end_date' => Time.zone.now + 1.week, + 'start_from' => Time.zone.now, + 'context_name' => @admin.contexts.first.name, + 'monthly_selector' => 'monthly_every_x_day', + 'monthly_every_x_month' => 3, + 'monthly_every_x_month2' => 2 + }) + + assert builder.save, "should save: #{builder.errors.full_messages}" + rt = builder.saved_recurring_todo + + assert_equal 3, builder.pattern.every_x_month + assert_equal 3, rt.every_other2 + + builder = RecurringTodosBuilder.new(@admin, { + 'recurring_period' => 'monthly', + 'description' => 'a repeating todo', # generic + 'recurring_period' => 'monthly', + 'recurring_target' => 'show_from_date', + 'ends_on' => 'ends_on_end_date', + 'end_date' => Time.zone.now + 1.week, + 'start_from' => Time.zone.now, + 'context_name' => @admin.contexts.first.name, + 'monthly_selector' => 'monthly_every_xth_day', + 'monthly_every_x_month' => 3, + 'monthly_every_x_month2' => 2, + 'monthly_day_of_week' => 7 + }) + + assert builder.save, "should save: #{builder.errors.full_messages}" + rt = builder.saved_recurring_todo + + assert_equal 2, builder.pattern.every_x_month2 + assert_equal 2, rt.every_other2 + end + + def test_validations + rt = @admin.recurring_todos.where(recurring_period: 'monthly').first + assert rt.valid?, "should be valid at start: #{rt.errors.full_messages}" + + rt.recurrence_selector = 0 # 'monthly_every_x_day' + rt.every_other2 = nil + assert !rt.valid?, "should not be valid since every_x_month is empty" + + rt.recurrence_selector = 1 # 'monthly_every_xth_day' + rt.every_other2 = nil + assert !rt.valid?, "should not be valid since every_xth_month is empty" + + rt.every_count = nil + assert !rt.valid?, "should not be valid since day_of_week is empty" + end + end end \ No newline at end of file diff --git a/test/models/recurring_todos/weekly_repeat_pattern_test.rb b/test/models/recurring_todos/weekly_repeat_pattern_test.rb index b69f17d2..95303b48 100644 --- a/test/models/recurring_todos/weekly_repeat_pattern_test.rb +++ b/test/models/recurring_todos/weekly_repeat_pattern_test.rb @@ -8,6 +8,37 @@ module RecurringTodos def setup @admin = users(:admin_user) end + + def test_every_x_week + rt = @admin.recurring_todos.where(recurring_period: 'weekly').first + + assert_equal rt.every_other1, rt.pattern.every_x_week + end + + def test_on_xday + rt = @admin.recurring_todos.where(recurring_period: 'weekly').first + assert rt.valid?, "should be valid at start: id= #{rt.id} --> #{rt.errors.full_messages}" + + rt.every_day = 'smtwtfs' + %w{monday tuesday wednesday thursday friday saturday sunday}.each do |day| + assert rt.pattern.send("on_#{day}"), "on_#{day} should return true" + end + + rt.every_day = 'smt tfs' # no wednesday + assert !rt.pattern.on_wednesday, "wednesday should be false" + end + + def test_validations + rt = @admin.recurring_todos.where(recurring_period: 'weekly').first + assert rt.valid?, "should be valid at start: #{rt.errors.full_messages}" + + rt.every_other1 = nil + assert !rt.valid?, "missing evert_x_week should not be valid" + + rt.every_other1 = 1 + rt.every_day = ' ' + assert !rt.valid?, "missing selected days in every_day" + end end diff --git a/test/models/recurring_todos/yearly_repeat_pattern_test.rb b/test/models/recurring_todos/yearly_repeat_pattern_test.rb index fd8b1150..7bc22eb7 100644 --- a/test/models/recurring_todos/yearly_repeat_pattern_test.rb +++ b/test/models/recurring_todos/yearly_repeat_pattern_test.rb @@ -9,6 +9,61 @@ module RecurringTodos @admin = users(:admin_user) end + def test_attribute_mapping + builder = RecurringTodosBuilder.new(@admin, { + 'recurring_period' => 'yearly', + 'description' => 'a repeating todo', # generic + 'recurring_target' => 'show_from_date', + 'ends_on' => 'ends_on_end_date', + 'end_date' => Time.zone.now + 1.week, + 'start_from' => Time.zone.now, + 'context_name' => @admin.contexts.first.name, + 'yearly_selector' => 'yearly_every_x_day', + 'yearly_every_x_day' => 5, + 'yearly_every_xth_day' => 6, + 'yearly_month_of_year' => 7, + 'yearly_month_of_year2' => 8, + 'yearly_day_of_week' => 9 + }) + + assert builder.save, "should save: #{builder.errors.full_messages}" + rt = builder.saved_recurring_todo + + assert builder.pattern.is_a?(YearlyRepeatPattern), "should be monthly pattern, but is #{builder.pattern.class}" + + assert_equal rt.recurrence_selector, builder.pattern.recurrence_selector + assert_equal rt.every_other2, builder.pattern.month_of_year + assert_equal rt.every_other1, builder.pattern.every_x_day + assert_equal rt.every_other3, builder.pattern.every_xth_day + assert_equal rt.every_count, builder.pattern.day_of_week + assert_equal Time.zone.now.month, builder.pattern.month_of_year2, "uses default for moy2, which is current month" + + rt.recurrence_selector = 1 # 'yearly_every_xth_day' + assert_equal rt.every_other2, rt.pattern.month_of_year2, "uses every_other2 for moy2 when yearly_every_xth_day" + end + + def test_validations + rt = @admin.recurring_todos.where(recurring_period: 'yearly').first + assert rt.valid?, "should be valid at start: #{rt.errors.full_messages}" + + rt.recurrence_selector = 0 # 'yearly_every_x_day' + rt.every_other1 = nil + assert !rt.valid?, "should not be valid since every_x_day is empty" + rt.every_other1 = 1 + rt.every_other2 = nil + assert !rt.valid?, "should not be valid since month_of_year is empty" + + rt.recurrence_selector = 1 # 'yearly_every_xth_day' + rt.every_other2 = nil + assert !rt.valid?, "should not be valid since month_of_year2 is empty" + rt.every_other2 = 1 + rt.every_other3 = nil + assert !rt.valid?, "should not be valid since every_xth_day is empty" + rt.every_other3 = 1 + rt.every_count = nil + assert !rt.valid?, "should not be valid since day_of_week is empty" + end + end end \ No newline at end of file From d8507bf8b795f4d9daada3a42f336a3afca99293 Mon Sep 17 00:00:00 2001 From: Reinier Balt Date: Sun, 23 Feb 2014 15:54:02 +0100 Subject: [PATCH 17/24] move as_text helpers to respective pattern --- app/models/recurring_todo.rb | 84 +------------------ .../abstract_repeat_pattern.rb | 23 +++++ .../recurring_todos/daily_repeat_pattern.rb | 10 +++ .../recurring_todos/monthly_repeat_pattern.rb | 23 ++++- .../recurring_todos/weekly_repeat_pattern.rb | 8 ++ .../recurring_todos/yearly_repeat_pattern.rb | 14 ++++ 6 files changed, 77 insertions(+), 85 deletions(-) diff --git a/app/models/recurring_todo.rb b/app/models/recurring_todo.rb index b731e697..44f9988d 100644 --- a/app/models/recurring_todo.rb +++ b/app/models/recurring_todo.rb @@ -90,90 +90,12 @@ class RecurringTodo < ActiveRecord::Base end end - def recurring_target_as_text - case self.target - when 'due_date' - I18n.t("todos.recurrence.pattern.due") - when 'show_from_date' - I18n.t("todos.recurrence.pattern.show") - else - raise Exception.new, "unexpected value of recurrence target '#{self.target}'" - end - end - - def daily_recurrence_pattern - if only_work_days - I18n.t("todos.recurrence.pattern.on_work_days") - elsif every_other1 > 1 - I18n.t("todos.recurrence.pattern.every_n", :n => every_other1) + " " + I18n.t("common.days_midsentence.other") - else - I18n.t("todos.recurrence.pattern.every_day") - end - end - - def weekly_recurrence_pattern - if every_other1 > 1 - I18n.t("todos.recurrence.pattern.every_n", :n => every_other1) + " " + I18n.t("common.weeks") - else - I18n.t('todos.recurrence.pattern.weekly') - end - end - - def monthly_recurrence_pattern - return "invalid repeat pattern" if every_other2.nil? - if self.recurrence_selector == 0 - on_day = " #{I18n.t('todos.recurrence.pattern.on_day_n', :n => self.every_other1)}" - if self.every_other2>1 - I18n.t("todos.recurrence.pattern.every_n", :n => self.every_other2) + " " + I18n.t('common.months') + on_day - else - I18n.t("todos.recurrence.pattern.every_month") + on_day - end - else - n_months = if self.every_other2 > 1 - "#{self.every_other2} #{I18n.t('common.months')}" - else - I18n.t('common.month') - end - I18n.t('todos.recurrence.pattern.every_xth_day_of_every_n_months', - :x => self.xth, :day => self.day_of_week, :n_months => n_months) - end - end - - def yearly_recurrence_pattern - if self.recurrence_selector == 0 - I18n.t("todos.recurrence.pattern.every_year_on", - :date => I18n.l(DateTime.new(Time.zone.now.year, self.every_other2, self.every_other1), :format => :month_day)) - else - I18n.t("todos.recurrence.pattern.every_year_on", - :date => I18n.t("todos.recurrence.pattern.the_xth_day_of_month", :x => self.xth, :day => self.day_of_week, :month => self.month_of_year)) - end - end - def recurrence_pattern - return "invalid repeat pattern" if every_other1.nil? - case recurring_period - when 'daily' then daily_recurrence_pattern - when 'weekly' then weekly_recurrence_pattern - when 'monthly' then monthly_recurrence_pattern - when 'yearly' then yearly_recurrence_pattern - else - 'unknown recurrence pattern: period unknown' - end + pattern.recurrence_pattern end - def xth - xth_day = [ - I18n.t('todos.recurrence.pattern.first'),I18n.t('todos.recurrence.pattern.second'),I18n.t('todos.recurrence.pattern.third'), - I18n.t('todos.recurrence.pattern.fourth'),I18n.t('todos.recurrence.pattern.last')] - self.every_other3.nil? ? '??' : xth_day[self.every_other3-1] - end - - def day_of_week - self.every_count.nil? ? '??' : I18n.t('todos.recurrence.pattern.day_names')[self.every_count] - end - - def month_of_year - self.every_other2.nil? ? '??' : I18n.t('todos.recurrence.pattern.month_names')[self.every_other2] + def recurring_target_as_text + pattern.recurring_target_as_text end def starred? diff --git a/app/models/recurring_todos/abstract_repeat_pattern.rb b/app/models/recurring_todos/abstract_repeat_pattern.rb index 0c7ad29a..ec2b6422 100644 --- a/app/models/recurring_todos/abstract_repeat_pattern.rb +++ b/app/models/recurring_todos/abstract_repeat_pattern.rb @@ -32,6 +32,29 @@ module RecurringTodos get :show_from_delta end + def recurring_target_as_text + target == 'due_date' ? I18n.t("todos.recurrence.pattern.due") : I18n.t("todos.recurrence.pattern.show") + end + + def recurrence_pattern + raise "Should not call AbstractRepeatPattern.recurrence_pattern directly. Overwrite in subclass" + end + + def xth(x) + xth_day = [ + I18n.t('todos.recurrence.pattern.first'),I18n.t('todos.recurrence.pattern.second'),I18n.t('todos.recurrence.pattern.third'), + I18n.t('todos.recurrence.pattern.fourth'),I18n.t('todos.recurrence.pattern.last')] + x.nil? ? '??' : xth_day[x-1] + end + + def day_of_week_as_text(day) + day.nil? ? '??' : I18n.t('todos.recurrence.pattern.day_names')[day] + end + + def month_of_year_as_text(month) + month.nil? ? '??' : I18n.t('todos.recurrence.pattern.month_names')[month] + end + def build_recurring_todo(attribute_handler) @recurring_todo = @user.recurring_todos.build(attribute_handler.safe_attributes) end diff --git a/app/models/recurring_todos/daily_repeat_pattern.rb b/app/models/recurring_todos/daily_repeat_pattern.rb index 87f14350..4c9ec1c2 100644 --- a/app/models/recurring_todos/daily_repeat_pattern.rb +++ b/app/models/recurring_todos/daily_repeat_pattern.rb @@ -14,6 +14,16 @@ module RecurringTodos get :only_work_days end + def recurrence_pattern + if only_work_days? + I18n.t("todos.recurrence.pattern.on_work_days") + elsif every_x_days > 1 + I18n.t("todos.recurrence.pattern.every_n", :n => every_x_days) + " " + I18n.t("common.days_midsentence.other") + else + I18n.t("todos.recurrence.pattern.every_day") + end + end + def validate super errors[:base] << "Every other nth day may not be empty for this daily recurrence setting" if (!only_work_days?) && every_x_days.blank? diff --git a/app/models/recurring_todos/monthly_repeat_pattern.rb b/app/models/recurring_todos/monthly_repeat_pattern.rb index 0fa1f6f2..997f2865 100644 --- a/app/models/recurring_todos/monthly_repeat_pattern.rb +++ b/app/models/recurring_todos/monthly_repeat_pattern.rb @@ -22,10 +22,6 @@ module RecurringTodos get(:recurrence_selector) == 1 end - def every_xth_day - get :every_other2 - end - def every_x_month # in case monthly pattern is every day x, return every_other2 otherwise # return a default value @@ -46,6 +42,25 @@ module RecurringTodos get :every_count end + def recurrence_pattern + if recurrence_selector == 0 + on_day = " #{I18n.t('todos.recurrence.pattern.on_day_n', :n => every_x_day)}" + if every_xth_day(0) > 1 + I18n.t("todos.recurrence.pattern.every_n", :n => every_xth_day) + " " + I18n.t('common.months') + on_day + else + I18n.t("todos.recurrence.pattern.every_month") + on_day + end + else + n_months = if get(:every_other2) > 1 + "#{get(:every_other2)} #{I18n.t('common.months')}" + else + I18n.t('common.month') + end + I18n.t('todos.recurrence.pattern.every_xth_day_of_every_n_months', + :x => xth(every_xth_day), :day => day_of_week_as_text(day_of_week), :n_months => n_months) + end + end + def validate super diff --git a/app/models/recurring_todos/weekly_repeat_pattern.rb b/app/models/recurring_todos/weekly_repeat_pattern.rb index 721907a8..697a14a1 100644 --- a/app/models/recurring_todos/weekly_repeat_pattern.rb +++ b/app/models/recurring_todos/weekly_repeat_pattern.rb @@ -20,6 +20,14 @@ module RecurringTodos get(:every_day) && get(:every_day)[n, 1] != ' ' end + def recurrence_pattern + if every_x_week > 1 + I18n.t("todos.recurrence.pattern.every_n", :n => every_x_week) + " " + I18n.t("common.weeks") + else + I18n.t('todos.recurrence.pattern.weekly') + end + end + def validate super validate_not_blank(every_x_week, "Every other nth week may not be empty for weekly recurrence setting") diff --git a/app/models/recurring_todos/yearly_repeat_pattern.rb b/app/models/recurring_todos/yearly_repeat_pattern.rb index 948b4dca..89942221 100644 --- a/app/models/recurring_todos/yearly_repeat_pattern.rb +++ b/app/models/recurring_todos/yearly_repeat_pattern.rb @@ -32,6 +32,20 @@ module RecurringTodos get(:recurrence_selector) == 1 ? get(:every_other2) : Time.zone.now.month end + def recurrence_pattern + if self.recurrence_selector == 0 + I18n.t("todos.recurrence.pattern.every_year_on", + :date => I18n.l(DateTime.new(Time.zone.now.year, month_of_year, every_x_day), :format => :month_day)) + else + I18n.t("todos.recurrence.pattern.every_year_on", + :date => I18n.t("todos.recurrence.pattern.the_xth_day_of_month", + :x => xth(every_xth_day), + :day => day_of_week_as_text(day_of_week), + :month => month_of_year_as_text(month_of_year) + )) + end + end + def validate super case recurrence_selector From b84adfc1728d5daceceea3f7ac24f8fc06d2f608 Mon Sep 17 00:00:00 2001 From: Reinier Balt Date: Thu, 27 Feb 2014 16:01:01 +0100 Subject: [PATCH 18/24] move recurrence text helpers into patterns. move next_date calculation into respective models --- app/models/recurring_todo.rb | 256 +----------------- .../abstract_repeat_pattern.rb | 95 ++++++- .../recurring_todos/daily_repeat_pattern.rb | 18 +- .../recurring_todos/monthly_repeat_pattern.rb | 48 ++++ .../weekly_recurring_todos_builder.rb | 7 +- .../recurring_todos/weekly_repeat_pattern.rb | 42 +++ .../recurring_todos/yearly_repeat_pattern.rb | 35 ++- config/locales/en.yml | 2 +- test/models/recurring_todo_test.rb | 34 +-- .../daily_repeat_pattern_test.rb | 11 + .../monthly_repeat_pattern_test.rb | 14 + .../weekly_repeat_pattern_test.rb | 8 + .../yearly_repeat_pattern_test.rb | 10 + 13 files changed, 291 insertions(+), 289 deletions(-) diff --git a/app/models/recurring_todo.rb b/app/models/recurring_todo.rb index 44f9988d..3473f81a 100644 --- a/app/models/recurring_todo.rb +++ b/app/models/recurring_todo.rb @@ -40,14 +40,6 @@ class RecurringTodo < ActiveRecord::Base end end - def pattern - if valid_period? - @pattern = eval("RecurringTodos::#{recurring_period.capitalize}RepeatPattern.new(user)") - @pattern.build_from_recurring_todo(self) - end - @pattern - end - def valid_period? %W[daily weekly monthly yearly].include?(recurring_period) end @@ -78,16 +70,12 @@ class RecurringTodo < ActiveRecord::Base # choosing between both options is done on recurrence_selector where 0 is # for first type and 1 for second type - - def switch_week_day(day, position) - self.every_day = ' ' if self.every_day.nil? - self.every_day = every_day[0, position] + day + every_day[position+1, every_day.length] - end - - { monday: 1, tuesday: 2, wednesday: 3, thursday: 4, friday: 5, saturday: 6, sunday: 0 }.each do |day, number| - define_method("weekly_return_#{day}=") do |selector| - switch_week_day(selector, number) if recurring_period=='weekly' + def pattern + if valid_period? + @pattern = eval("RecurringTodos::#{recurring_period.capitalize}RepeatPattern.new(user)") + @pattern.build_from_recurring_todo(self) end + @pattern end def recurrence_pattern @@ -103,214 +91,11 @@ class RecurringTodo < ActiveRecord::Base end def get_due_date(previous) - case self.target - when 'due_date' - get_next_date(previous) - when 'show_from_date' - # so leave due date empty - nil - else - raise Exception.new, "unexpected value of recurrence target '#{self.target}'" - end + pattern.get_due_date(previous) end def get_show_from_date(previous) - case self.target - when 'due_date' - # so set show from date relative to due date unless show_always is true or show_from_delta is nil - (self.show_always? || self.show_from_delta.nil?) ? nil : get_due_date(previous) - self.show_from_delta.days - when 'show_from_date' - # Leave due date empty - get_next_date(previous) - else - raise Exception.new, "unexpected value of recurrence target '#{self.target}'" - end - end - - def get_next_date(previous) - case self.recurring_period - when 'daily' then get_daily_date(previous) - when 'weekly' then get_weekly_date(previous) - when 'monthly' then get_monthly_date(previous) - when 'yearly' then get_yearly_date(previous) - else - raise Exception.new, "unknown recurrence pattern: '#{self.recurring_period}'" - end - end - - def get_daily_date(previous) - # previous is the due date of the previous todo or it is the completed_at - # date when the completed_at date is after due_date (i.e. you did not make - # the due date in time) - # - # assumes self.recurring_period == 'daily' - - start = determine_start(previous, 1.day) - - if self.only_work_days - return start + 2.day if start.wday() == 6 # saturday - return start + 1.day if start.wday() == 0 # sunday - return start - else # every nth day; n = every_other1 - # if there was no previous todo, do not add n: the first todo starts on - # today or on start_from - return previous == nil ? start : start+every_other1.day-1.day - end - end - - def get_weekly_date(previous) - # determine start - if previous == nil - start = self.start_from.nil? ? Time.zone.now : self.start_from - else - start = previous + 1.day - if start.wday() == 0 - # we went to a new week , go to the nth next week and find first match - # that week. Note that we already went into the next week, so -1 - start += (self.every_other1-1).week - end - unless self.start_from.nil? - # check if the start_from date is later than previous. If so, use - # start_from as start to search for next date - start = self.start_from if self.start_from > previous - end - end - - day = find_first_day_in_this_week(start) - return day unless day == -1 - - # we did not find anything this week, so check the nth next, starting from - # sunday - start = start + self.every_other1.week - (start.wday()).days - - start = find_first_day_in_this_week(start) - return start unless start == -1 - - raise Exception.new, "unable to find next weekly date (#{self.every_day})" - end - - def get_monthly_date(previous) - start = determine_start(previous) - day = self.every_other1 - n = self.every_other2 - - case self.recurrence_selector - when 0 # specific day of the month - if (previous && start.mday >= day) || (previous.nil? && start.mday > day) - # there is no next day n in this month, search in next month - # - # start += n.months - # - # The above seems to not work. Fiddle with timezone. Looks like we hit a - # bug in rails here where 2008-12-01 +0100 plus 1.month becomes - # 2008-12-31 +0100. For now, just calculate in UTC and convert back to - # local timezone. - # - # TODO: recheck if future rails versions have this problem too - start = Time.utc(start.year, start.month, start.day)+n.months - start = Time.zone.local(start.year, start.month, start.day) - - # go back to day - end - Time.zone.local(start.year, start.month, day) - - when 1 # relative weekday of a month - the_next = get_xth_day_of_month(self.every_other3, self.every_count, start.month, start.year) - if the_next.nil? || the_next <= start - # the nth day is already passed in this month, go to next month and try - # again - - # fiddle with timezone. Looks like we hit a bug in rails here where - # 2008-12-01 +0100 plus 1.month becomes 2008-12-31 +0100. For now, just - # calculate in UTC and convert back to local timezone. - # TODO: recheck if future rails versions have this problem too - the_next = Time.utc(the_next.year, the_next.month, the_next.day)+n.months - the_next = Time.zone.local(the_next.year, the_next.month, the_next.day) - - # TODO: if there is still no match, start will be set to nil. if we ever - # support 5th day of the month, we need to handle this case - the_next = get_xth_day_of_month(self.every_other3, self.every_count, the_next.month, the_next.year) - end - the_next - else - raise Exception.new, "unknown monthly recurrence selection (#{self.recurrence_selector})" - end - end - - def get_xth_day_of_month(x, weekday, month, year) - if x == 5 - # last -> count backwards. use UTC to avoid strange timezone oddities - # where last_day -= 1.day seems to shift tz+0100 to tz+0000 - last_day = Time.utc(year, month, Time.days_in_month(month)) - while last_day.wday != weekday - last_day -= 1.day - end - # convert back to local timezone - Time.zone.local(last_day.year, last_day.month, last_day.day) - else - # 1-4th -> count upwards last -> count backwards. use UTC to avoid strange - # timezone oddities where last_day -= 1.day seems to shift tz+0100 to - # tz+0000 - start = Time.utc(year,month,1) - n = x - while n > 0 - while start.wday() != weekday - start+= 1.day - end - n -= 1 - start += 1.day unless n==0 - end - # convert back to local timezone - Time.zone.local(start.year, start.month, start.day) - end - end - - def get_yearly_date(previous) - start = determine_start(previous) - day = self.every_other1 - month = self.every_other2 - - case self.recurrence_selector - when 0 # specific day of a specific month - if start.month > month || (start.month == month && start.day >= day) - # if there is no next month n and day m in this year, search in next - # year - start = Time.zone.local(start.year+1, month, 1) - else - # if there is a next month n, stay in this year - start = Time.zone.local(start.year, month, 1) - end - Time.zone.local(start.year, month, day) - - when 1 # relative weekday of a specific month - # if there is no next month n in this year, search in next year - the_next = start.month > month ? Time.zone.local(start.year+1, month, 1) : start - - # get the xth day of the month - the_next = get_xth_day_of_month(self.every_other3, self.every_count, month, the_next.year) - - # if the_next is before previous, we went back into the past, so try next - # year - the_next = get_xth_day_of_month(self.every_other3, self.every_count, month, start.year+1) if the_next <= start - - the_next - else - raise Exception.new, "unknown monthly recurrence selection (#{self.recurrence_selector})" - end - end - - def continues_recurring?(previous) - return self.occurences_count < self.number_of_occurences unless self.number_of_occurences.nil? - return true if self.end_date.nil? || self.ends_on == 'no_end_date' - - case self.target - when 'due_date' - get_due_date(previous) <= self.end_date - when 'show_from_date' - get_show_from_date(previous) <= self.end_date - else - raise Exception.new, "unexpected value of recurrence target '#{self.target}'" - end + pattern.get_show_from_date(previous) end def done?(end_date) @@ -350,29 +135,8 @@ class RecurringTodo < ActiveRecord::Base self.save end - protected - - # Determine start date to calculate next date for recurring todo - # offset needs to be 1.day for daily patterns - def determine_start(previous, offset=0.day) - start = self.start_from || NullTime.new - now = Time.zone.now - if previous - # check if the start_from date is later than previous. If so, use - # start_from as start to search for next date - start > previous ? start : previous + offset - else - # skip to present - start > now ? start : now - end + def continues_recurring?(previous) + pattern.continues_recurring?(previous) end - def find_first_day_in_this_week(start) - # check if there are any days left this week for the next todo - start.wday().upto 6 do |i| - return start + (i-start.wday()).days unless self.every_day[i,1] == ' ' - end - -1 - end - -end +end \ No newline at end of file diff --git a/app/models/recurring_todos/abstract_repeat_pattern.rb b/app/models/recurring_todos/abstract_repeat_pattern.rb index ec2b6422..9e6dcd3a 100644 --- a/app/models/recurring_todos/abstract_repeat_pattern.rb +++ b/app/models/recurring_todos/abstract_repeat_pattern.rb @@ -24,7 +24,7 @@ module RecurringTodos get :target end - def show_always + def show_always? get :show_always end @@ -104,8 +104,8 @@ module RecurringTodos when 'show_from_date' # no validations when 'due_date' - validate_not_nil(show_always, "Please select when to show the action") - validate_not_blank(show_from_delta, "Please fill in the number of days to show the todo before the due date") unless show_always + validate_not_nil(show_always?, "Please select when to show the action") + validate_not_blank(show_from_delta, "Please fill in the number of days to show the todo before the due date") unless show_always? else errors[:base] << "Unexpected value of recurrence target selector '#{target}'" end @@ -119,6 +119,93 @@ module RecurringTodos @attributes[attribute] end + # gets the next due date. returns nil if recurrence_target is not 'due_date' + def get_due_date(previous) + case target + when 'due_date' + get_next_date(previous) + when 'show_from_date' + nil + end + end + + def get_show_from_date(previous) + case target + when 'due_date' + # so set show from date relative to due date unless show_always is true or show_from_delta is nil + return nil unless put_in_tickler? + get_due_date(previous) - show_from_delta.days + when 'show_from_date' + # Leave due date empty + get_next_date(previous) + end + end + + # checks if the next todos should be put in the tickler for recurrence_target == 'due_date' + def put_in_tickler? + !( show_always? || show_from_delta.nil?) + end + + def get_next_date(previous) + raise "Should not call AbstractRepeatPattern.get_next_date directly. Overwrite in subclass" + end + + def continues_recurring?(previous) + return @recurring_todo.occurences_count < @recurring_todo.number_of_occurences unless @recurring_todo.number_of_occurences.nil? + return true if self.end_date.nil? || self.ends_on == 'no_end_date' + + case self.target + when 'due_date' + get_due_date(previous) <= self.end_date + when 'show_from_date' + get_show_from_date(previous) <= self.end_date + end + end + + private + + # Determine start date to calculate next date for recurring todo + # offset needs to be 1.day for daily patterns + def determine_start(previous, offset=0.day) + start = self.start_from || NullTime.new + now = Time.zone.now + if previous + # check if the start_from date is later than previous. If so, use + # start_from as start to search for next date + start > previous ? start : previous + offset + else + # skip to present + start > now ? start : now + end + end + + def get_xth_day_of_month(x, weekday, month, year) + if x == 5 + # last -> count backwards. use UTC to avoid strange timezone oddities + # where last_day -= 1.day seems to shift tz+0100 to tz+0000 + last_day = Time.utc(year, month, Time.days_in_month(month)) + while last_day.wday != weekday + last_day -= 1.day + end + # convert back to local timezone + Time.zone.local(last_day.year, last_day.month, last_day.day) + else + # 1-4th -> count upwards last -> count backwards. use UTC to avoid strange + # timezone oddities where last_day -= 1.day seems to shift tz+0100 to + # tz+0000 + start = Time.utc(year,month,1) + n = x + while n > 0 + while start.wday() != weekday + start+= 1.day + end + n -= 1 + start += 1.day unless n==0 + end + # convert back to local timezone + Time.zone.local(start.year, start.month, start.day) + end + end + end - end \ No newline at end of file diff --git a/app/models/recurring_todos/daily_repeat_pattern.rb b/app/models/recurring_todos/daily_repeat_pattern.rb index 4c9ec1c2..4e94da55 100644 --- a/app/models/recurring_todos/daily_repeat_pattern.rb +++ b/app/models/recurring_todos/daily_repeat_pattern.rb @@ -29,7 +29,23 @@ module RecurringTodos errors[:base] << "Every other nth day may not be empty for this daily recurrence setting" if (!only_work_days?) && every_x_days.blank? end + def get_next_date(previous) + # previous is the due date of the previous todo or it is the completed_at + # date when the completed_at date is after due_date (i.e. you did not make + # the due date in time) + + start = determine_start(previous, 1.day) + + if only_work_days? + return start + 2.day if start.wday() == 6 # saturday + return start + 1.day if start.wday() == 0 # sunday + return start + else # every nth day; n = every_other1 + # if there was no previous todo, do not add n: the first todo starts on + # today or on start_from + return previous == nil ? start : start+every_x_days.day-1.day + end + end end - end \ No newline at end of file diff --git a/app/models/recurring_todos/monthly_repeat_pattern.rb b/app/models/recurring_todos/monthly_repeat_pattern.rb index 997f2865..ddb49c06 100644 --- a/app/models/recurring_todos/monthly_repeat_pattern.rb +++ b/app/models/recurring_todos/monthly_repeat_pattern.rb @@ -75,6 +75,54 @@ module RecurringTodos end end + def get_next_date(previous) + start = determine_start(previous) + day = every_x_day + n = get(:every_other2) + + case recurrence_selector + when 0 # specific day of the month + if (previous && start.mday >= day) || (previous.nil? && start.mday > day) + # there is no next day n in this month, search in next month + # + # start += n.months + # + # The above seems to not work. Fiddle with timezone. Looks like we hit a + # bug in rails here where 2008-12-01 +0100 plus 1.month becomes + # 2008-12-31 +0100. For now, just calculate in UTC and convert back to + # local timezone. + # + # TODO: recheck if future rails versions have this problem too + start = Time.utc(start.year, start.month, start.day)+n.months + start = Time.zone.local(start.year, start.month, start.day) + + # go back to day + end + Time.zone.local(start.year, start.month, day) + + when 1 # relative weekday of a month + the_next = get_xth_day_of_month(every_xth_day, day_of_week, start.month, start.year) + if the_next.nil? || the_next <= start + # the nth day is already passed in this month, go to next month and try + # again + + # fiddle with timezone. Looks like we hit a bug in rails here where + # 2008-12-01 +0100 plus 1.month becomes 2008-12-31 +0100. For now, just + # calculate in UTC and convert back to local timezone. + # TODO: recheck if future rails versions have this problem too + the_next = Time.utc(the_next.year, the_next.month, the_next.day)+n.months + the_next = Time.zone.local(the_next.year, the_next.month, the_next.day) + + # TODO: if there is still no match, start will be set to nil. if we ever + # support 5th day of the month, we need to handle this case + the_next = get_xth_day_of_month(every_xth_day, day_of_week, the_next.month, the_next.year) + end + the_next + else + raise Exception.new, "unknown monthly recurrence selection (#{self.recurrence_selector})" + end + end + end end \ No newline at end of file diff --git a/app/models/recurring_todos/weekly_recurring_todos_builder.rb b/app/models/recurring_todos/weekly_recurring_todos_builder.rb index 17ec13ba..8a5254ca 100644 --- a/app/models/recurring_todos/weekly_recurring_todos_builder.rb +++ b/app/models/recurring_todos/weekly_recurring_todos_builder.rb @@ -13,7 +13,10 @@ module RecurringTodos def map_attributes(mapping) mapping = map(mapping, :every_other1, 'weekly_every_x_week') - { monday: 1, tuesday: 2, wednesday: 3, thursday: 4, friday: 5, saturday: 6, sunday: 0 }.each{|day, index| mapping = map_day(mapping, :every_day, "weekly_return_#{day}", index)} + { monday: 1, tuesday: 2, wednesday: 3, thursday: 4, friday: 5, saturday: 6, sunday: 0 } + .each do |day, index| + mapping = map_day(mapping, :every_day, "weekly_return_#{day}", index) + end mapping end @@ -23,7 +26,7 @@ module RecurringTodos mapping.set_if_nil(source_key, ' ') # avoid nil mapping.set(key, mapping.get(key)[0, index] + mapping.get(source_key) + mapping.get(key)[index+1, mapping.get(key).length]) - mapping + mapping.except(source_key) end def selector_key diff --git a/app/models/recurring_todos/weekly_repeat_pattern.rb b/app/models/recurring_todos/weekly_repeat_pattern.rb index 697a14a1..8dead7fa 100644 --- a/app/models/recurring_todos/weekly_repeat_pattern.rb +++ b/app/models/recurring_todos/weekly_repeat_pattern.rb @@ -35,6 +35,48 @@ module RecurringTodos errors[:base] << "You must specify at least one day on which the todo recurs" unless something_set end + def get_next_date(previous) + # determine start + if previous.nil? + start = start_from.nil? ? Time.zone.now : self.start_from + else + start = previous + 1.day + if start.wday() == 0 + # we went to a new week , go to the nth next week and find first match + # that week. Note that we already went into the next week, so -1 + start += (every_x_week-1).week + end + unless self.start_from.nil? + # check if the start_from date is later than previous. If so, use + # start_from as start to search for next date + start = self.start_from if self.start_from > previous + end + end + + day = find_first_day_in_this_week(start) + return day unless day == -1 + + # we did not find anything this week, so check the nth next, starting from + # sunday + start = start + self.every_x_week.week - (start.wday()).days + + start = find_first_day_in_this_week(start) + return start unless start == -1 + + raise Exception.new, "unable to find next weekly date (#{self.every_day})" + end + + private + + def find_first_day_in_this_week(start) + # check if there are any days left this week for the next todo + start.wday().upto 6 do |i| + return start + (i-start.wday()).days if on_xday(i) + end + -1 + end + + end end \ No newline at end of file diff --git a/app/models/recurring_todos/yearly_repeat_pattern.rb b/app/models/recurring_todos/yearly_repeat_pattern.rb index 89942221..bc1c1772 100644 --- a/app/models/recurring_todos/yearly_repeat_pattern.rb +++ b/app/models/recurring_todos/yearly_repeat_pattern.rb @@ -61,6 +61,37 @@ module RecurringTodos end end - end - + def get_next_date(previous) + start = determine_start(previous) + day = every_x_day + month = get(:every_other2) + + case recurrence_selector + when 0 # specific day of a specific month + if start.month > month || (start.month == month && start.day >= day) + # if there is no next month n and day m in this year, search in next + # year + start = Time.zone.local(start.year+1, month, 1) + else + # if there is a next month n, stay in this year + start = Time.zone.local(start.year, month, 1) + end + Time.zone.local(start.year, month, day) + + when 1 # relative weekday of a specific month + # if there is no next month n in this year, search in next year + the_next = start.month > month ? Time.zone.local(start.year+1, month, 1) : start + + # get the xth day of the month + the_next = get_xth_day_of_month(self.every_xth_day, day_of_week, month, the_next.year) + + # if the_next is before previous, we went back into the past, so try next + # year + the_next = get_xth_day_of_month(self.every_xth_day, day_of_week, month, start.year+1) if the_next <= start + + the_next + end + end + + end end \ No newline at end of file diff --git a/config/locales/en.yml b/config/locales/en.yml index 5d18dd8e..e68c3a90 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -623,7 +623,7 @@ en: - December third: third every_n: every %{n} - on_day_n: on day %{n} + on_day_n: on day %{n} second: second every_xth_day_of_every_n_months: every %{x} %{day} of every %{n_months} from: from diff --git a/test/models/recurring_todo_test.rb b/test/models/recurring_todo_test.rb index 54fbbb3b..32eca170 100644 --- a/test/models/recurring_todo_test.rb +++ b/test/models/recurring_todo_test.rb @@ -25,13 +25,6 @@ class RecurringTodoTest < ActiveSupport::TestCase @thursday = Time.zone.local(2008,6,12) end - def test_pattern_text - assert_equal "every day", @every_day.recurrence_pattern - assert_equal "on work days", @every_workday.recurrence_pattern - assert_equal "every last friday of every 2 months", @monthly_every_last_friday.recurrence_pattern - assert_equal "every year on June 08", @yearly.recurrence_pattern - end - def test_daily_every_day # every_day should return todays date if there was no previous date due_date = @every_day.get_due_date(nil) @@ -96,29 +89,6 @@ class RecurringTodoTest < ActiveSupport::TestCase assert_equal false, @every_day.continues_recurring?(@in_four_days) end - def test_weekly_every_day_setters - @weekly_every_day.every_day = ' ' - - @weekly_every_day.weekly_return_sunday=('s') - assert_equal 's ', @weekly_every_day.every_day - @weekly_every_day.weekly_return_monday=('m') - assert_equal 'sm ', @weekly_every_day.every_day - @weekly_every_day.weekly_return_tuesday=('t') - assert_equal 'smt ', @weekly_every_day.every_day - @weekly_every_day.weekly_return_wednesday=('w') - assert_equal 'smtw ', @weekly_every_day.every_day - @weekly_every_day.weekly_return_thursday=('t') - assert_equal 'smtwt ', @weekly_every_day.every_day - @weekly_every_day.weekly_return_friday=('f') - assert_equal 'smtwtf ', @weekly_every_day.every_day - @weekly_every_day.weekly_return_saturday=('s') - assert_equal 'smtwtfs', @weekly_every_day.every_day - - # test remove - @weekly_every_day.weekly_return_wednesday=(' ') - assert_equal 'smt tfs', @weekly_every_day.every_day - end - def test_weekly_pattern assert_equal true, @weekly_every_day.continues_recurring?(nil) @@ -132,9 +102,7 @@ class RecurringTodoTest < ActiveSupport::TestCase assert_equal @sunday + 2.weeks, due_date # remove tuesday and wednesday - @weekly_every_day.weekly_return_tuesday=(' ') - @weekly_every_day.weekly_return_wednesday=(' ') - assert_equal 'sm tfs', @weekly_every_day.every_day + @weekly_every_day.every_day = 'sm tfs' due_date = @weekly_every_day.get_due_date(@monday) assert_equal @thursday, due_date diff --git a/test/models/recurring_todos/daily_repeat_pattern_test.rb b/test/models/recurring_todos/daily_repeat_pattern_test.rb index 173171c8..f73bc0ec 100644 --- a/test/models/recurring_todos/daily_repeat_pattern_test.rb +++ b/test/models/recurring_todos/daily_repeat_pattern_test.rb @@ -32,6 +32,17 @@ module RecurringTodos assert rt.valid?, "should be valid again" end + def test_pattern_text + @every_day = recurring_todos(:call_bill_gates_every_day) + @every_workday = recurring_todos(:call_bill_gates_every_workday) + + assert_equal "every day", @every_day.recurrence_pattern + assert_equal "on work days", @every_workday.recurrence_pattern + + @every_day.every_other1 = 2 + assert_equal "every 2 days", @every_day.recurrence_pattern + end + end end \ No newline at end of file diff --git a/test/models/recurring_todos/monthly_repeat_pattern_test.rb b/test/models/recurring_todos/monthly_repeat_pattern_test.rb index a8c0994d..b3608074 100644 --- a/test/models/recurring_todos/monthly_repeat_pattern_test.rb +++ b/test/models/recurring_todos/monthly_repeat_pattern_test.rb @@ -98,6 +98,20 @@ module RecurringTodos assert !rt.valid?, "should not be valid since day_of_week is empty" end + def test_pattern_text + rt = recurring_todos(:check_with_bill_every_last_friday_of_month) + assert_equal "every last friday of every 2 months", rt.recurrence_pattern + + rt.every_other2 = 1 + assert_equal "every last friday of every month", rt.recurrence_pattern + + rt.recurrence_selector = 0 + assert_equal "every 5 months on day 1", rt.recurrence_pattern + + rt.every_other3 = 1 + assert_equal "every month on day 1", rt.recurrence_pattern + end + end end \ No newline at end of file diff --git a/test/models/recurring_todos/weekly_repeat_pattern_test.rb b/test/models/recurring_todos/weekly_repeat_pattern_test.rb index 95303b48..c1904af1 100644 --- a/test/models/recurring_todos/weekly_repeat_pattern_test.rb +++ b/test/models/recurring_todos/weekly_repeat_pattern_test.rb @@ -40,6 +40,14 @@ module RecurringTodos assert !rt.valid?, "missing selected days in every_day" end + def test_pattern_text + rt = @admin.recurring_todos.where(recurring_period: 'weekly').first + assert_equal "every 2 weeks", rt.recurrence_pattern + + rt.every_other1 = 1 + assert_equal "weekly", rt.recurrence_pattern + end + end end \ No newline at end of file diff --git a/test/models/recurring_todos/yearly_repeat_pattern_test.rb b/test/models/recurring_todos/yearly_repeat_pattern_test.rb index 7bc22eb7..a08f88d9 100644 --- a/test/models/recurring_todos/yearly_repeat_pattern_test.rb +++ b/test/models/recurring_todos/yearly_repeat_pattern_test.rb @@ -64,6 +64,16 @@ module RecurringTodos assert !rt.valid?, "should not be valid since day_of_week is empty" end + def test_pattern_text + rt = recurring_todos(:birthday_reinier) + assert_equal "every year on June 08", rt.recurrence_pattern + + rt.recurrence_selector = 1 + rt.every_count = 3 + rt.every_other3 = 3 + assert_equal "every year on the third wednesday of June", rt.recurrence_pattern + end + end end \ No newline at end of file From a9fa955c332351615dbf68c5a04ccbf016932a0a Mon Sep 17 00:00:00 2001 From: Reinier Balt Date: Thu, 27 Feb 2014 16:35:47 +0100 Subject: [PATCH 19/24] move daily test to daily pattern test --- test/models/recurring_todo_test.rb | 31 ------------------- .../abstract_repeat_pattern_test.rb | 11 +++++++ .../daily_repeat_pattern_test.rb | 26 ++++++++++++++++ test/test_helper.rb | 17 +++++++++- 4 files changed, 53 insertions(+), 32 deletions(-) diff --git a/test/models/recurring_todo_test.rb b/test/models/recurring_todo_test.rb index 32eca170..c9ea6703 100644 --- a/test/models/recurring_todo_test.rb +++ b/test/models/recurring_todo_test.rb @@ -25,29 +25,6 @@ class RecurringTodoTest < ActiveSupport::TestCase @thursday = Time.zone.local(2008,6,12) end - def test_daily_every_day - # every_day should return todays date if there was no previous date - due_date = @every_day.get_due_date(nil) - # use only day-month-year compare, because milisec / secs could be different - assert_equal_dmy @today, due_date - - # when the last todo was completed today, the next todo is due tomorrow - due_date =@every_day.get_due_date(@today) - assert_equal @tomorrow, due_date - - # do something every 14 days - @every_day.every_other1=14 - due_date = @every_day.get_due_date(@today) - assert_equal @today+14.days, due_date - end - - def test_daily_work_days - assert_equal @monday, @every_workday.get_due_date(@friday) - assert_equal @monday, @every_workday.get_due_date(@saturday) - assert_equal @monday, @every_workday.get_due_date(@sunday) - assert_equal @tuesday, @every_workday.get_due_date(@monday) - end - def test_show_from_date # assume that target due_date works fine, i.e. don't do the same tests over @@ -81,14 +58,6 @@ class RecurringTodoTest < ActiveSupport::TestCase # weekly/monthly/yearly end - def test_end_date_on_recurring_todo - assert_equal true, @every_day.continues_recurring?(@in_three_days) - assert_equal true, @every_day.continues_recurring?(@in_four_days) - @every_day.end_date = @in_four_days - @every_day.ends_on = 'ends_on_end_date' - assert_equal false, @every_day.continues_recurring?(@in_four_days) - end - def test_weekly_pattern assert_equal true, @weekly_every_day.continues_recurring?(nil) diff --git a/test/models/recurring_todos/abstract_repeat_pattern_test.rb b/test/models/recurring_todos/abstract_repeat_pattern_test.rb index ce3792c8..748b0e94 100644 --- a/test/models/recurring_todos/abstract_repeat_pattern_test.rb +++ b/test/models/recurring_todos/abstract_repeat_pattern_test.rb @@ -6,6 +6,7 @@ module RecurringTodos fixtures :users def setup + super @admin = users(:admin_user) end @@ -88,6 +89,16 @@ module RecurringTodos assert pattern.valid?, "should be valid" end + def test_end_date_on_recurring_todo + rt = recurring_todos(:call_bill_gates_every_day) + + assert_equal true, rt.continues_recurring?(@in_three_days) + assert_equal true, rt.continues_recurring?(@in_four_days) + rt.end_date = @in_four_days + rt.ends_on = 'ends_on_end_date' + assert_equal false, rt.continues_recurring?(@in_four_days) + end + private def create_pattern(attributes) diff --git a/test/models/recurring_todos/daily_repeat_pattern_test.rb b/test/models/recurring_todos/daily_repeat_pattern_test.rb index f73bc0ec..89a6348e 100644 --- a/test/models/recurring_todos/daily_repeat_pattern_test.rb +++ b/test/models/recurring_todos/daily_repeat_pattern_test.rb @@ -6,7 +6,10 @@ module RecurringTodos fixtures :users def setup + super @admin = users(:admin_user) + @every_day = recurring_todos(:call_bill_gates_every_day) + @every_workday = recurring_todos(:call_bill_gates_every_workday) end def test_daily_attributes @@ -43,6 +46,29 @@ module RecurringTodos assert_equal "every 2 days", @every_day.recurrence_pattern end + def test_daily_every_day + # every_day should return todays date if there was no previous date + due_date = @every_day.get_due_date(nil) + # use only day-month-year compare, because milisec / secs could be different + assert_equal_dmy @today, due_date + + # when the last todo was completed today, the next todo is due tomorrow + due_date =@every_day.get_due_date(@today) + assert_equal @tomorrow, due_date + + # do something every 14 days + @every_day.every_other1=14 + due_date = @every_day.get_due_date(@today) + assert_equal @today+14.days, due_date + end + + def test_daily_work_days + assert_equal @monday, @every_workday.get_due_date(@friday) + assert_equal @monday, @every_workday.get_due_date(@saturday) + assert_equal @monday, @every_workday.get_due_date(@sunday) + assert_equal @tuesday, @every_workday.get_due_date(@monday) + end + end end \ No newline at end of file diff --git a/test/test_helper.rb b/test/test_helper.rb index 22b639f1..19c74096 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -13,7 +13,22 @@ class ActiveSupport::TestCase # Note: You'll currently still have to declare fixtures explicitly in integration tests # -- they do not yet inherit this setting fixtures :all - + + def setup + @today = Time.now.utc + @tomorrow = @today + 1.day + @in_three_days = @today + 3.days + @in_four_days = @in_three_days + 1.day # need a day after start_from + + @friday = Time.zone.local(2008,6,6) + @saturday = Time.zone.local(2008,6,7) + @sunday = Time.zone.local(2008,6,8) # june 8, 2008 was a sunday + @monday = Time.zone.local(2008,6,9) + @tuesday = Time.zone.local(2008,6,10) + @wednesday = Time.zone.local(2008,6,11) + @thursday = Time.zone.local(2008,6,12) + end + # Add more helper methods to be used by all tests here... def assert_value_changed(object, method = nil) initial_value = object.send(method) From 0c153ef28cb77c6a98c0106157d23c534293d1a1 Mon Sep 17 00:00:00 2001 From: Reinier Balt Date: Mon, 3 Mar 2014 21:48:25 +0100 Subject: [PATCH 20/24] improve test of daily repeat pattern --- .../recurring_todos/daily_repeat_pattern.rb | 5 +++-- config/locales/en.yml | 3 ++- .../daily_repeat_pattern_test.rb | 17 ++++++++++++----- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/app/models/recurring_todos/daily_repeat_pattern.rb b/app/models/recurring_todos/daily_repeat_pattern.rb index 4e94da55..17dd00f0 100644 --- a/app/models/recurring_todos/daily_repeat_pattern.rb +++ b/app/models/recurring_todos/daily_repeat_pattern.rb @@ -18,7 +18,7 @@ module RecurringTodos if only_work_days? I18n.t("todos.recurrence.pattern.on_work_days") elsif every_x_days > 1 - I18n.t("todos.recurrence.pattern.every_n", :n => every_x_days) + " " + I18n.t("common.days_midsentence.other") + I18n.t("todos.recurrence.pattern.every_n_days", :n => every_x_days) else I18n.t("todos.recurrence.pattern.every_day") end @@ -37,10 +37,11 @@ module RecurringTodos start = determine_start(previous, 1.day) if only_work_days? + # jump over weekend if necessary return start + 2.day if start.wday() == 6 # saturday return start + 1.day if start.wday() == 0 # sunday return start - else # every nth day; n = every_other1 + else # if there was no previous todo, do not add n: the first todo starts on # today or on start_from return previous == nil ? start : start+every_x_days.day-1.day diff --git a/config/locales/en.yml b/config/locales/en.yml index e68c3a90..90e9970d 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -623,13 +623,14 @@ en: - December third: third every_n: every %{n} + every_n_days: every %{n} days + every_day: every day on_day_n: on day %{n} second: second every_xth_day_of_every_n_months: every %{x} %{day} of every %{n_months} from: from weekly: weekly last: last - every_day: every day the_xth_day_of_month: the %{x} %{day} of %{month} times: for %{number} times on_work_days: on work days diff --git a/test/models/recurring_todos/daily_repeat_pattern_test.rb b/test/models/recurring_todos/daily_repeat_pattern_test.rb index 89a6348e..edfd4977 100644 --- a/test/models/recurring_todos/daily_repeat_pattern_test.rb +++ b/test/models/recurring_todos/daily_repeat_pattern_test.rb @@ -62,11 +62,18 @@ module RecurringTodos assert_equal @today+14.days, due_date end - def test_daily_work_days - assert_equal @monday, @every_workday.get_due_date(@friday) - assert_equal @monday, @every_workday.get_due_date(@saturday) - assert_equal @monday, @every_workday.get_due_date(@sunday) - assert_equal @tuesday, @every_workday.get_due_date(@monday) + def test_only_work_days_skips_weekend + assert_equal @tuesday, @every_workday.get_due_date(@monday), "should select next day if it is not in weekend" + + assert_equal @monday, @every_workday.get_due_date(@friday), "should select monday if it is in weekend" + assert_equal @monday, @every_workday.get_due_date(@saturday), "should select monday if it is in weekend" + assert_equal @monday, @every_workday.get_due_date(@sunday), "should select monday if it is in weekend" + end + + def test_every_x_days + assert_equal @tuesday, @every_day.get_due_date(@monday), "should select next day in middle week" + assert_equal @saturday, @every_day.get_due_date(@friday), "should select next day at end of week" + assert_equal @sunday, @every_day.get_due_date(@saturday), "should select next day in weekend" end end From 3cb18cd875f44b99bee8a813b7995ac7ac370aa4 Mon Sep 17 00:00:00 2001 From: Reinier Balt Date: Wed, 5 Mar 2014 09:37:34 +0100 Subject: [PATCH 21/24] move tests from recurring_todo_test to respective repeat_pattern_tests --- .../recurring_todos/monthly_repeat_pattern.rb | 38 +++++--- config/locales/en.yml | 1 + test/models/recurring_todo_test.rb | 89 ------------------- .../abstract_repeat_pattern_test.rb | 69 ++++++++------ .../monthly_repeat_pattern_test.rb | 31 +++++++ .../weekly_repeat_pattern_test.rb | 29 ++++++ .../yearly_repeat_pattern_test.rb | 36 ++++++++ 7 files changed, 163 insertions(+), 130 deletions(-) diff --git a/app/models/recurring_todos/monthly_repeat_pattern.rb b/app/models/recurring_todos/monthly_repeat_pattern.rb index ddb49c06..3fd6204f 100644 --- a/app/models/recurring_todos/monthly_repeat_pattern.rb +++ b/app/models/recurring_todos/monthly_repeat_pattern.rb @@ -44,20 +44,9 @@ module RecurringTodos def recurrence_pattern if recurrence_selector == 0 - on_day = " #{I18n.t('todos.recurrence.pattern.on_day_n', :n => every_x_day)}" - if every_xth_day(0) > 1 - I18n.t("todos.recurrence.pattern.every_n", :n => every_xth_day) + " " + I18n.t('common.months') + on_day - else - I18n.t("todos.recurrence.pattern.every_month") + on_day - end + recurrence_pattern_for_specific_day else - n_months = if get(:every_other2) > 1 - "#{get(:every_other2)} #{I18n.t('common.months')}" - else - I18n.t('common.month') - end - I18n.t('todos.recurrence.pattern.every_xth_day_of_every_n_months', - :x => xth(every_xth_day), :day => day_of_week_as_text(day_of_week), :n_months => n_months) + recurrence_pattern_for_relative_day_in_month end end @@ -123,6 +112,29 @@ module RecurringTodos end end + private + + def recurrence_pattern_for_specific_day + on_day = " #{I18n.t('todos.recurrence.pattern.on_day_n', :n => every_x_day)}" + if every_xth_day(0) > 1 + I18n.t("todos.recurrence.pattern.every_n_months", :n => every_xth_day) + on_day + else + I18n.t("todos.recurrence.pattern.every_month") + on_day + end + end + + def recurrence_pattern_for_relative_day_in_month + n_months = if every_x_month2 > 1 + "#{every_x_month2} #{I18n.t('common.months')}" + else + I18n.t('common.month') + end + I18n.t('todos.recurrence.pattern.every_xth_day_of_every_n_months', + x: xth(every_xth_day), + day: day_of_week_as_text(day_of_week), + n_months: n_months) + end + end end \ No newline at end of file diff --git a/config/locales/en.yml b/config/locales/en.yml index 90e9970d..987ae205 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -624,6 +624,7 @@ en: third: third every_n: every %{n} every_n_days: every %{n} days + every_n_months: every %{n} months every_day: every day on_day_n: on day %{n} second: second diff --git a/test/models/recurring_todo_test.rb b/test/models/recurring_todo_test.rb index c9ea6703..7ac14610 100644 --- a/test/models/recurring_todo_test.rb +++ b/test/models/recurring_todo_test.rb @@ -58,95 +58,6 @@ class RecurringTodoTest < ActiveSupport::TestCase # weekly/monthly/yearly end - def test_weekly_pattern - assert_equal true, @weekly_every_day.continues_recurring?(nil) - - due_date = @weekly_every_day.get_due_date(@sunday) - assert_equal @monday, due_date - - # saturday is last day in week, so the next date should be sunday + n-1 weeks - # n-1 because sunday is already in the next week - @weekly_every_day.every_other1 = 3 - due_date = @weekly_every_day.get_due_date(@saturday) - assert_equal @sunday + 2.weeks, due_date - - # remove tuesday and wednesday - @weekly_every_day.every_day = 'sm tfs' - due_date = @weekly_every_day.get_due_date(@monday) - assert_equal @thursday, due_date - - @weekly_every_day.every_other1 = 1 - @weekly_every_day.every_day = ' tw ' - due_date = @weekly_every_day.get_due_date(@tuesday) - assert_equal @wednesday, due_date - due_date = @weekly_every_day.get_due_date(@wednesday) - assert_equal @tuesday+1.week, due_date - - @weekly_every_day.every_day = ' s' - due_date = @weekly_every_day.get_due_date(@sunday) - assert_equal @saturday+1.week, due_date - end - - def test_monthly_pattern - due_date = @monthly_every_last_friday.get_due_date(@sunday) - assert_equal Time.zone.local(2008,6,27), due_date - - friday_is_last_day_of_month = Time.zone.local(2008,10,31) - due_date = @monthly_every_last_friday.get_due_date(friday_is_last_day_of_month-1.day ) - assert_equal friday_is_last_day_of_month , due_date - - @monthly_every_third_friday = @monthly_every_last_friday - @monthly_every_third_friday.every_other3=3 #third - due_date = @monthly_every_last_friday.get_due_date(@sunday) # june 8th 2008 - assert_equal Time.zone.local(2008, 6, 20), due_date - # set date past third friday of this month - due_date = @monthly_every_last_friday.get_due_date(Time.zone.local(2008,6,21)) # june 21th 2008 - assert_equal Time.zone.local(2008, 8, 15), due_date # every 2 months, so aug - - @monthly = @monthly_every_last_friday - @monthly.recurrence_selector=0 - @monthly.every_other1 = 8 # every 8th day of the month - @monthly.every_other2 = 2 # every 2 months - - due_date = @monthly.get_due_date(@saturday) # june 7th - assert_equal @sunday, due_date # june 8th - - due_date = @monthly.get_due_date(@sunday) # june 8th - assert_equal Time.zone.local(2008,8,8), due_date # aug 8th - end - - def test_yearly_pattern - # beginning of same year - due_date = @yearly.get_due_date(Time.zone.local(2008,2,10)) # feb 10th - assert_equal @sunday, due_date # june 8th - - # same month, previous date - due_date = @yearly.get_due_date(@saturday) # june 7th - show_from_date = @yearly.get_show_from_date(@saturday) # june 7th - assert_equal @sunday, due_date # june 8th - assert_equal @sunday-5.days, show_from_date - - # same month, day after - due_date = @yearly.get_due_date(@monday) # june 9th - assert_equal Time.zone.local(2009,6,8), due_date # june 8th next year - # very overdue - due_date = @yearly.get_due_date(@monday+5.months-2.days) # november 7 - assert_equal Time.zone.local(2009,6,8), due_date # june 8th next year - - @yearly.recurrence_selector = 1 - @yearly.every_other3 = 2 # second - @yearly.every_count = 3 # wednesday - # beginning of same year - due_date = @yearly.get_due_date(Time.zone.local(2008,2,10)) # feb 10th - assert_equal Time.zone.local(2008,6,11), due_date # june 11th - # same month, before second wednesday - due_date = @yearly.get_due_date(@saturday) # june 7th - assert_equal Time.zone.local(2008,6,11), due_date # june 11th - # same month, after second wednesday - due_date = @yearly.get_due_date(Time.zone.local(2008,6,12)) # june 7th - assert_equal Time.zone.local(2009,6,10), due_date # june 10th - end - def test_next_todo_without_previous_todo # test handling of nil as previous # diff --git a/test/models/recurring_todos/abstract_repeat_pattern_test.rb b/test/models/recurring_todos/abstract_repeat_pattern_test.rb index 748b0e94..fbf8a78a 100644 --- a/test/models/recurring_todos/abstract_repeat_pattern_test.rb +++ b/test/models/recurring_todos/abstract_repeat_pattern_test.rb @@ -19,73 +19,57 @@ module RecurringTodos def test_validation_on_due_date attributes = { - 'recurring_period' => 'weekly', - 'recurring_target' => 'due_date', - 'description' => 'a repeating todo', # generic - 'weekly_return_monday' => 'm', # weekly specific - 'ends_on' => 'ends_on_end_date', - 'end_date' => Time.zone.now + 1.week, - 'context_id' => @admin.contexts.first.id, - 'start_from' => Time.zone.now - 1.week, 'weekly_every_x_week' => 1, + 'weekly_return_monday' => 'm', # weekly specific } - pattern = create_pattern(attributes) + pattern = create_recurring_todo(attributes) assert !pattern.valid?, "should fail because show_always and show_from_delta are not there" attributes['recurring_show_always'] = false - pattern = create_pattern(attributes) + pattern = create_recurring_todo(attributes) assert !pattern.valid?, "should fail because show_from_delta is not there" attributes[:recurring_show_days_before] = 5 - pattern = create_pattern(attributes) + pattern = create_recurring_todo(attributes) assert pattern.valid?, "should be valid:" + pattern.errors.full_messages.to_s end def test_validation_on_start_date attributes = { - 'recurring_period' => 'weekly', - 'recurring_target' => 'due_date', - 'description' => 'a repeating todo', # generic - 'weekly_return_monday' => 'm', # weekly specific - 'ends_on' => 'ends_on_end_date', - 'context_id' => @admin.contexts.first.id, - 'end_date' => Time.zone.now + 1.week, 'weekly_every_x_week' => 1, + 'weekly_return_monday' => 'm', # weekly specific 'recurring_show_always' => false, 'recurring_show_days_before' => 5, + 'start_from' => nil } - pattern = create_pattern(attributes) + pattern = create_recurring_todo(attributes) assert !pattern.valid?, "should be not valid because start_from is empty" attributes['start_from'] = Time.zone.now - 1.week - pattern = create_pattern(attributes) + pattern = create_recurring_todo(attributes) assert pattern.valid?, "should be valid: " + pattern.errors.full_messages.to_s end def test_validation_on_end_date attributes = { - 'recurring_period' => 'weekly', - 'recurring_target' => 'due_date', - 'description' => 'a repeating todo', # generic 'weekly_return_monday' => 'm', # weekly specific 'ends_on' => 'invalid_value', - 'context_id' => @admin.contexts.first.id, - 'start_from' => Time.zone.now - 1.week, 'weekly_every_x_week' => 1, 'recurring_show_always' => false, 'recurring_show_days_before' => 5, } - pattern = create_pattern(attributes) + pattern = create_recurring_todo(attributes) assert !pattern.valid? attributes['ends_on']='ends_on_end_date' - pattern = create_pattern(attributes) + attributes['end_date']=nil + pattern = create_recurring_todo(attributes) assert !pattern.valid?, "should not be valid, because end_date is not supplied" attributes['end_date']= Time.zone.now + 1.week - pattern = create_pattern(attributes) + pattern = create_recurring_todo(attributes) assert pattern.valid?, "should be valid" end @@ -99,6 +83,23 @@ module RecurringTodos assert_equal false, rt.continues_recurring?(@in_four_days) end + def test_continues_recurring + rt = recurring_todos(:call_bill_gates_every_day) + assert rt.continues_recurring?(Time.zone.now), "should not end" + + rt.end_date = Time.zone.now - 1.day + rt.ends_on = 'ends_on_end_date' + assert !rt.continues_recurring?(Time.zone.now), "should end because end_date is in the past" + + rt.reload # reset + rt.number_of_occurences = 2 + rt.occurences_count = 1 + assert rt.continues_recurring?(Time.zone.now), "should continue since there still may come occurences" + + rt.occurences_count = 2 + assert !rt.continues_recurring?(Time.zone.now), "should end since all occurences are there" + end + private def create_pattern(attributes) @@ -107,6 +108,18 @@ module RecurringTodos builder.pattern end + def create_recurring_todo(attributes) + create_pattern(attributes.reverse_merge({ + 'recurring_period' => 'weekly', + 'recurring_target' => 'due_date', + 'description' => 'a repeating todo', # generic + 'ends_on' => 'ends_on_end_date', + 'end_date' => Time.zone.now + 1.week, + 'context_id' => @admin.contexts.first.id, + 'start_from' => Time.zone.now - 1.week, + })) + end + end end \ No newline at end of file diff --git a/test/models/recurring_todos/monthly_repeat_pattern_test.rb b/test/models/recurring_todos/monthly_repeat_pattern_test.rb index b3608074..5603108f 100644 --- a/test/models/recurring_todos/monthly_repeat_pattern_test.rb +++ b/test/models/recurring_todos/monthly_repeat_pattern_test.rb @@ -6,6 +6,7 @@ module RecurringTodos fixtures :users def setup + super @admin = users(:admin_user) end @@ -112,6 +113,36 @@ module RecurringTodos assert_equal "every month on day 1", rt.recurrence_pattern end + def test_monthly_pattern + @monthly_every_last_friday = recurring_todos(:check_with_bill_every_last_friday_of_month) + + due_date = @monthly_every_last_friday.get_due_date(@sunday) + assert_equal Time.zone.local(2008,6,27), due_date + + friday_is_last_day_of_month = Time.zone.local(2008,10,31) + due_date = @monthly_every_last_friday.get_due_date(friday_is_last_day_of_month-1.day ) + assert_equal friday_is_last_day_of_month , due_date + + @monthly_every_third_friday = @monthly_every_last_friday + @monthly_every_third_friday.every_other3=3 #third + due_date = @monthly_every_last_friday.get_due_date(@sunday) # june 8th 2008 + assert_equal Time.zone.local(2008, 6, 20), due_date + # set date past third friday of this month + due_date = @monthly_every_last_friday.get_due_date(Time.zone.local(2008,6,21)) # june 21th 2008 + assert_equal Time.zone.local(2008, 8, 15), due_date # every 2 months, so aug + + @monthly = @monthly_every_last_friday + @monthly.recurrence_selector=0 + @monthly.every_other1 = 8 # every 8th day of the month + @monthly.every_other2 = 2 # every 2 months + + due_date = @monthly.get_due_date(@saturday) # june 7th + assert_equal @sunday, due_date # june 8th + + due_date = @monthly.get_due_date(@sunday) # june 8th + assert_equal Time.zone.local(2008,8,8), due_date # aug 8th + end + end end \ No newline at end of file diff --git a/test/models/recurring_todos/weekly_repeat_pattern_test.rb b/test/models/recurring_todos/weekly_repeat_pattern_test.rb index c1904af1..87f87397 100644 --- a/test/models/recurring_todos/weekly_repeat_pattern_test.rb +++ b/test/models/recurring_todos/weekly_repeat_pattern_test.rb @@ -6,6 +6,7 @@ module RecurringTodos fixtures :users def setup + super @admin = users(:admin_user) end @@ -48,6 +49,34 @@ module RecurringTodos assert_equal "weekly", rt.recurrence_pattern end + def test_weekly_pattern + rt = recurring_todos(:call_bill_gates_every_week) + due_date = rt.get_due_date(@sunday) + assert_equal @monday, due_date + + # saturday is last day in week, so the next date should be sunday + n-1 weeks + # n-1 because sunday is already in the next week + rt.every_other1 = 3 + due_date = rt.get_due_date(@saturday) + assert_equal @sunday + 2.weeks, due_date + + # remove tuesday and wednesday + rt.every_day = 'sm tfs' + due_date = rt.get_due_date(@monday) + assert_equal @thursday, due_date + + rt.every_other1 = 1 + rt.every_day = ' tw ' + due_date = rt.get_due_date(@tuesday) + assert_equal @wednesday, due_date + due_date = rt.get_due_date(@wednesday) + assert_equal @tuesday+1.week, due_date + + rt.every_day = ' s' + due_date = rt.get_due_date(@sunday) + assert_equal @saturday+1.week, due_date + end + end end \ No newline at end of file diff --git a/test/models/recurring_todos/yearly_repeat_pattern_test.rb b/test/models/recurring_todos/yearly_repeat_pattern_test.rb index a08f88d9..0514e655 100644 --- a/test/models/recurring_todos/yearly_repeat_pattern_test.rb +++ b/test/models/recurring_todos/yearly_repeat_pattern_test.rb @@ -6,6 +6,7 @@ module RecurringTodos fixtures :users def setup + super @admin = users(:admin_user) end @@ -74,6 +75,41 @@ module RecurringTodos assert_equal "every year on the third wednesday of June", rt.recurrence_pattern end + def test_yearly_pattern + @yearly = recurring_todos(:birthday_reinier) + + # beginning of same year + due_date = @yearly.get_due_date(Time.zone.local(2008,2,10)) # feb 10th + assert_equal @sunday, due_date # june 8th + + # same month, previous date + due_date = @yearly.get_due_date(@saturday) # june 7th + show_from_date = @yearly.get_show_from_date(@saturday) # june 7th + assert_equal @sunday, due_date # june 8th + assert_equal @sunday-5.days, show_from_date + + # same month, day after + due_date = @yearly.get_due_date(@monday) # june 9th + assert_equal Time.zone.local(2009,6,8), due_date # june 8th next year + # very overdue + due_date = @yearly.get_due_date(@monday+5.months-2.days) # november 7 + assert_equal Time.zone.local(2009,6,8), due_date # june 8th next year + + @yearly.recurrence_selector = 1 + @yearly.every_other3 = 2 # second + @yearly.every_count = 3 # wednesday + # beginning of same year + due_date = @yearly.get_due_date(Time.zone.local(2008,2,10)) # feb 10th + assert_equal Time.zone.local(2008,6,11), due_date # june 11th + # same month, before second wednesday + due_date = @yearly.get_due_date(@saturday) # june 7th + assert_equal Time.zone.local(2008,6,11), due_date # june 11th + # same month, after second wednesday + due_date = @yearly.get_due_date(Time.zone.local(2008,6,12)) # june 7th + assert_equal Time.zone.local(2009,6,10), due_date # june 10th + end + + end end \ No newline at end of file From ed039d4c4a6d28bde8e34e1609697f5a89b65ee9 Mon Sep 17 00:00:00 2001 From: Reinier Balt Date: Mon, 31 Mar 2014 11:09:00 +0200 Subject: [PATCH 22/24] small refactorings and add some tests --- .../abstract_repeat_pattern.rb | 13 +++- .../recurring_todos/monthly_repeat_pattern.rb | 78 ++++++++++--------- .../recurring_todos/weekly_repeat_pattern.rb | 36 +++++---- .../recurring_todos/yearly_repeat_pattern.rb | 60 ++++++++------ .../abstract_repeat_pattern_test.rb | 28 ++++++- 5 files changed, 133 insertions(+), 82 deletions(-) diff --git a/app/models/recurring_todos/abstract_repeat_pattern.rb b/app/models/recurring_todos/abstract_repeat_pattern.rb index 9e6dcd3a..e678c9e4 100644 --- a/app/models/recurring_todos/abstract_repeat_pattern.rb +++ b/app/models/recurring_todos/abstract_repeat_pattern.rb @@ -164,8 +164,10 @@ module RecurringTodos private - # Determine start date to calculate next date for recurring todo - # offset needs to be 1.day for daily patterns + # Determine start date to calculate next date for recurring todo which + # takes start_from and previous into account. + # offset needs to be 1.day for daily patterns or the start will be the + # same day as the previous def determine_start(previous, offset=0.day) start = self.start_from || NullTime.new now = Time.zone.now @@ -179,9 +181,14 @@ module RecurringTodos end end + # Example: get 3rd (x) wednesday (weekday) of december (month) 2014 (year) + # 5th means last, so it will return the 4th if there is no 5th def get_xth_day_of_month(x, weekday, month, year) + raise "Weekday should be between 0 and 6 with 0=sunday. You supplied #{weekday}" unless (0..6).include?(weekday) + raise "x should be 1-4 for first-fourth or 5 for last. You supplied #{x}" unless (0..5).include?(x) + if x == 5 - # last -> count backwards. use UTC to avoid strange timezone oddities + # 5 means last -> count backwards. use UTC to avoid strange timezone oddities # where last_day -= 1.day seems to shift tz+0100 to tz+0000 last_day = Time.utc(year, month, Time.days_in_month(month)) while last_day.wday != weekday diff --git a/app/models/recurring_todos/monthly_repeat_pattern.rb b/app/models/recurring_todos/monthly_repeat_pattern.rb index 3fd6204f..04d1c8f6 100644 --- a/app/models/recurring_todos/monthly_repeat_pattern.rb +++ b/app/models/recurring_todos/monthly_repeat_pattern.rb @@ -66,54 +66,56 @@ module RecurringTodos def get_next_date(previous) start = determine_start(previous) - day = every_x_day n = get(:every_other2) case recurrence_selector when 0 # specific day of the month - if (previous && start.mday >= day) || (previous.nil? && start.mday > day) - # there is no next day n in this month, search in next month - # - # start += n.months - # - # The above seems to not work. Fiddle with timezone. Looks like we hit a - # bug in rails here where 2008-12-01 +0100 plus 1.month becomes - # 2008-12-31 +0100. For now, just calculate in UTC and convert back to - # local timezone. - # - # TODO: recheck if future rails versions have this problem too - start = Time.utc(start.year, start.month, start.day)+n.months - start = Time.zone.local(start.year, start.month, start.day) - - # go back to day - end - Time.zone.local(start.year, start.month, day) - + return find_specific_day_of_month(previous, start, n) when 1 # relative weekday of a month - the_next = get_xth_day_of_month(every_xth_day, day_of_week, start.month, start.year) - if the_next.nil? || the_next <= start - # the nth day is already passed in this month, go to next month and try - # again - - # fiddle with timezone. Looks like we hit a bug in rails here where - # 2008-12-01 +0100 plus 1.month becomes 2008-12-31 +0100. For now, just - # calculate in UTC and convert back to local timezone. - # TODO: recheck if future rails versions have this problem too - the_next = Time.utc(the_next.year, the_next.month, the_next.day)+n.months - the_next = Time.zone.local(the_next.year, the_next.month, the_next.day) - - # TODO: if there is still no match, start will be set to nil. if we ever - # support 5th day of the month, we need to handle this case - the_next = get_xth_day_of_month(every_xth_day, day_of_week, the_next.month, the_next.year) - end - the_next - else - raise Exception.new, "unknown monthly recurrence selection (#{self.recurrence_selector})" + return find_relative_day_of_month(start, n) end + nil end private + def find_specific_day_of_month(previous, start, n) + if (previous && start.mday >= every_x_day) || (previous.nil? && start.mday > every_x_day) + # there is no next day n in this month, search in next month + # + # start += n.months + # + # The above seems to not work. Fiddle with timezone. Looks like we hit a + # bug in rails here where 2008-12-01 +0100 plus 1.month becomes + # 2008-12-31 +0100. For now, just calculate in UTC and convert back to + # local timezone. + # + # TODO: recheck if future rails versions have this problem too + start = Time.utc(start.year, start.month, start.day)+n.months + end + Time.zone.local(start.year, start.month, every_x_day) + end + + def find_relative_day_of_month(start, n) + the_next = get_xth_day_of_month(every_xth_day, day_of_week, start.month, start.year) + if the_next.nil? || the_next <= start + # the nth day is already passed in this month, go to next month and try + # again + + # fiddle with timezone. Looks like we hit a bug in rails here where + # 2008-12-01 +0100 plus 1.month becomes 2008-12-31 +0100. For now, just + # calculate in UTC and convert back to local timezone. + # TODO: recheck if future rails versions have this problem too + the_next = Time.utc(the_next.year, the_next.month, the_next.day)+n.months + the_next = Time.zone.local(the_next.year, the_next.month, the_next.day) + + # TODO: if there is still no match, start will be set to nil. if we ever + # support 5th day of the month, we need to handle this case + the_next = get_xth_day_of_month(every_xth_day, day_of_week, the_next.month, the_next.year) + end + the_next + end + def recurrence_pattern_for_specific_day on_day = " #{I18n.t('todos.recurrence.pattern.on_day_n', :n => every_x_day)}" if every_xth_day(0) > 1 diff --git a/app/models/recurring_todos/weekly_repeat_pattern.rb b/app/models/recurring_todos/weekly_repeat_pattern.rb index 8dead7fa..4e36ad43 100644 --- a/app/models/recurring_todos/weekly_repeat_pattern.rb +++ b/app/models/recurring_todos/weekly_repeat_pattern.rb @@ -36,22 +36,7 @@ module RecurringTodos end def get_next_date(previous) - # determine start - if previous.nil? - start = start_from.nil? ? Time.zone.now : self.start_from - else - start = previous + 1.day - if start.wday() == 0 - # we went to a new week , go to the nth next week and find first match - # that week. Note that we already went into the next week, so -1 - start += (every_x_week-1).week - end - unless self.start_from.nil? - # check if the start_from date is later than previous. If so, use - # start_from as start to search for next date - start = self.start_from if self.start_from > previous - end - end + start = determine_start_date(previous) day = find_first_day_in_this_week(start) return day unless day == -1 @@ -68,6 +53,25 @@ module RecurringTodos private + def determine_start_date(previous) + if previous.nil? + return self.start_from || Time.zone.now + else + start = previous + 1.day + if start.wday() == 0 + # we went to a new week, go to the nth next week and find first match + # that week. Note that we already went into the next week, so -1 + start += (every_x_week-1).week + end + unless self.start_from.nil? + # check if the start_from date is later than previous. If so, use + # start_from as start to search for next date + start = self.start_from if self.start_from > previous + end + return start + end + end + def find_first_day_in_this_week(start) # check if there are any days left this week for the next todo start.wday().upto 6 do |i| diff --git a/app/models/recurring_todos/yearly_repeat_pattern.rb b/app/models/recurring_todos/yearly_repeat_pattern.rb index bc1c1772..041909c3 100644 --- a/app/models/recurring_todos/yearly_repeat_pattern.rb +++ b/app/models/recurring_todos/yearly_repeat_pattern.rb @@ -34,8 +34,7 @@ module RecurringTodos def recurrence_pattern if self.recurrence_selector == 0 - I18n.t("todos.recurrence.pattern.every_year_on", - :date => I18n.l(DateTime.new(Time.zone.now.year, month_of_year, every_x_day), :format => :month_day)) + I18n.t("todos.recurrence.pattern.every_year_on", :date => date_as_month_day) else I18n.t("todos.recurrence.pattern.every_year_on", :date => I18n.t("todos.recurrence.pattern.the_xth_day_of_month", @@ -63,34 +62,47 @@ module RecurringTodos def get_next_date(previous) start = determine_start(previous) - day = every_x_day month = get(:every_other2) case recurrence_selector when 0 # specific day of a specific month - if start.month > month || (start.month == month && start.day >= day) - # if there is no next month n and day m in this year, search in next - # year - start = Time.zone.local(start.year+1, month, 1) - else - # if there is a next month n, stay in this year - start = Time.zone.local(start.year, month, 1) - end - Time.zone.local(start.year, month, day) - + return get_specific_day_of_month(start, month) when 1 # relative weekday of a specific month - # if there is no next month n in this year, search in next year - the_next = start.month > month ? Time.zone.local(start.year+1, month, 1) : start - - # get the xth day of the month - the_next = get_xth_day_of_month(self.every_xth_day, day_of_week, month, the_next.year) - - # if the_next is before previous, we went back into the past, so try next - # year - the_next = get_xth_day_of_month(self.every_xth_day, day_of_week, month, start.year+1) if the_next <= start - - the_next + return get_relative_weekday_of_month(start, month) end + nil + end + + private + + def date_as_month_day + I18n.l(DateTime.new(Time.zone.now.year, month_of_year, every_x_day), :format => :month_day) + end + + def get_specific_day_of_month(start, month) + if start.month > month || (start.month == month && start.day >= every_x_day) + # if there is no next month n and day m in this year, search in next + # year + start = Time.zone.local(start.year+1, month, 1) + else + # if there is a next month n, stay in this year + start = Time.zone.local(start.year, month, 1) + end + Time.zone.local(start.year, month, every_x_day) + end + + def get_relative_weekday_of_month(start, month) + # if there is no next month n in this year, search in next year + the_next = start.month > month ? Time.zone.local(start.year+1, month, 1) : start + + # get the xth day of the month + the_next = get_xth_day_of_month(self.every_xth_day, day_of_week, month, the_next.year) + + # if the_next is before previous, we went back into the past, so try next + # year + the_next = get_xth_day_of_month(self.every_xth_day, day_of_week, month, start.year+1) if the_next <= start + + the_next end end diff --git a/test/models/recurring_todos/abstract_repeat_pattern_test.rb b/test/models/recurring_todos/abstract_repeat_pattern_test.rb index fbf8a78a..998eb83c 100644 --- a/test/models/recurring_todos/abstract_repeat_pattern_test.rb +++ b/test/models/recurring_todos/abstract_repeat_pattern_test.rb @@ -100,6 +100,32 @@ module RecurringTodos assert !rt.continues_recurring?(Time.zone.now), "should end since all occurences are there" end + def test_determine_start + Timecop.travel(2013,1,1) do + rt = create_recurring_todo + assert_equal "2013-01-01 00:00:00 UTC", rt.send(:determine_start, nil).to_s, "no previous date, use today" + assert_equal "2013-01-01 00:00:00 UTC", rt.send(:determine_start, nil, 1.day).to_s, "no previous date, use today without offset" + assert_equal "2013-01-02 00:00:00 UTC", rt.send(:determine_start, Time.zone.now, 1.day).to_s, "use previous date and offset" + end + end + + def test_xth_day_of_month + rt = create_recurring_todo + + # march 2014 has 5 saturdays, the last will return the 5th + assert_equal "2014-03-01 00:00:00 UTC", rt.send(:get_xth_day_of_month, 1, 6, 3, 2014).to_s + assert_equal "2014-03-22 00:00:00 UTC", rt.send(:get_xth_day_of_month, 4, 6, 3, 2014).to_s + assert_equal "2014-03-29 00:00:00 UTC", rt.send(:get_xth_day_of_month, 5, 6, 3, 2014).to_s + + # march 2014 has 4 fridays, the last will return the 4th + assert_equal "2014-03-07 00:00:00 UTC", rt.send(:get_xth_day_of_month, 1, 5, 3, 2014).to_s + assert_equal "2014-03-28 00:00:00 UTC", rt.send(:get_xth_day_of_month, 4, 5, 3, 2014).to_s + assert_equal "2014-03-28 00:00:00 UTC", rt.send(:get_xth_day_of_month, 5, 5, 3, 2014).to_s + + assert_raise(RuntimeError, "should check on valid weekdays"){ rt.send(:get_xth_day_of_month, 5, 9, 3, 2014) } + assert_raise(RuntimeError, "should check on valid count x"){ rt.send(:get_xth_day_of_month, 6, 5, 3, 2014) } + end + private def create_pattern(attributes) @@ -108,7 +134,7 @@ module RecurringTodos builder.pattern end - def create_recurring_todo(attributes) + def create_recurring_todo(attributes={}) create_pattern(attributes.reverse_merge({ 'recurring_period' => 'weekly', 'recurring_target' => 'due_date', From 0839765e7b3f5cf70af835c5931f9b37bbd64916 Mon Sep 17 00:00:00 2001 From: Reinier Balt Date: Mon, 31 Mar 2014 11:22:23 +0200 Subject: [PATCH 23/24] smaal refactoring and improvement of date handling in test --- .../abstract_repeat_pattern.rb | 52 +++++++++++-------- .../abstract_repeat_pattern_test.rb | 18 +++---- 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/app/models/recurring_todos/abstract_repeat_pattern.rb b/app/models/recurring_todos/abstract_repeat_pattern.rb index e678c9e4..45332d99 100644 --- a/app/models/recurring_todos/abstract_repeat_pattern.rb +++ b/app/models/recurring_todos/abstract_repeat_pattern.rb @@ -188,31 +188,39 @@ module RecurringTodos raise "x should be 1-4 for first-fourth or 5 for last. You supplied #{x}" unless (0..5).include?(x) if x == 5 - # 5 means last -> count backwards. use UTC to avoid strange timezone oddities - # where last_day -= 1.day seems to shift tz+0100 to tz+0000 - last_day = Time.utc(year, month, Time.days_in_month(month)) - while last_day.wday != weekday - last_day -= 1.day - end - # convert back to local timezone - Time.zone.local(last_day.year, last_day.month, last_day.day) + return find_last_day_x_of_month(weekday, month, year) else - # 1-4th -> count upwards last -> count backwards. use UTC to avoid strange - # timezone oddities where last_day -= 1.day seems to shift tz+0100 to - # tz+0000 - start = Time.utc(year,month,1) - n = x - while n > 0 - while start.wday() != weekday - start+= 1.day - end - n -= 1 - start += 1.day unless n==0 - end - # convert back to local timezone - Time.zone.local(start.year, start.month, start.day) + return find_xth_day_of_month(x, weekday, month, year) end end + def find_last_day_x_of_month(weekday, month, year) + # count backwards. use UTC to avoid strange timezone oddities + # where last_day -= 1.day seems to shift tz+0100 to tz+0000 + last_day = Time.utc(year, month, Time.days_in_month(month)) + while last_day.wday != weekday + last_day -= 1.day + end + # convert back to local timezone + Time.zone.local(last_day.year, last_day.month, last_day.day) + end + + def find_xth_day_of_month(x, weekday, month, year) + # 1-4th -> count upwards last -> count backwards. use UTC to avoid strange + # timezone oddities where last_day -= 1.day seems to shift tz+0100 to + # tz+0000 + start = Time.utc(year,month,1) + n = x + while n > 0 + while start.wday() != weekday + start+= 1.day + end + n -= 1 + start+= 1.day unless n==0 + end + # convert back to local timezone + Time.zone.local(start.year, start.month, start.day) + end + end end \ No newline at end of file diff --git a/test/models/recurring_todos/abstract_repeat_pattern_test.rb b/test/models/recurring_todos/abstract_repeat_pattern_test.rb index 998eb83c..d0bcf98b 100644 --- a/test/models/recurring_todos/abstract_repeat_pattern_test.rb +++ b/test/models/recurring_todos/abstract_repeat_pattern_test.rb @@ -103,9 +103,9 @@ module RecurringTodos def test_determine_start Timecop.travel(2013,1,1) do rt = create_recurring_todo - assert_equal "2013-01-01 00:00:00 UTC", rt.send(:determine_start, nil).to_s, "no previous date, use today" - assert_equal "2013-01-01 00:00:00 UTC", rt.send(:determine_start, nil, 1.day).to_s, "no previous date, use today without offset" - assert_equal "2013-01-02 00:00:00 UTC", rt.send(:determine_start, Time.zone.now, 1.day).to_s, "use previous date and offset" + assert_equal "2013-01-01 00:00:00", rt.send(:determine_start, nil).to_s(:db), "no previous date, use today" + assert_equal "2013-01-01 00:00:00", rt.send(:determine_start, nil, 1.day).to_s(:db), "no previous date, use today without offset" + assert_equal "2013-01-02 00:00:00", rt.send(:determine_start, Time.zone.now, 1.day).to_s(:db), "use previous date and offset" end end @@ -113,14 +113,14 @@ module RecurringTodos rt = create_recurring_todo # march 2014 has 5 saturdays, the last will return the 5th - assert_equal "2014-03-01 00:00:00 UTC", rt.send(:get_xth_day_of_month, 1, 6, 3, 2014).to_s - assert_equal "2014-03-22 00:00:00 UTC", rt.send(:get_xth_day_of_month, 4, 6, 3, 2014).to_s - assert_equal "2014-03-29 00:00:00 UTC", rt.send(:get_xth_day_of_month, 5, 6, 3, 2014).to_s + assert_equal "2014-03-01 00:00:00", rt.send(:get_xth_day_of_month, 1, 6, 3, 2014).to_s(:db) + assert_equal "2014-03-22 00:00:00", rt.send(:get_xth_day_of_month, 4, 6, 3, 2014).to_s(:db) + assert_equal "2014-03-29 00:00:00", rt.send(:get_xth_day_of_month, 5, 6, 3, 2014).to_s(:db) # march 2014 has 4 fridays, the last will return the 4th - assert_equal "2014-03-07 00:00:00 UTC", rt.send(:get_xth_day_of_month, 1, 5, 3, 2014).to_s - assert_equal "2014-03-28 00:00:00 UTC", rt.send(:get_xth_day_of_month, 4, 5, 3, 2014).to_s - assert_equal "2014-03-28 00:00:00 UTC", rt.send(:get_xth_day_of_month, 5, 5, 3, 2014).to_s + assert_equal "2014-03-07 00:00:00", rt.send(:get_xth_day_of_month, 1, 5, 3, 2014).to_s(:db) + assert_equal "2014-03-28 00:00:00", rt.send(:get_xth_day_of_month, 4, 5, 3, 2014).to_s(:db) + assert_equal "2014-03-28 00:00:00", rt.send(:get_xth_day_of_month, 5, 5, 3, 2014).to_s(:db) assert_raise(RuntimeError, "should check on valid weekdays"){ rt.send(:get_xth_day_of_month, 5, 9, 3, 2014) } assert_raise(RuntimeError, "should check on valid count x"){ rt.send(:get_xth_day_of_month, 6, 5, 3, 2014) } From 3c737ea2943f35f6efd6797518ab63f822c64236 Mon Sep 17 00:00:00 2001 From: Dan Rice Date: Wed, 2 Apr 2014 13:13:16 -0400 Subject: [PATCH 24/24] Update to latest Tolk which doesn't depend on protected_attributes Fixes #1455 --- Gemfile | 1 - Gemfile.lock | 5 +---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/Gemfile b/Gemfile index a44705a1..4a2bffd0 100644 --- a/Gemfile +++ b/Gemfile @@ -45,7 +45,6 @@ gem 'bcrypt-ruby', '~> 3.0.0' group :development do gem "yard" gem "tolk", git: 'https://github.com/tolk/tolk' - gem 'protected_attributes' # needed for tolk. remove when tolk updates gem "bullet" gem "rack-mini-profiler" end diff --git a/Gemfile.lock b/Gemfile.lock index 324f5ae7..8bc8c494 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -16,7 +16,7 @@ GIT GIT remote: https://github.com/tolk/tolk - revision: da2888c181493027a68f04ba7e2b9060883cc464 + revision: 06f99b3b747f2f8f7297fa35ec9c9d5f38eaada6 specs: tolk (1.4.00) safe_yaml (~> 0.8) @@ -128,8 +128,6 @@ GEM nokogiri (1.6.1) mini_portile (~> 0.5.0) polyglot (0.3.4) - protected_attributes (1.0.5) - activemodel (>= 4.0.1, < 5.0) rack (1.5.2) rack-mini-profiler (0.9.0) rack (>= 1.1.3) @@ -232,7 +230,6 @@ DEPENDENCIES json mocha mysql2 - protected_attributes rack-mini-profiler rails (~> 4.0.0) rails_autolink