mirror of
https://github.com/TracksApp/tracks.git
synced 2026-02-25 00:24:07 +01:00
Merge branch 'master' into new-gui
Conflicts: Gemfile.lock
This commit is contained in:
commit
fdd4c267b3
39 changed files with 2824 additions and 1022 deletions
1
Gemfile
1
Gemfile
|
|
@ -52,7 +52,6 @@ gem 'turbolinks'
|
||||||
group :development do
|
group :development do
|
||||||
gem "yard"
|
gem "yard"
|
||||||
gem "tolk", git: 'https://github.com/tolk/tolk'
|
gem "tolk", git: 'https://github.com/tolk/tolk'
|
||||||
gem 'protected_attributes' # needed for tolk. remove when tolk updates
|
|
||||||
gem "bullet"
|
gem "bullet"
|
||||||
gem "rack-mini-profiler"
|
gem "rack-mini-profiler"
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -133,8 +133,6 @@ GEM
|
||||||
nokogiri (1.6.1)
|
nokogiri (1.6.1)
|
||||||
mini_portile (~> 0.5.0)
|
mini_portile (~> 0.5.0)
|
||||||
polyglot (0.3.4)
|
polyglot (0.3.4)
|
||||||
protected_attributes (1.0.7)
|
|
||||||
activemodel (>= 4.0.1, < 5.0)
|
|
||||||
rack (1.5.2)
|
rack (1.5.2)
|
||||||
rack-mini-profiler (0.9.1)
|
rack-mini-profiler (0.9.1)
|
||||||
rack (>= 1.1.3)
|
rack (>= 1.1.3)
|
||||||
|
|
@ -245,7 +243,6 @@ DEPENDENCIES
|
||||||
mocha
|
mocha
|
||||||
mousetrap-rails
|
mousetrap-rails
|
||||||
mysql2
|
mysql2
|
||||||
protected_attributes
|
|
||||||
rack-mini-profiler
|
rack-mini-profiler
|
||||||
rails (~> 4.0.0)
|
rails (~> 4.0.0)
|
||||||
rails_autolink
|
rails_autolink
|
||||||
|
|
|
||||||
53
app/controllers/recurring_todos/form_helper.rb
Normal file
53
app/controllers/recurring_todos/form_helper.rb
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
module RecurringTodos
|
||||||
|
|
||||||
|
class FormHelper
|
||||||
|
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
# 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
|
||||||
|
|
||||||
|
end
|
||||||
|
|
@ -37,68 +37,18 @@ class RecurringTodosController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def edit
|
def edit
|
||||||
|
@form_helper = RecurringTodos::FormHelper.new(@recurring_todo)
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.js
|
format.js
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
# TODO: write tests for updating
|
updater = RecurringTodos::RecurringTodosBuilder.new(current_user, update_recurring_todo_params)
|
||||||
@recurring_todo.tag_with(params[:edit_recurring_todo_tag_list]) if params[:edit_recurring_todo_tag_list]
|
@saved = updater.update(@recurring_todo)
|
||||||
@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
|
@recurring_todo.reload
|
||||||
# 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'])
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.js
|
format.js
|
||||||
|
|
@ -106,42 +56,17 @@ class RecurringTodosController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
p = RecurringTodoCreateParamsHelper.new(params, recurring_todo_params)
|
builder = RecurringTodos::RecurringTodosBuilder.new(current_user, all_recurring_todo_params)
|
||||||
p.attributes['end_date']=parse_date_per_user_prefs(p.attributes['end_date'])
|
@saved = builder.save
|
||||||
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
|
|
||||||
|
|
||||||
if @saved
|
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
|
todo_saved = TodoFromRecurringTodo.new(current_user, @recurring_todo).create.nil? == false
|
||||||
if @todo_saved
|
|
||||||
@status_message += " / " + t('todos.new_related_todo_created_short')
|
@status_message =
|
||||||
else
|
t('todos.recurring_action_saved') + " / " +
|
||||||
@status_message += " / " + t('todos.new_related_todo_not_created_short')
|
t("todos.new_related_todo_#{todo_saved ? "" : "not_"}created_short")
|
||||||
end
|
|
||||||
@down_count = current_user.recurring_todos.active.count
|
@down_count = current_user.recurring_todos.active.count
|
||||||
@new_recurring_todo = RecurringTodo.new
|
@new_recurring_todo = RecurringTodo.new
|
||||||
else
|
else
|
||||||
|
|
@ -154,13 +79,10 @@ class RecurringTodosController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
|
@number_of_todos = @recurring_todo.todos.count
|
||||||
|
|
||||||
# remove all references to this recurring todo
|
# remove all references to this recurring todo
|
||||||
@todos = @recurring_todo.todos
|
@recurring_todo.clear_todos_association
|
||||||
@number_of_todos = @todos.size
|
|
||||||
@todos.each do |t|
|
|
||||||
t.recurring_todo_id = nil
|
|
||||||
t.save
|
|
||||||
end
|
|
||||||
|
|
||||||
# delete the recurring todo
|
# delete the recurring todo
|
||||||
@saved = @recurring_todo.destroy
|
@saved = @recurring_todo.destroy
|
||||||
|
|
@ -174,11 +96,10 @@ class RecurringTodosController < ApplicationController
|
||||||
format.html do
|
format.html do
|
||||||
if @saved
|
if @saved
|
||||||
notify :notice, t('todos.recurring_deleted_success')
|
notify :notice, t('todos.recurring_deleted_success')
|
||||||
redirect_to :action => 'index'
|
|
||||||
else
|
else
|
||||||
notify :error, t('todos.error_deleting_recurring', :description => @recurring_todo.description)
|
notify :error, t('todos.error_deleting_recurring', :description => @recurring_todo.description)
|
||||||
redirect_to :action => 'index'
|
|
||||||
end
|
end
|
||||||
|
redirect_to :action => 'index'
|
||||||
end
|
end
|
||||||
|
|
||||||
format.js do
|
format.js do
|
||||||
|
|
@ -217,58 +138,6 @@ class RecurringTodosController < ApplicationController
|
||||||
end
|
end
|
||||||
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
|
private
|
||||||
|
|
||||||
def recurring_todo_params
|
def recurring_todo_params
|
||||||
|
|
@ -278,7 +147,7 @@ class RecurringTodosController < ApplicationController
|
||||||
:ends_on, :end_date, :number_of_occurences, :occurences_count, :target,
|
:ends_on, :end_date, :number_of_occurences, :occurences_count, :target,
|
||||||
:show_from_delta, :recurring_period, :recurrence_selector, :every_other1,
|
:show_from_delta, :recurring_period, :recurrence_selector, :every_other1,
|
||||||
:every_other2, :every_other3, :every_day, :only_work_days, :every_count,
|
: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
|
# form attributes
|
||||||
:recurring_period, :daily_selector, :monthly_selector, :yearly_selector,
|
:recurring_period, :daily_selector, :monthly_selector, :yearly_selector,
|
||||||
:recurring_target, :daily_every_x_days, :monthly_day_of_week,
|
:recurring_target, :daily_every_x_days, :monthly_day_of_week,
|
||||||
|
|
@ -293,17 +162,50 @@ class RecurringTodosController < ApplicationController
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
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|
|
||||||
|
move_into_recurring_todo_param(params, target, source)
|
||||||
|
end
|
||||||
|
recurring_todo_params
|
||||||
|
end
|
||||||
|
|
||||||
|
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,
|
||||||
|
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
|
||||||
|
|
||||||
|
recurring_todo_params
|
||||||
|
end
|
||||||
|
|
||||||
|
def move_into_recurring_todo_param(params, target, source)
|
||||||
|
params[:recurring_todo][target] = params[source] unless params[source].blank?
|
||||||
|
end
|
||||||
|
|
||||||
def init
|
def init
|
||||||
@days_of_week = []
|
@days_of_week = (0..6).map{|i| [t('date.day_names')[i], i] }
|
||||||
0.upto 6 do |i|
|
@months_of_year = (1..12).map{|i| [t('date.month_names')[i], 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
|
|
||||||
|
|
||||||
@xth_day = [[t('common.first'),1],[t('common.second'),2],[t('common.third'),3],[t('common.fourth'),4],[t('common.last'),5]]
|
@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)
|
@projects = current_user.projects.includes(:default_context)
|
||||||
@contexts = current_user.contexts
|
@contexts = current_user.contexts
|
||||||
|
|
|
||||||
|
|
@ -136,7 +136,7 @@ class Project < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def age_in_days
|
def age_in_days
|
||||||
@age_in_days ||= ((Time.now.utc - created_at).to_i / 1.day) + 1
|
@age_in_days ||= (Time.current.to_date - created_at.to_date).to_i + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.import(filename, params, user)
|
def self.import(filename, params, user)
|
||||||
|
|
|
||||||
|
|
@ -29,86 +29,23 @@ class RecurringTodo < ActiveRecord::Base
|
||||||
validates_length_of :description, :maximum => 100
|
validates_length_of :description, :maximum => 100
|
||||||
validates_length_of :notes, :maximum => 60000, :allow_nil => true
|
validates_length_of :notes, :maximum => 60000, :allow_nil => true
|
||||||
|
|
||||||
validate :period_specific_validations
|
validate :period_validation
|
||||||
validate :starts_and_ends_on_validations
|
validate :pattern_specific_validations
|
||||||
validate :set_recurrence_on_validations
|
|
||||||
|
|
||||||
def period_specific_validations
|
def pattern_specific_validations
|
||||||
if %W[daily weekly monthly yearly].include?(recurring_period)
|
if pattern
|
||||||
self.send("validate_#{recurring_period}")
|
pattern.validate
|
||||||
else
|
else
|
||||||
errors.add(:recurring_period, "is an unknown recurrence pattern: '#{recurring_period}'")
|
errors[:recurring_todo] << "Invalid recurrence period '#{recurring_period}'"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_daily
|
def valid_period?
|
||||||
if (!only_work_days) && daily_every_x_days.blank?
|
%W[daily weekly monthly yearly].include?(recurring_period)
|
||||||
errors[:base] << "Every other nth day may not be empty for recurrence setting"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_weekly
|
def period_validation
|
||||||
if weekly_every_x_week.blank?
|
errors.add(:recurring_period, "is an unknown recurrence pattern: '#{recurring_period}'") unless valid_period?
|
||||||
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
|
|
||||||
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 '#{recurrence_target}'"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# the following recurrence patterns can be stored:
|
# the following recurrence patterns can be stored:
|
||||||
|
|
@ -133,262 +70,20 @@ class RecurringTodo < ActiveRecord::Base
|
||||||
# choosing between both options is done on recurrence_selector where 0 is
|
# choosing between both options is done on recurrence_selector where 0 is
|
||||||
# for first type and 1 for second type
|
# for first type and 1 for second type
|
||||||
|
|
||||||
# DAILY
|
def pattern
|
||||||
|
if valid_period?
|
||||||
def daily_selector=(selector)
|
@pattern = eval("RecurringTodos::#{recurring_period.capitalize}RepeatPattern.new(user)")
|
||||||
case selector
|
@pattern.build_from_recurring_todo(self)
|
||||||
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?
|
|
||||||
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'
|
|
||||||
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
|
|
||||||
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 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")
|
|
||||||
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
|
||||||
|
@pattern
|
||||||
end
|
end
|
||||||
|
|
||||||
def recurrence_pattern
|
def recurrence_pattern
|
||||||
return "invalid repeat pattern" if every_other1.nil?
|
pattern.recurrence_pattern
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def xth
|
def recurring_target_as_text
|
||||||
xth_day = [
|
pattern.recurring_target_as_text
|
||||||
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]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def starred?
|
def starred?
|
||||||
|
|
@ -396,214 +91,11 @@ class RecurringTodo < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_due_date(previous)
|
def get_due_date(previous)
|
||||||
case self.target
|
pattern.get_due_date(previous)
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_show_from_date(previous)
|
def get_show_from_date(previous)
|
||||||
case self.target
|
pattern.get_show_from_date(previous)
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def done?(end_date)
|
def done?(end_date)
|
||||||
|
|
@ -627,7 +119,7 @@ class RecurringTodo < ActiveRecord::Base
|
||||||
def remove_from_project!
|
def remove_from_project!
|
||||||
self.project = nil
|
self.project = nil
|
||||||
self.save
|
self.save
|
||||||
end
|
end
|
||||||
|
|
||||||
def clear_todos_association
|
def clear_todos_association
|
||||||
unless todos.nil?
|
unless todos.nil?
|
||||||
|
|
@ -643,29 +135,8 @@ class RecurringTodo < ActiveRecord::Base
|
||||||
self.save
|
self.save
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
def continues_recurring?(previous)
|
||||||
|
pattern.continues_recurring?(previous)
|
||||||
# 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
|
end
|
||||||
|
|
||||||
def find_first_day_in_this_week(start)
|
end
|
||||||
# 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
|
|
||||||
152
app/models/recurring_todos/abstract_recurring_todos_builder.rb
Normal file
152
app/models/recurring_todos/abstract_recurring_todos_builder.rb
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
module RecurringTodos
|
||||||
|
|
||||||
|
class AbstractRecurringTodosBuilder
|
||||||
|
|
||||||
|
attr_reader :mapped_attributes, :pattern
|
||||||
|
|
||||||
|
def initialize(user, attributes, pattern_class)
|
||||||
|
@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)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update(recurring_todo)
|
||||||
|
@recurring_todo = @pattern.update_recurring_todo(recurring_todo, @mapped_attributes)
|
||||||
|
save_recurring_todo
|
||||||
|
end
|
||||||
|
|
||||||
|
def save
|
||||||
|
build
|
||||||
|
save_recurring_todo
|
||||||
|
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
|
||||||
|
|
||||||
|
@recurring_todo
|
||||||
|
end
|
||||||
|
|
||||||
|
def attributes
|
||||||
|
@pattern.attributes
|
||||||
|
end
|
||||||
|
|
||||||
|
def errors
|
||||||
|
@recurring_todo.try(:errors)
|
||||||
|
end
|
||||||
|
|
||||||
|
def attributes_to_filter
|
||||||
|
raise Exception.new, "attributes_to_filter should be overridden"
|
||||||
|
end
|
||||||
|
|
||||||
|
def filter_attributes(attributes)
|
||||||
|
# get pattern independend attributes
|
||||||
|
filterred_attributes = filter_generic_attributes(attributes)
|
||||||
|
# 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[: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],
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
# changes name of key from source_key to key
|
||||||
|
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.selector_key_present?(key)
|
||||||
|
selector = @attributes[key]
|
||||||
|
|
||||||
|
raise Exception.new, "unknown recurrence selector pattern: '#{selector}'" unless valid_selector?(selector)
|
||||||
|
|
||||||
|
@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 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[:tag_list].blank? ? "" : attributes[:tag_list].strip
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
226
app/models/recurring_todos/abstract_repeat_pattern.rb
Normal file
226
app/models/recurring_todos/abstract_repeat_pattern.rb
Normal file
|
|
@ -0,0 +1,226 @@
|
||||||
|
module RecurringTodos
|
||||||
|
|
||||||
|
class AbstractRepeatPattern
|
||||||
|
|
||||||
|
attr_accessor :attributes
|
||||||
|
|
||||||
|
def initialize(user)
|
||||||
|
@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 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
|
||||||
|
|
||||||
|
def update_recurring_todo(recurring_todo, attribute_handler)
|
||||||
|
recurring_todo.assign_attributes(attribute_handler.safe_attributes)
|
||||||
|
recurring_todo
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_from_recurring_todo(recurring_todo)
|
||||||
|
@recurring_todo = recurring_todo
|
||||||
|
@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
|
||||||
|
|
||||||
|
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
|
||||||
|
validate_not_blank(start_from, "The start date needs to be filled in")
|
||||||
|
case ends_on
|
||||||
|
when 'ends_on_number_of_times'
|
||||||
|
validate_not_blank(number_of_occurences, "The number of recurrences needs to be filled in for 'Ends on'")
|
||||||
|
when "ends_on_end_date"
|
||||||
|
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
|
||||||
|
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'
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
def errors
|
||||||
|
@recurring_todo.errors
|
||||||
|
end
|
||||||
|
|
||||||
|
def get(attribute)
|
||||||
|
@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 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
|
||||||
|
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
|
||||||
|
|
||||||
|
# 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
|
||||||
|
return find_last_day_x_of_month(weekday, month, year)
|
||||||
|
else
|
||||||
|
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
|
||||||
35
app/models/recurring_todos/daily_recurring_todos_builder.rb
Normal file
35
app/models/recurring_todos/daily_recurring_todos_builder.rb
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
module RecurringTodos
|
||||||
|
|
||||||
|
class DailyRecurringTodosBuilder < AbstractRecurringTodosBuilder
|
||||||
|
attr_reader :recurring_todo, :pattern
|
||||||
|
|
||||||
|
def initialize(user, attributes)
|
||||||
|
super(user, attributes, DailyRepeatPattern)
|
||||||
|
end
|
||||||
|
|
||||||
|
def attributes_to_filter
|
||||||
|
%w{daily_selector daily_every_x_days}
|
||||||
|
end
|
||||||
|
|
||||||
|
def map_attributes(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)
|
||||||
|
{ 'daily_every_x_day' => false,
|
||||||
|
'daily_every_work_day' => true}[daily_selector]
|
||||||
|
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
|
||||||
52
app/models/recurring_todos/daily_repeat_pattern.rb
Normal file
52
app/models/recurring_todos/daily_repeat_pattern.rb
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
module RecurringTodos
|
||||||
|
|
||||||
|
class DailyRepeatPattern < AbstractRepeatPattern
|
||||||
|
|
||||||
|
def initialize(user)
|
||||||
|
super user
|
||||||
|
end
|
||||||
|
|
||||||
|
def every_x_days
|
||||||
|
get :every_other1
|
||||||
|
end
|
||||||
|
|
||||||
|
def only_work_days?
|
||||||
|
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_days", :n => every_x_days)
|
||||||
|
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?
|
||||||
|
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?
|
||||||
|
# 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
|
||||||
|
# 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
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
module RecurringTodos
|
||||||
|
|
||||||
|
class MonthlyRecurringTodosBuilder < AbstractRecurringTodosBuilder
|
||||||
|
|
||||||
|
def initialize(user, attributes)
|
||||||
|
super(user, attributes, MonthlyRepeatPattern)
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
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.set(:recurrence_selector, get_recurrence_selector)
|
||||||
|
|
||||||
|
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
|
||||||
|
@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
|
||||||
142
app/models/recurring_todos/monthly_repeat_pattern.rb
Normal file
142
app/models/recurring_todos/monthly_repeat_pattern.rb
Normal file
|
|
@ -0,0 +1,142 @@
|
||||||
|
module RecurringTodos
|
||||||
|
|
||||||
|
class MonthlyRepeatPattern < AbstractRepeatPattern
|
||||||
|
|
||||||
|
def initialize(user)
|
||||||
|
super user
|
||||||
|
end
|
||||||
|
|
||||||
|
def recurrence_selector
|
||||||
|
get :recurrence_selector
|
||||||
|
end
|
||||||
|
|
||||||
|
def every_x_day?
|
||||||
|
get(:recurrence_selector) == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def every_x_day
|
||||||
|
get(:every_other1)
|
||||||
|
end
|
||||||
|
|
||||||
|
def every_xth_day?
|
||||||
|
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 recurrence_pattern
|
||||||
|
if recurrence_selector == 0
|
||||||
|
recurrence_pattern_for_specific_day
|
||||||
|
else
|
||||||
|
recurrence_pattern_for_relative_day_in_month
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate
|
||||||
|
super
|
||||||
|
|
||||||
|
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 # '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
|
||||||
|
raise Exception.new, "unexpected value of recurrence selector '#{recurrence_selector}'"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_next_date(previous)
|
||||||
|
start = determine_start(previous)
|
||||||
|
n = get(:every_other2)
|
||||||
|
|
||||||
|
case recurrence_selector
|
||||||
|
when 0 # specific day of the month
|
||||||
|
return find_specific_day_of_month(previous, start, n)
|
||||||
|
when 1 # relative weekday of a month
|
||||||
|
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
|
||||||
|
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
|
||||||
78
app/models/recurring_todos/recurring_todos_builder.rb
Normal file
78
app/models/recurring_todos/recurring_todos_builder.rb
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
module RecurringTodos
|
||||||
|
|
||||||
|
class RecurringTodosBuilder
|
||||||
|
|
||||||
|
attr_reader :builder, :project, :context, :tag_list, :user
|
||||||
|
|
||||||
|
def initialize (user, attributes)
|
||||||
|
@user = user
|
||||||
|
@attributes = Tracks::AttributeHandler.new(@user, attributes)
|
||||||
|
|
||||||
|
parse_dates
|
||||||
|
parse_project
|
||||||
|
parse_context
|
||||||
|
|
||||||
|
@builder = create_builder(@attributes[:recurring_period])
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_builder(selector)
|
||||||
|
raise "Unknown recurrence selector in :recurring_period (#{selector})" unless valid_selector? selector
|
||||||
|
eval("RecurringTodos::#{selector.capitalize}RecurringTodosBuilder.new(@user, @attributes)")
|
||||||
|
end
|
||||||
|
|
||||||
|
def build
|
||||||
|
@builder.build
|
||||||
|
end
|
||||||
|
|
||||||
|
def update(recurring_todo)
|
||||||
|
@builder.update(recurring_todo)
|
||||||
|
end
|
||||||
|
|
||||||
|
def save
|
||||||
|
@builder.save_project if @new_project_created
|
||||||
|
@builder.save_context 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
|
||||||
|
|
||||||
|
def pattern
|
||||||
|
@builder.pattern
|
||||||
|
end
|
||||||
|
|
||||||
|
def errors
|
||||||
|
@builder.errors
|
||||||
|
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)
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_context
|
||||||
|
@context, @new_context_created = @attributes.parse_collection(:context, @user.contexts)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
42
app/models/recurring_todos/weekly_recurring_todos_builder.rb
Normal file
42
app/models/recurring_todos/weekly_recurring_todos_builder.rb
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
module RecurringTodos
|
||||||
|
|
||||||
|
class WeeklyRecurringTodosBuilder < AbstractRecurringTodosBuilder
|
||||||
|
|
||||||
|
def initialize(user, 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 do |day, index|
|
||||||
|
mapping = map_day(mapping, :every_day, "weekly_return_#{day}", index)
|
||||||
|
end
|
||||||
|
|
||||||
|
mapping
|
||||||
|
end
|
||||||
|
|
||||||
|
def map_day(mapping, key, source_key, index)
|
||||||
|
mapping.set_if_nil(key, ' ') # avoid nil
|
||||||
|
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.except(source_key)
|
||||||
|
end
|
||||||
|
|
||||||
|
def selector_key
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def valid_selector?(key)
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
86
app/models/recurring_todos/weekly_repeat_pattern.rb
Normal file
86
app/models/recurring_todos/weekly_repeat_pattern.rb
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
module RecurringTodos
|
||||||
|
|
||||||
|
class WeeklyRepeatPattern < AbstractRepeatPattern
|
||||||
|
|
||||||
|
def initialize(user)
|
||||||
|
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 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")
|
||||||
|
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
|
||||||
|
|
||||||
|
def get_next_date(previous)
|
||||||
|
start = determine_start_date(previous)
|
||||||
|
|
||||||
|
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 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|
|
||||||
|
return start + (i-start.wday()).days if on_xday(i)
|
||||||
|
end
|
||||||
|
-1
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
44
app/models/recurring_todos/yearly_recurring_todos_builder.rb
Normal file
44
app/models/recurring_todos/yearly_recurring_todos_builder.rb
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
module RecurringTodos
|
||||||
|
|
||||||
|
class YearlyRecurringTodosBuilder < AbstractRecurringTodosBuilder
|
||||||
|
|
||||||
|
def initialize(user, attributes)
|
||||||
|
super(user, attributes, YearlyRepeatPattern)
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
def map_attributes(mapping)
|
||||||
|
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.except(:yearly_month_of_year).except(:yearly_month_of_year2)
|
||||||
|
end
|
||||||
|
|
||||||
|
def selector_key
|
||||||
|
:yearly_selector
|
||||||
|
end
|
||||||
|
|
||||||
|
def valid_selector?(selector)
|
||||||
|
%w{yearly_every_x_day yearly_every_xth_day}.include?(selector.to_s)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_recurrence_selector
|
||||||
|
@selector=='yearly_every_x_day' ? 0 : 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_every_other2
|
||||||
|
{ 0 => :yearly_month_of_year, 1 => :yearly_month_of_year2 }[get_recurrence_selector]
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
109
app/models/recurring_todos/yearly_repeat_pattern.rb
Normal file
109
app/models/recurring_todos/yearly_repeat_pattern.rb
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
module RecurringTodos
|
||||||
|
|
||||||
|
class YearlyRepeatPattern < AbstractRepeatPattern
|
||||||
|
|
||||||
|
def initialize(user)
|
||||||
|
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 recurrence_pattern
|
||||||
|
if self.recurrence_selector == 0
|
||||||
|
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",
|
||||||
|
: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
|
||||||
|
when 0 # 'yearly_every_x_day'
|
||||||
|
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'
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_next_date(previous)
|
||||||
|
start = determine_start(previous)
|
||||||
|
month = get(:every_other2)
|
||||||
|
|
||||||
|
case recurrence_selector
|
||||||
|
when 0 # specific day of a specific month
|
||||||
|
return get_specific_day_of_month(start, month)
|
||||||
|
when 1 # relative weekday of a specific month
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
@ -5,91 +5,91 @@
|
||||||
<div id="recurring_todo_form_container">
|
<div id="recurring_todo_form_container">
|
||||||
<div id="recurring_todo">
|
<div id="recurring_todo">
|
||||||
<label for="recurring_todo_description"><%= Todo.human_attribute_name('description') %></label><%=
|
<label for="recurring_todo_description"><%= Todo.human_attribute_name('description') %></label><%=
|
||||||
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") -%>
|
||||||
|
|
||||||
<label for="recurring_todo_notes"><%= Todo.human_attribute_name('notes') %></label><%=
|
<label for="recurring_todo_notes"><%= Todo.human_attribute_name('notes') %></label><%=
|
||||||
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}) -%>
|
||||||
|
|
||||||
<label for="edit_recurring_todo_project_name"><%= Todo.human_attribute_name('project') %></label>
|
<label for="edit_recurring_todo_project_name"><%= Todo.human_attribute_name('project') %></label>
|
||||||
<input id="edit_recurring_todo_project_name" name="project_name" autocomplete="off" size="30" type="text" value="<%= @recurring_todo.project.nil? ? 'None' : @recurring_todo.project.name.gsub(/"/,""") %>" />
|
<input id="edit_recurring_todo_project_name" name="project_name" autocomplete="off" size="30" type="text" value="<%= @form_helper.project.nil? ? 'None' : @form_helper.project.name.gsub(/"/,""") %>" />
|
||||||
<div class="page_name_auto_complete" id="edit_project_list" style="display:none"></div>
|
<div class="page_name_auto_complete" id="edit_project_list" style="display:none"></div>
|
||||||
|
|
||||||
<label for="edit_recurring_todo_context_name"><%= Todo.human_attribute_name('context') %></label>
|
<label for="edit_recurring_todo_context_name"><%= Todo.human_attribute_name('context') %></label>
|
||||||
<input id="edit_recurring_todo_context_name" name="context_name" autocomplete="off" size="30" type="text" value="<%= @recurring_todo.context.name %>" />
|
<input id="edit_recurring_todo_context_name" name="context_name" autocomplete="off" size="30" type="text" value="<%= @form_helper.context.name %>" />
|
||||||
<div class="page_name_auto_complete" id="edit_context_list" style="display:none"></div>
|
<div class="page_name_auto_complete" id="edit_context_list" style="display:none"></div>
|
||||||
|
|
||||||
<label for="edit_recurring_todo_tag_list"><%= "#{Todo.human_attribute_name('tags')} #{t('shared.separate_tags_with_commas')}"%></label>
|
<label for="edit_recurring_todo_tag_list"><%= "#{Todo.human_attribute_name('tags')} #{t('shared.separate_tags_with_commas')}"%></label>
|
||||||
<%= 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 -%>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="recurring_edit_period_id">
|
<div id="recurring_edit_period_id">
|
||||||
<div id="recurring_edit_period">
|
<div id="recurring_edit_period">
|
||||||
<label><%= t('todos.recurrence_period') %></label><br/>
|
<label><%= t('todos.recurrence_period') %></label><br/>
|
||||||
<%= radio_button_tag('recurring_edit_todo[recurring_period]', 'daily', @recurring_todo.recurring_period == 'daily')%> <%= t('todos.recurrence.daily') %><br/>
|
<%= radio_button_tag('recurring_edit_todo[recurring_period]', 'daily', @form_helper.recurring_period == 'daily')%> <%= t('todos.recurrence.daily') %><br/>
|
||||||
<%= radio_button_tag('recurring_edit_todo[recurring_period]', 'weekly', @recurring_todo.recurring_period == 'weekly')%> <%= t('todos.recurrence.weekly') %><br/>
|
<%= radio_button_tag('recurring_edit_todo[recurring_period]', 'weekly', @form_helper.recurring_period == 'weekly')%> <%= t('todos.recurrence.weekly') %><br/>
|
||||||
<%= radio_button_tag('recurring_edit_todo[recurring_period]', 'monthly', @recurring_todo.recurring_period == 'monthly')%> <%= t('todos.recurrence.monthly') %><br/>
|
<%= radio_button_tag('recurring_edit_todo[recurring_period]', 'monthly', @form_helper.recurring_period == 'monthly')%> <%= t('todos.recurrence.monthly') %><br/>
|
||||||
<%= radio_button_tag('recurring_edit_todo[recurring_period]', 'yearly', @recurring_todo.recurring_period == 'yearly')%> <%= t('todos.recurrence.yearly') %><br/>
|
<%= radio_button_tag('recurring_edit_todo[recurring_period]', 'yearly', @form_helper.recurring_period == 'yearly')%> <%= t('todos.recurrence.yearly') %><br/>
|
||||||
</div>
|
</div>
|
||||||
<div id="recurring_timespan">
|
<div id="recurring_timespan">
|
||||||
<br/>
|
<br/>
|
||||||
<label for="recurring_todo[start_from]"><%= t('todos.recurrence.starts_on') %>: </label><%=
|
<label for="recurring_todo[start_from]"><%= t('todos.recurrence.starts_on') %>: </label><%=
|
||||||
text_field_tag("recurring_todo_edit_start_from", format_date(@recurring_todo.start_from), "size" => 12, "class" => "Date", "autocomplete" => "off") %><br/>
|
text_field_tag("recurring_todo_edit_start_from", format_date(@form_helper.start_from), "size" => 12, "class" => "Date", "autocomplete" => "off") %><br/>
|
||||||
<br/>
|
<br/>
|
||||||
<label for="recurring_todo[ends_on]"><%= t('todos.recurrence.ends_on') %>:</label><br/>
|
<label for="recurring_todo[ends_on]"><%= t('todos.recurrence.ends_on') %>:</label><br/>
|
||||||
<%= radio_button_tag('recurring_todo[ends_on]', 'no_end_date', @recurring_todo.ends_on == 'no_end_date')%> <%= t('todos.recurrence.no_end_date') %><br/>
|
<%= radio_button_tag('recurring_todo[ends_on]', 'no_end_date', @form_helper.ends_on == 'no_end_date')%> <%= t('todos.recurrence.no_end_date') %><br/>
|
||||||
<%= 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]', '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)) %><br/>
|
<%= raw t('todos.recurrence.ends_on_number_times', :number => text_field( :recurring_todo, :number_of_occurences, "size" => 3)) %><br/>
|
||||||
<%= radio_button_tag('recurring_todo[ends_on]', 'ends_on_end_date', @recurring_todo.ends_on == 'ends_on_end_date')%>
|
<%= 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(@recurring_todo.end_date), "size" => 12, "class" => "Date", "autocomplete" => "off")) %><br/>
|
<%= 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")) %><br/>
|
||||||
</div></div>
|
</div></div>
|
||||||
<div id="recurring_edit_daily" style="display:<%= @recurring_todo.recurring_period == 'daily' ? 'block' : 'none' %> ">
|
<div id="recurring_edit_daily" style="display:<%= @form_helper.recurring_period == 'daily' ? 'block' : 'none' %> ">
|
||||||
<label><%= t('todos.recurrence.daily_options') %></label><br/>
|
<label><%= t('todos.recurrence.daily_options') %></label><br/>
|
||||||
<%= radio_button_tag('recurring_todo[daily_selector]', 'daily_every_x_day', !@recurring_todo.only_work_days)%>
|
<%= 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]', @recurring_todo.daily_every_x_days, {"size" => 3})) %><br/>
|
<%= 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})) %><br/>
|
||||||
<%= radio_button_tag('recurring_todo[daily_selector]', 'daily_every_work_day', @recurring_todo.only_work_days)%> <%= t('todos.recurrence.every_work_day') %><br/>
|
<%= radio_button_tag('recurring_todo[daily_selector]', 'daily_every_work_day', @form_helper.only_work_days)%> <%= t('todos.recurrence.every_work_day') %><br/>
|
||||||
</div>
|
</div>
|
||||||
<div id="recurring_edit_weekly" style="display:<%= @recurring_todo.recurring_period == 'weekly' ? 'block' : 'none' %>">
|
<div id="recurring_edit_weekly" style="display:<%= @form_helper.recurring_period == 'weekly' ? 'block' : 'none' %>">
|
||||||
<label><%= t('todos.recurrence.weekly_options') %></label><br/>
|
<label><%= t('todos.recurrence.weekly_options') %></label><br/>
|
||||||
<%= 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})) %><br/>
|
<%= 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})) %><br/>
|
||||||
<%= 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_monday]', 'm', @form_helper.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_tuesday]', 't', @form_helper.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_wednesday]', 'w', @form_helper.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] %><br/>
|
<%= check_box_tag('recurring_todo[weekly_return_thursday]', 't', @form_helper.on_thursday) %> <%= t('date.day_names')[4] %><br/>
|
||||||
<%= 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_friday]', 'f', @form_helper.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_saturday]', 's', @form_helper.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] %><br/>
|
<%= check_box_tag('recurring_todo[weekly_return_sunday]', 's', @form_helper.on_sunday) %> <%= t('date.day_names')[0] %><br/>
|
||||||
</div>
|
</div>
|
||||||
<div id="recurring_edit_monthly" style="display:<%= @recurring_todo.recurring_period == 'monthly' ? 'block' : 'none' %>">
|
<div id="recurring_edit_monthly" style="display:<%= @form_helper.recurring_period == 'monthly' ? 'block' : 'none' %>">
|
||||||
<label><%= t('todos.recurrence.monthly_options') %></label><br/>
|
<label><%= t('todos.recurrence.monthly_options') %></label><br/>
|
||||||
<%= 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',
|
<%= 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}),
|
: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]', @recurring_todo.monthly_every_x_month, {"size" => 3})) %><br/>
|
:month => text_field_tag('recurring_todo[monthly_every_x_month]', @form_helper.monthly_every_x_month, {"size" => 3})) %><br/>
|
||||||
<%= radio_button_tag('recurring_todo[monthly_selector]', 'monthly_every_xth_day', @recurring_todo.is_monthly_every_xth_day)%>
|
<%= 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',
|
<%= 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 => 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, @recurring_todo.monthly_day_of_week)),
|
: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]', @recurring_todo.monthly_every_x_month2, {"size" => 3})) %><br/>
|
:month => text_field_tag('recurring_todo[monthly_every_x_month2]', @form_helper.monthly_every_x_month2, {"size" => 3})) %><br/>
|
||||||
</div>
|
</div>
|
||||||
<div id="recurring_edit_yearly" style="display:<%= @recurring_todo.recurring_period == 'yearly' ? 'block' : 'none' %>">
|
<div id="recurring_edit_yearly" style="display:<%= @form_helper.recurring_period == 'yearly' ? 'block' : 'none' %>">
|
||||||
<label><%= t('todos.recurrence.yearly_options') %></label><br/>
|
<label><%= t('todos.recurrence.yearly_options') %></label><br/>
|
||||||
<%= 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',
|
<%= 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)),
|
: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]', @recurring_todo.yearly_every_x_day, "size" => 3)) %><br/>
|
:day => text_field_tag('recurring_todo[yearly_every_x_day]', @form_helper.yearly_every_x_day, "size" => 3)) %><br/>
|
||||||
<%= radio_button_tag('recurring_todo[yearly_selector]', 'yearly_every_xth_day', @recurring_todo.recurrence_selector == 1)%>
|
<%= radio_button_tag('recurring_todo[yearly_selector]', 'yearly_every_xth_day', @form_helper.recurrence_selector == 1)%>
|
||||||
<%= raw t('todos.recurrence.yearly_every_xth_day',
|
<%= 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 => 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, @recurring_todo.yearly_day_of_week)),
|
: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, @recurring_todo.yearly_month_of_year2))) %><br/>
|
:month => select_tag('recurring_todo[yearly_month_of_year2]', options_for_select(@months_of_year, @form_helper.yearly_month_of_year2))) %><br/>
|
||||||
</div>
|
</div>
|
||||||
<div id="recurring_target">
|
<div id="recurring_target">
|
||||||
<label><%= t('todos.recurrence.recurrence_on.options') %></label><br/>
|
<label><%= t('todos.recurrence.recurrence_on.options') %></label><br/>
|
||||||
<%= 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_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', @recurring_todo.show_always?)%> <%= t('todos.recurrence.recurrence_on.show_always') %>
|
<%= 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', !@recurring_todo.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]', @recurring_todo.show_from_delta, {"size" => 3})) %><br/>
|
<%= 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})) %><br/>
|
||||||
<%= radio_button_tag('recurring_todo[recurring_target]', 'show_from_date', @recurring_todo.target == 'show_from_date')%> <%= t('todos.recurrence.recurrence_on.from_tickler') %><br/>
|
<%= radio_button_tag('recurring_todo[recurring_target]', 'show_from_date', @form_helper.target == 'show_from_date')%> <%= t('todos.recurrence.recurrence_on.from_tickler') %><br/>
|
||||||
<br/>
|
<br/>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
||||||
|
|
@ -630,13 +630,15 @@ en:
|
||||||
- December
|
- December
|
||||||
third: third
|
third: third
|
||||||
every_n: every %{n}
|
every_n: every %{n}
|
||||||
on_day_n: on day %{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
|
second: second
|
||||||
every_xth_day_of_every_n_months: every %{x} %{day} of every %{n_months}
|
every_xth_day_of_every_n_months: every %{x} %{day} of every %{n_months}
|
||||||
from: from
|
from: from
|
||||||
weekly: weekly
|
weekly: weekly
|
||||||
last: last
|
last: last
|
||||||
every_day: every day
|
|
||||||
the_xth_day_of_month: the %{x} %{day} of %{month}
|
the_xth_day_of_month: the %{x} %{day} of %{month}
|
||||||
times: for %{number} times
|
times: for %{number} times
|
||||||
on_work_days: on work days
|
on_work_days: on work days
|
||||||
|
|
|
||||||
|
|
@ -20,5 +20,15 @@ namespace :tracks do
|
||||||
user.errors.each_full { |msg| puts "- #{msg}\n" }
|
user.errors.each_full { |msg| puts "- #{msg}\n" }
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
class TodoFromRecurringTodo
|
class TodoFromRecurringTodo
|
||||||
attr_reader :user, :recurring_todo, :todo
|
attr_reader :user, :recurring_todo, :todo
|
||||||
|
|
||||||
def initialize(user, recurring_todo)
|
def initialize(user, recurring_todo)
|
||||||
@user = user
|
@user = user
|
||||||
@recurring_todo = recurring_todo
|
@recurring_todo = recurring_todo
|
||||||
|
|
@ -32,9 +33,9 @@ class TodoFromRecurringTodo
|
||||||
def attributes
|
def attributes
|
||||||
{
|
{
|
||||||
:description => recurring_todo.description,
|
:description => recurring_todo.description,
|
||||||
:notes => recurring_todo.notes,
|
:notes => recurring_todo.notes,
|
||||||
:project_id => recurring_todo.project_id,
|
:project_id => recurring_todo.project_id,
|
||||||
:context_id => recurring_todo.context_id
|
:context_id => recurring_todo.context_id
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
140
lib/tracks/attribute_handler.rb
Normal file
140
lib/tracks/attribute_handler.rb
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
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 [](attribute)
|
||||||
|
get attribute
|
||||||
|
end
|
||||||
|
|
||||||
|
def set(key, value)
|
||||||
|
@attributes[key.to_sym] = value
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_if_nil(key, value)
|
||||||
|
@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
|
||||||
|
|
||||||
|
def key?(key)
|
||||||
|
@attributes.key?(key.to_sym)
|
||||||
|
end
|
||||||
|
|
||||||
|
def selector_key_present?(key)
|
||||||
|
key?(key)
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_date(date)
|
||||||
|
set(date, @user.prefs.parse_date(get(date)))
|
||||||
|
end
|
||||||
|
|
||||||
|
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, 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
|
||||||
|
# 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 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]
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
@ -9,13 +9,21 @@ class RecurringTodosControllerTest < ActionController::TestCase
|
||||||
|
|
||||||
def test_destroy_recurring_todo
|
def test_destroy_recurring_todo
|
||||||
login_as(:admin_user)
|
login_as(:admin_user)
|
||||||
|
|
||||||
|
rc = RecurringTodo.find(1)
|
||||||
|
todo = rc.todos.first
|
||||||
|
|
||||||
xhr :post, :destroy, :id => 1, :_source_view => 'todo'
|
xhr :post, :destroy, :id => 1, :_source_view => 'todo'
|
||||||
|
|
||||||
begin
|
begin
|
||||||
rc = RecurringTodo.find(1)
|
rc = RecurringTodo.find(1)
|
||||||
rescue
|
rescue
|
||||||
rc = nil
|
rc = nil
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
def test_new_recurring_todo
|
def test_new_recurring_todo
|
||||||
|
|
@ -62,6 +70,52 @@ class RecurringTodosControllerTest < ActionController::TestCase
|
||||||
assert_equal orig_todo_count+1, Todo.count
|
assert_equal orig_todo_count+1, Todo.count
|
||||||
end
|
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
|
def test_recurring_todo_toggle_check
|
||||||
# the test fixtures did add recurring_todos but not the corresponding todos,
|
# 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
|
# so we check complete and uncheck to force creation of a todo from the
|
||||||
|
|
@ -313,4 +367,29 @@ class RecurringTodosControllerTest < ActionController::TestCase
|
||||||
assert_equal "completed", rt.state, "repeat pattern should be completed"
|
assert_equal "completed", rt.state, "repeat pattern should be completed"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_update_recurring_todo
|
||||||
|
login_as(:admin_user)
|
||||||
|
rt = recurring_todos(:call_bill_gates_every_day)
|
||||||
|
|
||||||
|
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
|
end
|
||||||
|
|
|
||||||
2
test/fixtures/recurring_todos.yml
vendored
2
test/fixtures/recurring_todos.yml
vendored
|
|
@ -92,7 +92,7 @@ call_bill_gates_every_week:
|
||||||
end_date: ~
|
end_date: ~
|
||||||
number_of_occurences: ~
|
number_of_occurences: ~
|
||||||
target: due_date
|
target: due_date
|
||||||
show_from_delta: ~
|
show_from_delta: 5
|
||||||
recurring_period: weekly
|
recurring_period: weekly
|
||||||
recurrence_selector: ~
|
recurrence_selector: ~
|
||||||
show_always: false
|
show_always: false
|
||||||
|
|
|
||||||
97
test/models/attribute_handler_test.rb
Normal file
97
test/models/attribute_handler_test.rb
Normal file
|
|
@ -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
|
||||||
|
|
@ -25,36 +25,6 @@ class RecurringTodoTest < ActiveSupport::TestCase
|
||||||
@thursday = Time.zone.local(2008,6,12)
|
@thursday = Time.zone.local(2008,6,12)
|
||||||
end
|
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)
|
|
||||||
# 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
|
def test_show_from_date
|
||||||
# assume that target due_date works fine, i.e. don't do the same tests over
|
# assume that target due_date works fine, i.e. don't do the same tests over
|
||||||
|
|
||||||
|
|
@ -88,128 +58,6 @@ class RecurringTodoTest < ActiveSupport::TestCase
|
||||||
# weekly/monthly/yearly
|
# weekly/monthly/yearly
|
||||||
end
|
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_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)
|
|
||||||
|
|
||||||
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.weekly_return_tuesday=(' ')
|
|
||||||
@weekly_every_day.weekly_return_wednesday=(' ')
|
|
||||||
assert_equal 'sm tfs', @weekly_every_day.every_day
|
|
||||||
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
|
def test_next_todo_without_previous_todo
|
||||||
# test handling of nil as previous
|
# test handling of nil as previous
|
||||||
#
|
#
|
||||||
|
|
@ -277,7 +125,7 @@ class RecurringTodoTest < ActiveSupport::TestCase
|
||||||
|
|
||||||
def test_toggle_completion
|
def test_toggle_completion
|
||||||
assert @yearly.active?
|
assert @yearly.active?
|
||||||
assert @yearly.toggle_completion!
|
assert @yearly.toggle_completion!, "toggle of completion should succeed"
|
||||||
assert @yearly.completed?
|
assert @yearly.completed?
|
||||||
|
|
||||||
# entering completed state should set completed_at
|
# entering completed state should set completed_at
|
||||||
|
|
@ -320,97 +168,4 @@ class RecurringTodoTest < ActiveSupport::TestCase
|
||||||
assert_equal true, @every_day.continues_recurring?(@in_three_days)
|
assert_equal true, @every_day.continues_recurring?(@in_three_days)
|
||||||
assert_equal 0, @every_day.occurences_count
|
assert_equal 0, @every_day.occurences_count
|
||||||
end
|
end
|
||||||
|
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(Exception){ @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
|
|
||||||
todo = RecurringTodo.new({
|
|
||||||
:description => "Task every 2 days",
|
|
||||||
:context => Context.first,
|
|
||||||
: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,
|
|
||||||
})
|
|
||||||
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({
|
|
||||||
:description => "Task every 3 weeks",
|
|
||||||
:context => Context.first,
|
|
||||||
: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",
|
|
||||||
})
|
|
||||||
assert todo.valid?, todo.errors.full_messages
|
|
||||||
assert_equal 3, todo.every_other1
|
|
||||||
assert todo.on_monday
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_set_every_n_months_from_form_input
|
|
||||||
todo = RecurringTodo.new({
|
|
||||||
:description => "Task every 4 months",
|
|
||||||
:context => Context.first,
|
|
||||||
: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,
|
|
||||||
})
|
|
||||||
assert todo.valid?, todo.errors.full_messages
|
|
||||||
assert_equal 4, todo.every_other2
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_set_yearly_from_form_input
|
|
||||||
todo = RecurringTodo.new({
|
|
||||||
:description => "Task every year in May",
|
|
||||||
:context => Context.first,
|
|
||||||
: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,
|
|
||||||
})
|
|
||||||
assert todo.valid?, todo.errors.full_messages
|
|
||||||
assert_equal 5, todo.every_other2
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -0,0 +1,165 @@
|
||||||
|
require_relative '../../test_helper'
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
def test_filter_attributes_should_throw_exception
|
||||||
|
attributes = Tracks::AttributeHandler.new(@admin, {
|
||||||
|
'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, DailyRepeatPattern)
|
||||||
|
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_on_create_and_update
|
||||||
|
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"
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
def test_map_removes_mapped_key
|
||||||
|
attributes = Tracks::AttributeHandler.new(@admin, { :source => "value"})
|
||||||
|
|
||||||
|
a_builder = WeeklyRecurringTodosBuilder.new(@admin, attributes)
|
||||||
|
attributes = a_builder.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 = Tracks::AttributeHandler.new(@admin, { :selector => "weekly" })
|
||||||
|
a_builder = WeeklyRecurringTodosBuilder.new(@admin, attributes)
|
||||||
|
|
||||||
|
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, { })
|
||||||
|
a_builder = WeeklyRecurringTodosBuilder.new(@admin, attributes)
|
||||||
|
|
||||||
|
assert_raise(Exception, "should raise exception when recurrence selector is missing"){ a_builder.get_selector(:selector) }
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
151
test/models/recurring_todos/abstract_repeat_pattern_test.rb
Normal file
151
test/models/recurring_todos/abstract_repeat_pattern_test.rb
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
require_relative '../../test_helper'
|
||||||
|
|
||||||
|
module RecurringTodos
|
||||||
|
|
||||||
|
class AbstractRepeatPatternTest < ActiveSupport::TestCase
|
||||||
|
fixtures :users
|
||||||
|
|
||||||
|
def setup
|
||||||
|
super
|
||||||
|
@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 = {
|
||||||
|
'weekly_every_x_week' => 1,
|
||||||
|
'weekly_return_monday' => 'm', # weekly specific
|
||||||
|
}
|
||||||
|
|
||||||
|
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_recurring_todo(attributes)
|
||||||
|
assert !pattern.valid?, "should fail because show_from_delta is not there"
|
||||||
|
|
||||||
|
attributes[:recurring_show_days_before] = 5
|
||||||
|
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 = {
|
||||||
|
'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_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_recurring_todo(attributes)
|
||||||
|
assert pattern.valid?, "should be valid: " + pattern.errors.full_messages.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_validation_on_end_date
|
||||||
|
attributes = {
|
||||||
|
'weekly_return_monday' => 'm', # weekly specific
|
||||||
|
'ends_on' => 'invalid_value',
|
||||||
|
'weekly_every_x_week' => 1,
|
||||||
|
'recurring_show_always' => false,
|
||||||
|
'recurring_show_days_before' => 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
pattern = create_recurring_todo(attributes)
|
||||||
|
assert !pattern.valid?
|
||||||
|
|
||||||
|
attributes['ends_on']='ends_on_end_date'
|
||||||
|
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_recurring_todo(attributes)
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
def test_determine_start
|
||||||
|
Timecop.travel(2013,1,1) do
|
||||||
|
rt = create_recurring_todo
|
||||||
|
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
|
||||||
|
|
||||||
|
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", 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", 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) }
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def create_pattern(attributes)
|
||||||
|
builder = RecurringTodosBuilder.new(@admin, attributes)
|
||||||
|
builder.build
|
||||||
|
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
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
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.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 = Tracks::AttributeHandler.new(@admin, {
|
||||||
|
'recurring_period' => 'daily'
|
||||||
|
})
|
||||||
|
|
||||||
|
# should not raise
|
||||||
|
%w{daily_every_x_day daily_every_work_day}.each do |selector|
|
||||||
|
attributes.set('daily_selector', selector)
|
||||||
|
DailyRecurringTodosBuilder.new(@admin, attributes)
|
||||||
|
end
|
||||||
|
|
||||||
|
# should raise
|
||||||
|
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 = 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.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 = 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.get(:only_work_days)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
81
test/models/recurring_todos/daily_repeat_pattern_test.rb
Normal file
81
test/models/recurring_todos/daily_repeat_pattern_test.rb
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
require_relative '../../test_helper'
|
||||||
|
|
||||||
|
module RecurringTodos
|
||||||
|
|
||||||
|
class DailyRepeatPatternTest < ActiveSupport::TestCase
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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_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
|
||||||
|
|
||||||
|
end
|
||||||
24
test/models/recurring_todos/form_helper_test.rb
Normal file
24
test/models/recurring_todos/form_helper_test.rb
Normal file
|
|
@ -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
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
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.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 = Tracks::AttributeHandler.new(@admin, {
|
||||||
|
'recurring_period' => 'monthly'
|
||||||
|
})
|
||||||
|
|
||||||
|
# should not raise
|
||||||
|
%w{monthly_every_x_day monthly_every_xth_day}.each do |selector|
|
||||||
|
attributes.set('monthly_selector', selector)
|
||||||
|
MonthlyRecurringTodosBuilder.new(@admin, attributes)
|
||||||
|
end
|
||||||
|
|
||||||
|
# should raise
|
||||||
|
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 = Tracks::AttributeHandler.new(@admin, {
|
||||||
|
'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.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 = 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.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"
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
148
test/models/recurring_todos/monthly_repeat_pattern_test.rb
Normal file
148
test/models/recurring_todos/monthly_repeat_pattern_test.rb
Normal file
|
|
@ -0,0 +1,148 @@
|
||||||
|
require_relative '../../test_helper'
|
||||||
|
|
||||||
|
module RecurringTodos
|
||||||
|
|
||||||
|
class MonthlyRepeatPatternTest < ActiveSupport::TestCase
|
||||||
|
fixtures :users
|
||||||
|
|
||||||
|
def setup
|
||||||
|
super
|
||||||
|
@admin = users(:admin_user)
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
155
test/models/recurring_todos/recurring_todos_builder_test.rb
Normal file
155
test/models/recurring_todos/recurring_todos_builder_test.rb
Normal file
|
|
@ -0,0 +1,155 @@
|
||||||
|
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(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
|
||||||
|
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
|
||||||
|
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)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
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.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 = 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
|
||||||
|
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
|
||||||
|
|
||||||
|
def test_mapping_of_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.get(:every_other1), "every_other1 should be set to weekly_every_x_week"
|
||||||
|
assert_equal ' m ', pattern.mapped_attributes.get(:every_day), "weekly_return_<weekday> should be mapped to :every_day in format 'smtwtfs'"
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_map_day
|
||||||
|
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.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.set("weekly_return_#{day}", short)
|
||||||
|
end
|
||||||
|
|
||||||
|
pattern = WeeklyRecurringTodosBuilder.new(@admin, attributes)
|
||||||
|
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.get(:every_day), "only wednesday should be empty in :every_day"
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
82
test/models/recurring_todos/weekly_repeat_pattern_test.rb
Normal file
82
test/models/recurring_todos/weekly_repeat_pattern_test.rb
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
require_relative '../../test_helper'
|
||||||
|
|
||||||
|
module RecurringTodos
|
||||||
|
|
||||||
|
class WeeklyRepeatPatternTest < ActiveSupport::TestCase
|
||||||
|
fixtures :users
|
||||||
|
|
||||||
|
def setup
|
||||||
|
super
|
||||||
|
@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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
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.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 = Tracks::AttributeHandler.new(@admin, {
|
||||||
|
'recurring_period' => 'yearly'
|
||||||
|
})
|
||||||
|
|
||||||
|
# should not raise
|
||||||
|
%w{yearly_every_x_day yearly_every_xth_day}.each do |selector|
|
||||||
|
attributes.set(:yearly_selector, selector)
|
||||||
|
YearlyRecurringTodosBuilder.new(@admin, attributes)
|
||||||
|
end
|
||||||
|
|
||||||
|
# should raise
|
||||||
|
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) }
|
||||||
|
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, Tracks::AttributeHandler.new(@admin, attributes))
|
||||||
|
|
||||||
|
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',
|
||||||
|
'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, 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
|
||||||
|
|
||||||
|
end
|
||||||
115
test/models/recurring_todos/yearly_repeat_pattern_test.rb
Normal file
115
test/models/recurring_todos/yearly_repeat_pattern_test.rb
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
require_relative '../../test_helper'
|
||||||
|
|
||||||
|
module RecurringTodos
|
||||||
|
|
||||||
|
class YearlyRepeatPatternTest < ActiveSupport::TestCase
|
||||||
|
fixtures :users
|
||||||
|
|
||||||
|
def setup
|
||||||
|
super
|
||||||
|
@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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
@ -13,7 +13,22 @@ class ActiveSupport::TestCase
|
||||||
# Note: You'll currently still have to declare fixtures explicitly in integration tests
|
# Note: You'll currently still have to declare fixtures explicitly in integration tests
|
||||||
# -- they do not yet inherit this setting
|
# -- they do not yet inherit this setting
|
||||||
fixtures :all
|
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...
|
# Add more helper methods to be used by all tests here...
|
||||||
def assert_value_changed(object, method = nil)
|
def assert_value_changed(object, method = nil)
|
||||||
initial_value = object.send(method)
|
initial_value = object.send(method)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue