dgp-schedule_attributes 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +28 -0
  5. data/Appraisals +21 -0
  6. data/Gemfile +4 -0
  7. data/Gemfile.lock +63 -0
  8. data/README.markdown +78 -0
  9. data/Rakefile +7 -0
  10. data/gemfiles/rails_3.2.gemfile +8 -0
  11. data/gemfiles/rails_3.2.gemfile.lock +112 -0
  12. data/gemfiles/rails_4.0.gemfile +7 -0
  13. data/gemfiles/rails_4.0.gemfile.lock +76 -0
  14. data/gemfiles/rails_4.1.gemfile +7 -0
  15. data/gemfiles/rails_4.1.gemfile.lock +75 -0
  16. data/gemfiles/rails_4.2.gemfile +7 -0
  17. data/gemfiles/rails_4.2.gemfile.lock +75 -0
  18. data/gemfiles/rails_edge.gemfile +8 -0
  19. data/gemfiles/rails_edge.gemfile.lock +83 -0
  20. data/lib/schedule_attributes.rb +18 -0
  21. data/lib/schedule_attributes/active_record.rb +50 -0
  22. data/lib/schedule_attributes/configuration.rb +22 -0
  23. data/lib/schedule_attributes/core.rb +146 -0
  24. data/lib/schedule_attributes/extensions/ice_cube.rb +11 -0
  25. data/lib/schedule_attributes/form_builder.rb +42 -0
  26. data/lib/schedule_attributes/input.rb +159 -0
  27. data/lib/schedule_attributes/model.rb +24 -0
  28. data/lib/schedule_attributes/railtie.rb +13 -0
  29. data/lib/schedule_attributes/rule_parser.rb +25 -0
  30. data/lib/schedule_attributes/rule_parser/base.rb +96 -0
  31. data/lib/schedule_attributes/rule_parser/day.rb +13 -0
  32. data/lib/schedule_attributes/rule_parser/month.rb +40 -0
  33. data/lib/schedule_attributes/rule_parser/week.rb +18 -0
  34. data/lib/schedule_attributes/rule_parser/year.rb +21 -0
  35. data/lib/schedule_attributes/serializer.rb +30 -0
  36. data/lib/schedule_attributes/time_helpers.rb +36 -0
  37. data/lib/schedule_attributes/version.rb +3 -0
  38. data/schedule_attributes.gemspec +32 -0
  39. data/spec/active_record_integration_spec.rb +57 -0
  40. data/spec/schedule_attributes/configuration_spec.rb +29 -0
  41. data/spec/schedule_attributes/input_spec.rb +182 -0
  42. data/spec/schedule_attributes/rule_parser/day_spec.rb +113 -0
  43. data/spec/schedule_attributes/rule_parser/month_spec.rb +47 -0
  44. data/spec/schedule_attributes/rule_parser/week_spec.rb +36 -0
  45. data/spec/schedule_attributes/rule_parser/year_spec.rb +53 -0
  46. data/spec/schedule_attributes/rule_parser_spec.rb +18 -0
  47. data/spec/schedule_attributes/time_helpers_spec.rb +39 -0
  48. data/spec/schedule_attributes_spec.rb +226 -0
  49. data/spec/spec_helper.rb +11 -0
  50. data/spec/support/parser_macros.rb +31 -0
  51. data/spec/support/scheduled_active_record_model.rb +39 -0
  52. data/spec/support/scheduled_model.rb +16 -0
  53. metadata +234 -0
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "http://rubygems.org"
4
+
5
+ gem "activesupport", :github => "rails/rails", :branch => "4-2-stable"
6
+
7
+ gemspec :path => "../"
@@ -0,0 +1,75 @@
1
+ GIT
2
+ remote: git://github.com/rails/rails.git
3
+ revision: 7974611ee7df22174175b59094bfa5c971302efa
4
+ branch: 4-2-stable
5
+ specs:
6
+ activemodel (4.2.4)
7
+ activesupport (= 4.2.4)
8
+ builder (~> 3.1)
9
+ activerecord (4.2.4)
10
+ activemodel (= 4.2.4)
11
+ activesupport (= 4.2.4)
12
+ arel (~> 6.0)
13
+ activesupport (4.2.4)
14
+ i18n (~> 0.7)
15
+ json (~> 1.7, >= 1.7.7)
16
+ minitest (~> 5.1)
17
+ thread_safe (~> 0.3, >= 0.3.4)
18
+ tzinfo (~> 1.1)
19
+
20
+ PATH
21
+ remote: ../
22
+ specs:
23
+ dgp-schedule_attributes (0.4.0)
24
+ activesupport (>= 3.2)
25
+ ice_cube (~> 0.13)
26
+ tzinfo
27
+
28
+ GEM
29
+ remote: http://rubygems.org/
30
+ specs:
31
+ appraisal (2.1.0)
32
+ bundler
33
+ rake
34
+ thor (>= 0.14.0)
35
+ arel (6.0.3)
36
+ builder (3.2.2)
37
+ coderay (1.1.0)
38
+ diff-lcs (1.2.5)
39
+ i18n (0.7.0)
40
+ ice_cube (0.13.0)
41
+ json (1.8.3)
42
+ method_source (0.8.2)
43
+ minitest (5.8.0)
44
+ pry (0.10.1)
45
+ coderay (~> 1.1.0)
46
+ method_source (~> 0.8.1)
47
+ slop (~> 3.4)
48
+ rake (10.4.2)
49
+ rspec (2.99.0)
50
+ rspec-core (~> 2.99.0)
51
+ rspec-expectations (~> 2.99.0)
52
+ rspec-mocks (~> 2.99.0)
53
+ rspec-core (2.99.2)
54
+ rspec-expectations (2.99.2)
55
+ diff-lcs (>= 1.1.3, < 2.0)
56
+ rspec-mocks (2.99.4)
57
+ slop (3.6.0)
58
+ sqlite3 (1.3.10)
59
+ thor (0.19.1)
60
+ thread_safe (0.3.5)
61
+ tzinfo (1.2.2)
62
+ thread_safe (~> 0.1)
63
+
64
+ PLATFORMS
65
+ ruby
66
+
67
+ DEPENDENCIES
68
+ activerecord (>= 3.2)
69
+ activesupport!
70
+ appraisal
71
+ dgp-schedule_attributes!
72
+ pry
73
+ rake
74
+ rspec (~> 2.99)
75
+ sqlite3
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "http://rubygems.org"
4
+
5
+ gem "activesupport", :github => "rails/rails"
6
+ gem "arel", :github => "rails/arel"
7
+
8
+ gemspec :path => "../"
@@ -0,0 +1,83 @@
1
+ GIT
2
+ remote: git://github.com/rails/arel.git
3
+ revision: d5432b4616ff43fbb14540d351eed351e21bb20e
4
+ specs:
5
+ arel (7.0.0.alpha)
6
+
7
+ GIT
8
+ remote: git://github.com/rails/rails.git
9
+ revision: e47e8e915e733177fe1dee2c67bef1724bbba553
10
+ specs:
11
+ activemodel (5.0.0.alpha)
12
+ activesupport (= 5.0.0.alpha)
13
+ builder (~> 3.1)
14
+ activerecord (5.0.0.alpha)
15
+ activemodel (= 5.0.0.alpha)
16
+ activesupport (= 5.0.0.alpha)
17
+ arel (= 7.0.0.alpha)
18
+ activesupport (5.0.0.alpha)
19
+ concurrent-ruby (~> 0.9.1)
20
+ i18n (~> 0.7)
21
+ json (~> 1.7, >= 1.7.7)
22
+ method_source
23
+ minitest (~> 5.1)
24
+ thread_safe (~> 0.3, >= 0.3.4)
25
+ tzinfo (~> 1.1)
26
+
27
+ PATH
28
+ remote: ../
29
+ specs:
30
+ dgp-schedule_attributes (0.4.0)
31
+ activesupport (>= 3.2)
32
+ ice_cube (~> 0.13)
33
+ tzinfo
34
+
35
+ GEM
36
+ remote: http://rubygems.org/
37
+ specs:
38
+ appraisal (2.1.0)
39
+ bundler
40
+ rake
41
+ thor (>= 0.14.0)
42
+ builder (3.2.2)
43
+ coderay (1.1.0)
44
+ concurrent-ruby (0.9.1)
45
+ diff-lcs (1.2.5)
46
+ i18n (0.7.0)
47
+ ice_cube (0.13.0)
48
+ json (1.8.3)
49
+ method_source (0.8.2)
50
+ minitest (5.8.0)
51
+ pry (0.10.1)
52
+ coderay (~> 1.1.0)
53
+ method_source (~> 0.8.1)
54
+ slop (~> 3.4)
55
+ rake (10.4.2)
56
+ rspec (2.99.0)
57
+ rspec-core (~> 2.99.0)
58
+ rspec-expectations (~> 2.99.0)
59
+ rspec-mocks (~> 2.99.0)
60
+ rspec-core (2.99.2)
61
+ rspec-expectations (2.99.2)
62
+ diff-lcs (>= 1.1.3, < 2.0)
63
+ rspec-mocks (2.99.4)
64
+ slop (3.6.0)
65
+ sqlite3 (1.3.10)
66
+ thor (0.19.1)
67
+ thread_safe (0.3.5)
68
+ tzinfo (1.2.2)
69
+ thread_safe (~> 0.1)
70
+
71
+ PLATFORMS
72
+ ruby
73
+
74
+ DEPENDENCIES
75
+ activerecord (>= 3.2)
76
+ activesupport!
77
+ appraisal
78
+ arel!
79
+ dgp-schedule_attributes!
80
+ pry
81
+ rake
82
+ rspec (~> 2.99)
83
+ sqlite3
@@ -0,0 +1,18 @@
1
+ require 'schedule_attributes/model'
2
+ require 'schedule_attributes/railtie' if defined? Rails
3
+ require 'schedule_attributes/form_builder' if defined? Formtastic
4
+
5
+ if defined? Arel
6
+ module Arel::Visitors
7
+ class ToSql
8
+
9
+ # Allow schedule occurrences to be used directly as Time objects in
10
+ # ActiveRecord queries
11
+ #
12
+ def visit_IceCube_Occurrence(value)
13
+ quoted(value.to_time)
14
+ end
15
+
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,50 @@
1
+ require 'schedule_attributes/core'
2
+ require 'schedule_attributes/serializer'
3
+
4
+ module ScheduleAttributes::ActiveRecord
5
+ extend ActiveSupport::Concern
6
+ include ScheduleAttributes::Core
7
+
8
+ module ClassMethods
9
+ attr_accessor :schedule_field
10
+ attr_accessor :default_schedule
11
+
12
+ def default_schedule
13
+ @default_schedule || ScheduleAttributes.default_schedule
14
+ end
15
+ end
16
+
17
+ module Sugar
18
+ def has_schedule_attributes(options={:column_name => :schedule})
19
+ options[:column_name] ||= ScheduleAttributes::DEFAULT_ATTRIBUTE_KEY
20
+ @schedule_field = options[:column_name]
21
+ @default_schedule = options[:default_schedule] if options.has_key?(:default_schedule)
22
+ serialize @schedule_field, ScheduleAttributes::Serializer
23
+ include ScheduleAttributes::ActiveRecord
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def initialize(*args)
30
+ super
31
+ self[self.class.schedule_field] ||= default_schedule
32
+ end
33
+
34
+ def read_schedule_field
35
+ self[self.class.schedule_field] or default_schedule
36
+ end
37
+
38
+ def write_schedule_field(value)
39
+ self[self.class.schedule_field] = value
40
+ end
41
+
42
+ def default_schedule
43
+ self.class.default_schedule
44
+ end
45
+ end
46
+
47
+ # Injects the has_schedule_attributes method when loading without Rails
48
+ if defined? ActiveRecord::Base
49
+ ActiveRecord::Base.send :extend, ScheduleAttributes::ActiveRecord::Sugar
50
+ end
@@ -0,0 +1,22 @@
1
+ module ScheduleAttributes
2
+
3
+ class Configuration
4
+
5
+ attr_accessor :time_format
6
+
7
+ def initialize
8
+ @time_format = '%H:%M'
9
+ end
10
+ end
11
+
12
+ class << self
13
+ def configure
14
+ @configuration ||= Configuration.new
15
+ if block_given?
16
+ yield @configuration
17
+ end
18
+ return @configuration
19
+ end
20
+ alias_method :configuration, :configure
21
+ end
22
+ end
@@ -0,0 +1,146 @@
1
+ require 'active_support/time'
2
+ require 'active_support/concern'
3
+ require 'active_support/time_with_zone'
4
+ require 'ice_cube'
5
+ require 'ostruct'
6
+ require 'schedule_attributes/configuration'
7
+ require 'schedule_attributes/extensions/ice_cube'
8
+ require 'schedule_attributes/input'
9
+ require 'schedule_attributes/rule_parser'
10
+
11
+ module ScheduleAttributes
12
+ DEFAULT_ATTRIBUTE_KEY = :schedule
13
+ DAY_NAMES = Date::DAYNAMES.map(&:downcase).map(&:to_sym)
14
+
15
+ class << self
16
+ def default_schedule
17
+ IceCube::Schedule.new(TimeHelpers.today).tap do |s|
18
+ s.add_recurrence_rule(IceCube::Rule.daily)
19
+ end
20
+ end
21
+
22
+ def parse_rule(options)
23
+ RuleParser[options[:interval_unit]].parse(options)
24
+ end
25
+ end
26
+
27
+ module Core
28
+ extend ActiveSupport::Concern
29
+
30
+ def schedule_attributes=(options)
31
+ input = ScheduleAttributes::Input.new(options)
32
+ new_schedule = IceCube::Schedule.new(input.start_time || TimeHelpers.today)
33
+
34
+ if input.repeat?
35
+ parser = ScheduleAttributes::RuleParser[input.interval_unit || 'day'].new(input)
36
+ new_schedule.add_recurrence_rule(parser.rule)
37
+ parser.exceptions.each do |exrule|
38
+ new_schedule.add_exception_rule(exrule)
39
+ end
40
+ else
41
+ input.dates.each do |d|
42
+ new_schedule.add_recurrence_time(d)
43
+ end
44
+ end
45
+
46
+ new_schedule.duration = input.duration if input.duration
47
+
48
+ write_schedule_field(new_schedule)
49
+ end
50
+
51
+ # TODO: use a proper form input model, not OpenStruct
52
+ #
53
+ def schedule_attributes
54
+ atts = {}
55
+ time_format = ScheduleAttributes.configuration.time_format
56
+ schedule = read_schedule_field || ScheduleAttributes.default_schedule
57
+
58
+ if schedule.start_time.seconds_since_midnight == 0 && schedule.end_time.nil?
59
+ atts[:all_day] = true
60
+ else
61
+ atts[:start_time] = schedule.start_time.strftime(time_format)
62
+ atts[:end_time] = (schedule.end_time).strftime(time_format) if schedule.end_time
63
+ end
64
+
65
+ if rule = schedule.rrules.first
66
+ atts[:repeat] = 1
67
+ atts[:start_date] = schedule.start_time.to_date
68
+ atts[:date] = Date.current # default for populating the other part of the form
69
+
70
+ rule_hash = rule.to_hash
71
+ atts[:interval] = rule_hash[:interval]
72
+
73
+ case rule
74
+ when IceCube::DailyRule
75
+ atts[:interval_unit] = 'day'
76
+ when IceCube::WeeklyRule
77
+ atts[:interval_unit] = 'week'
78
+
79
+ if rule_hash[:validations][:day]
80
+ rule_hash[:validations][:day].each do |day_idx|
81
+ atts[ ScheduleAttributes::DAY_NAMES[day_idx] ] = 1
82
+ end
83
+ end
84
+ when IceCube::MonthlyRule
85
+ atts[:interval_unit] = 'month'
86
+
87
+ day_of_week = rule_hash[:validations][:day_of_week]
88
+ day_of_month = rule_hash[:validations][:day_of_month]
89
+
90
+ if day_of_week
91
+ day_of_week = day_of_week.first.flatten
92
+ atts[:ordinal_week] = day_of_week.first
93
+ atts[:ordinal_unit] = 'week'
94
+ elsif day_of_month
95
+ atts[:ordinal_day] = day_of_month.first
96
+ atts[:ordinal_unit] = 'day'
97
+ end
98
+ when IceCube::YearlyRule
99
+ atts[:interval_unit] = 'year'
100
+ end
101
+
102
+ if rule.until_time
103
+ atts[:end_date] = rule.until_time.to_date
104
+ atts[:ends] = 'eventually'
105
+ else
106
+ atts[:ends] = 'never'
107
+ end
108
+
109
+ months = rule.validations_for(:month_of_year).map(&:month)
110
+ if months.present?
111
+ atts[:yearly_start_month] = months.first
112
+ atts[:yearly_end_month] = months.last
113
+
114
+ # get leading & trailing days from exception rules
115
+ schedule.exrules.each do |x|
116
+ x.validations_for(:month_of_year).map(&:month).each do |m|
117
+ days = x.validations_for(:day_of_month).map(&:day)
118
+
119
+ if m == atts[:yearly_start_month]
120
+ atts[:yearly_start_month_day] = days.last + 1 if days.first == 1
121
+ end
122
+
123
+ if m == atts[:yearly_end_month]
124
+ if days.last == 31
125
+ atts[:yearly_end_month_day] = days.first - 1
126
+ atts[:yearly_start_month_day] ||= 1
127
+ end
128
+ end
129
+ end
130
+ end
131
+ else
132
+ rule.replace_validations_for(:month_of_year, nil)
133
+ end
134
+ else
135
+ atts[:repeat] = 0
136
+ atts[:interval] = 1
137
+ atts[:date] = schedule.rtimes.first.to_date
138
+ atts[:dates] = schedule.rtimes.map(&:to_date)
139
+ atts[:start_date] = DateHelpers.today # default for populating the other part of the form
140
+ end
141
+
142
+ OpenStruct.new(atts.delete_if { |_,v| v.blank? })
143
+ end
144
+
145
+ end
146
+ end
@@ -0,0 +1,11 @@
1
+ class IceCube::Rule
2
+ def ==(other)
3
+ to_hash == other.try(:to_hash)
4
+ end
5
+ end
6
+
7
+ class IceCube::Schedule
8
+ def ==(other)
9
+ to_hash == other.try(:to_hash)
10
+ end
11
+ end
@@ -0,0 +1,42 @@
1
+ class ScheduleAttributes::FormBuilder < Formtastic::FormBuilder
2
+
3
+ def weekdays
4
+ Hash[ I18n.t('date.day_names').zip(ScheduleAttributes::DAY_NAMES) ]
5
+ end
6
+
7
+ def ordinal_month_days
8
+ (1..31).map { |d| [d.ordinalize, d] }
9
+ end
10
+
11
+ def ordinal_month_weeks
12
+ Hash["first", 1, "second", 2, "third", 3, "fourth", 4, "last", -1]
13
+ end
14
+
15
+ def one_time_fields(options={}, &block)
16
+ hidden = object.repeat.to_i == 1
17
+ @template.content_tag :div, hiding_field_options("schedule_one_time_fields", hidden, options), &block
18
+ end
19
+
20
+ def repeat_fields(options={}, &block)
21
+ hidden = object.repeat.to_i != 1
22
+ @template.content_tag :div, hiding_field_options("schedule_repeat_fields", hidden, options), &block
23
+ end
24
+
25
+ def ordinal_fields(options={}, &block)
26
+ hidden = object.interval_unit == 'month' && object.by_day_of == 'week'
27
+ @template.content_tag :div, hiding_field_options("schedule_ordinal_fields", hidden, options), &block
28
+ end
29
+
30
+ def weekday_fields(options={}, &block)
31
+ hidden = false
32
+ @template.content_tag :div, hiding_field_options("schedule_weekday_fields", hidden, options), &block
33
+ end
34
+
35
+ def hiding_field_options(class_name, hidden=false, options={})
36
+ hidden_style = "display: none" if hidden
37
+ options.tap do |o|
38
+ o.merge!(style: [o[:style], hidden_style].compact.join('; '))
39
+ o.merge!(class: [o[:class], class_name].compact.join(' '))
40
+ end
41
+ end
42
+ end