dgp-schedule_attributes 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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