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,36 @@
1
+ module ScheduleAttributes
2
+ module TimeHelpers
3
+ def self.parse_in_zone(str)
4
+ if Time.respond_to?(:zone) && Time.zone
5
+ # Rails 4.1 removes Date.to_time_in_current_zone in favour of Date.in_time_zone
6
+ if str.respond_to?(:in_time_zone)
7
+ str.in_time_zone
8
+ else
9
+ str.is_a?(Date) ? str.to_time_in_current_zone : Time.zone.parse(str)
10
+ end
11
+ else
12
+ str.is_a?(Time) ? str : Time.parse(str)
13
+ end
14
+ end
15
+
16
+ def self.today
17
+ if Time.respond_to?(:zone) && Time.zone
18
+ # Rails 4.1 removes Date.to_time_in_current_zone in favour of Date.in_time_zone
19
+ current_date = Date.current
20
+ current_date.respond_to?(:in_time_zone) ? current_date.in_time_zone : current_date.to_time_in_current_zone
21
+ else
22
+ Date.today.to_time
23
+ end
24
+ end
25
+ end
26
+
27
+ module DateHelpers
28
+ def self.today
29
+ if Time.respond_to?(:zone) && Time.zone
30
+ Date.current
31
+ else
32
+ Date.today
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,3 @@
1
+ module ScheduleAttributes
2
+ VERSION = "0.4.0"
3
+ end
@@ -0,0 +1,32 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "schedule_attributes/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "dgp-schedule_attributes"
7
+ s.version = ScheduleAttributes::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["David Gil"]
10
+ s.email = ["dgilperez@gmail.com"]
11
+ s.homepage = "https://github.com/dgilperez/schedule_attributes"
12
+ s.summary = %q{Handle form inputs for IceCube schedules}
13
+ s.description = %q{Converts to/from date & time inputs for managing scheduled models.}
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ s.add_dependency 'ice_cube', '~> 0.13'
21
+ s.add_dependency 'activesupport', '>= 3.2'
22
+ s.add_dependency 'tzinfo' # this should be an activesupport dependency!
23
+
24
+ # s.add_runtime_dependency 'railties', '>= 3.2'
25
+
26
+ s.add_development_dependency 'rake'
27
+ s.add_development_dependency 'rspec', '~> 2.99'
28
+ s.add_development_dependency 'pry'
29
+ s.add_development_dependency 'activerecord', '>= 3.2'
30
+ s.add_development_dependency 'sqlite3'
31
+ s.add_development_dependency 'appraisal'
32
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+ require 'support/scheduled_active_record_model'
3
+
4
+ describe CustomScheduledActiveRecordModel do
5
+ it "should have a default schedule" do
6
+ subject.my_schedule.should == hourly
7
+ end
8
+
9
+ def hourly
10
+ IceCube::Schedule.new(Date.today.to_time).tap { |s|
11
+ s.add_recurrence_rule IceCube::Rule.hourly
12
+ }
13
+ end
14
+ end
15
+
16
+ describe DefaultScheduledActiveRecordModel do
17
+ alias :model :subject
18
+
19
+ it "should have a default schedule" do
20
+ subject.schedule.should be_a IceCube::Schedule
21
+ end
22
+
23
+
24
+ describe "schedule_attributes" do
25
+ it "round-trips a schedule from the database" do
26
+ model.schedule_attributes = {
27
+ "repeat"=>"1", "date"=>"",
28
+ "start_date"=>"2013-02-26", "start_time"=>"",
29
+ "end_date"=>"2016-07-07", "end_time"=>"",
30
+ "ordinal_day"=>"1", "interval"=>"3",
31
+ "ordinal_week"=>"1", "interval_unit"=>"day",
32
+ "sunday"=>"0", "monday"=>"0", "tuesday"=>"0", "wednesday"=>"0",
33
+ "thursday"=>"0", "friday"=>"0", "saturday"=>"0"
34
+ }
35
+ expected = IceCube::Schedule.new(Time.local(2013, 2, 26)) do |s|
36
+ s.rrule IceCube::Rule.daily(3).until(Time.local(2016, 7, 7))
37
+ end
38
+ model.schedule.should == expected
39
+ end
40
+
41
+ it "should deal with all_occurrences with no infinite loops" do
42
+ model.schedule_attributes = {
43
+ repeat: 1, interval: 7, interval_unit: "day",
44
+ start_date: "11/03/2000", end_date: "11/04/2000",
45
+ all_day: true
46
+ }
47
+ schedule = model.schedule
48
+
49
+ # Calling schedule_attributes should not change Schedule::Rule.
50
+ model.schedule_attributes
51
+
52
+ model.schedule.should == schedule
53
+ model.schedule.all_occurrences.size.should == 5
54
+ end
55
+ end
56
+
57
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+ require 'schedule_attributes/configuration'
3
+
4
+ describe "ScheduleAttributes" do
5
+ describe ".configure" do
6
+ it "yields a configuration instance" do
7
+ ScheduleAttributes.configure do |config|
8
+ config.should be_a ScheduleAttributes::Configuration
9
+ end
10
+ end
11
+
12
+ it "returns a configuration instance" do
13
+ ScheduleAttributes.configure.should be_a ScheduleAttributes::Configuration
14
+ end
15
+ end
16
+ end
17
+
18
+ describe ScheduleAttributes::Configuration do
19
+ describe ".time_format" do
20
+ it "has a default" do
21
+ subject.time_format.should == '%H:%M'
22
+ end
23
+
24
+ it "is settable" do
25
+ subject.time_format = '%l:%M %P'
26
+ subject.time_format.should == '%l:%M %P'
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,182 @@
1
+ require 'spec_helper'
2
+ require 'schedule_attributes/input'
3
+
4
+ describe ScheduleAttributes::Input do
5
+ let(:input) { described_class.new(RSpec.current_example.metadata[:args]) }
6
+
7
+ describe "#repeat?" do
8
+ subject(:repeat) { input.repeat? }
9
+
10
+ context 'no arguments', args: {} do
11
+ it { should be true }
12
+ end
13
+
14
+ [0, '0', 'false', 'f', 'F', 'no', 'none'].each do |cond|
15
+ context 'false', args: {repeat: cond} do
16
+ it { should be false }
17
+ end
18
+ end
19
+
20
+ [1, '1', 'true', 't', 'T', 'yes', 'whatever'].each do |cond|
21
+ context 'true', args: {repeat: cond} do
22
+ it { should be true }
23
+ end
24
+ end
25
+ end
26
+
27
+ describe "#start_time" do
28
+ subject(:start_time) { input.start_time }
29
+
30
+ context 'no arguments', args: {} do
31
+ it { should be nil }
32
+ end
33
+
34
+ context 'date argument', args: {date: '2000-12-31'} do
35
+ it { should == Time.new(2000,12,31,0,0,0) }
36
+ end
37
+
38
+ context 'start_date argument', args: {start_date: '2000-12-31'} do
39
+ it { should == Time.new(2000,12,31,0,0,0) }
40
+ end
41
+
42
+ context 'date and start_date arguments', args: {date: '2000-06-06', start_date: '2000-12-31'} do
43
+ it { should == Time.new(2000,12,31,0,0,0) }
44
+ end
45
+
46
+ context 'both dates and repeat arguments', args: {repeat: '0', date: '2000-06-06', start_date: '2000-12-31'} do
47
+ it "uses date instead of start_date when not repeating" do
48
+ should == Time.new(2000,6,6,0,0,0)
49
+ end
50
+ end
51
+
52
+ context 'start_date and start_time arguments', args: {start_date: '2000-12-31', start_time: '14:30'} do
53
+ it "combines start_date and start_time" do
54
+ should == Time.new(2000,12,31,14,30,0)
55
+ end
56
+ end
57
+
58
+ context 'start_time argument', args: {start_time: '14:00'} do
59
+ it { should == Date.today.to_time + 14.hours }
60
+ end
61
+ end
62
+
63
+ describe "#end_time" do
64
+ subject(:end_time) { input.end_time }
65
+
66
+ context 'no arguments', args: {} do
67
+ it { should be nil }
68
+ end
69
+
70
+ context 'end_time argument', args: {end_time: '14:00'} do
71
+ it { should == Date.today.to_time + 14.hours }
72
+ end
73
+
74
+ context 'start_date and end_time arguments', args: {start_date: '2000-12-31', end_time: '14:00'} do
75
+ it { should == Time.new(2000,12,31,14,0,0) }
76
+ end
77
+
78
+ context 'start_date, end_date and end_time arguments', args: {start_date: '2000-06-06', end_date: '2000-12-31', end_time: '14:00'} do
79
+ it { should == Time.new(2000,6,6,14,0,0) }
80
+ end
81
+
82
+ context 'date and end_time arguments', args: {date: '2000-12-31', end_time: '14:00'} do
83
+ it { should == Time.new(2000,12,31,14,0,0) }
84
+ end
85
+
86
+ context 'start_date and _time and end_date and _time arguments', args: {start_date: '2000-06-06', end_date: '2000-12-31', start_time: '06:00', end_time: '14:00'} do
87
+ it { should == Time.new(2000,6,6,14,0,0) }
88
+ end
89
+
90
+ context 'start_date and _time and end_date and _time arguments, 24h format', args: {start_date: '2000-06-06', end_date: '2000-12-31', start_time: '20:00', end_time: '14:00'} do
91
+ it { should == Time.new(2000,6,7,14,0,0) }
92
+ end
93
+ end
94
+
95
+ describe "#duration" do
96
+ subject(:duration) { input.duration }
97
+
98
+ context 'no arguments', args: {} do
99
+ it { should be nil }
100
+ end
101
+
102
+ context 'start_time argument', args: {start_time: '8:00'} do
103
+ it { should be nil }
104
+ end
105
+
106
+ context 'start_time and end_time arguments', args: {start_time: '8:00', end_time: '14:00'} do
107
+ it { should == 6.hours }
108
+ end
109
+ end
110
+
111
+ describe "#dates" do
112
+ subject(:dates) { input.dates }
113
+
114
+ context 'no arguments', args: {} do
115
+ it { should == [] }
116
+ end
117
+
118
+ context 'repeat argument', args: {repeat: '0'} do
119
+ it { should == [] }
120
+ end
121
+
122
+ context 'start_date argument', args: {start_date: '2000-06-06'} do
123
+ it { should == [] }
124
+ end
125
+
126
+ context 'repeat, date and start_date arguments', args: {repeat: '0', date: '2000-06-06', start_date: '2000-02-03'} do
127
+ it { should == [Time.new(2000,6,6)] }
128
+ end
129
+
130
+ context 'dates array argument', args: {dates: ['2000-01-02','2000-02-03']} do
131
+ it { should == [Time.new(2000,1,2), Time.new(2000,2,3)] }
132
+ end
133
+
134
+ context 'repeat, date, start_date and start_time arguments', args: {repeat: '0', date: '2000-06-06', start_date: '2000-06-06', start_time: '12:00'} do
135
+ it { should == [Time.new(2000,6,6,12,0)] }
136
+ end
137
+ end
138
+
139
+ describe "#ends?" do
140
+ subject(:ends) { input.ends? }
141
+
142
+ context 'repeat and end_date arguments', args: {repeat: '1', end_date: ''} do
143
+ it { should be_falsey }
144
+ end
145
+ end
146
+
147
+ describe "#yearly_start_month_day?" do
148
+ subject(:yearly_start_month_day) { input.yearly_start_month_day? }
149
+
150
+ context 'repeat and yearly_start_month_day arguments', args: {repeat: '1', yearly_start_month_day: '2'} do
151
+ it { should be_falsey }
152
+ end
153
+
154
+ context 'repeat, yearly_start_month and yearly_start_month_day arguments', args: {repeat: '1', yearly_start_month: '12', yearly_start_month_day: '2'} do
155
+ it { should be_truthy }
156
+ end
157
+
158
+ context 'repeat, yearly_start_month and yearly_start_month_day arguments', args: {repeat: '1', yearly_start_month: '12', yearly_start_month_day: '1'} do
159
+ it { should be_falsey }
160
+ end
161
+ end
162
+
163
+ describe "#yearly_end_month_day?" do
164
+ subject(:yearly_end_month_day) { input.yearly_end_month_day? }
165
+
166
+ context 'repeat and yearly_end_month_day arguments', args: {repeat: '1', yearly_end_month_day: '27'} do
167
+ it { should be_falsey }
168
+ end
169
+
170
+ context 'repeat, yearly_end_month and yearly_end_month_day arguments', args: {repeat: '1', yearly_end_month: '12', yearly_end_month_day: '27'} do
171
+ it { should be_truthy }
172
+ end
173
+
174
+ context 'repeat, yearly_end_month and yearly_end_month_day arguments', args: {repeat: '1', yearly_end_month: '12', yearly_end_month_day: '31'} do
175
+ it { should be_falsey }
176
+ end
177
+
178
+ context 'repeat, yearly_end_month and yearly_end_month_day arguments', args: {repeat: '1', yearly_end_month: '11', yearly_end_month_day: '30'} do
179
+ it { should be_falsey }
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,113 @@
1
+ require 'spec_helper'
2
+ require 'ice_cube'
3
+ require 'schedule_attributes/rule_parser'
4
+ require 'schedule_attributes/input'
5
+
6
+ describe ScheduleAttributes::RuleParser::Day do
7
+ let(:input) { ScheduleAttributes::Input.new(RSpec.current_example.metadata[:args]) }
8
+ let(:parser) { described_class.new(input) }
9
+ let(:t) { Date.current.to_time }
10
+
11
+ describe "#rule" do
12
+ subject { parser.rule }
13
+
14
+ context 'no arguments', args: {} do
15
+ let(:every_day) { [t, t+1.day, t+2.days, t+3.days, t+4.days, t+5.days, t+6.days, t+7.days, t+8.days] }
16
+
17
+ it { should == IceCube::Rule.daily }
18
+ its_occurrences_until(8.days.from_now) { should == every_day }
19
+ end
20
+
21
+ context 'interval argument', args: {interval: 2} do
22
+ let(:every_other_day) { [t, t+2.days, t+4.days, t+6.days, t+8.days] }
23
+
24
+ it { should == IceCube::Rule.daily(2) }
25
+ its_occurrences_until(8.days.from_now) { should == every_other_day }
26
+ end
27
+
28
+ context 'end_date argument', args: {end_date: '2014-01-30'} do
29
+ let(:t) { Time.new(2014,01,30) }
30
+ let(:last_5_days) { [t-4.days, t-3.days, t-2.days, t-1.day, t] }
31
+
32
+ it { should == IceCube::Rule.daily.until(t) }
33
+ its_occurrences_until(Time.new(2014,02,01)) { subject[-5..-1].should == last_5_days }
34
+ end
35
+
36
+ context 'yearly_start_month and yearly_end_month arguments, whole year', args: {yearly_start_month: 1, yearly_end_month: 12} do
37
+ it { should == IceCube::Rule.daily }
38
+ end
39
+
40
+ context 'yearly_start_month and yearly_end_month arguments, less than a year', args: {yearly_start_month: 6, yearly_end_month: 8} do
41
+ it { should == IceCube::Rule.daily.month_of_year(6,7,8) }
42
+ end
43
+
44
+ context 'yearly_start_month and yearly_end_month arguments, jump year', args: {yearly_start_month: 11, yearly_end_month: 1} do
45
+ it { should == IceCube::Rule.daily.month_of_year(11,12,1) }
46
+ end
47
+
48
+ context 'yearly_start_month and yearly_end_month arguments, jump year alt', args: {yearly_start_month: 12, yearly_end_month: 2} do
49
+ it { should == IceCube::Rule.daily.month_of_year(12,1,2) }
50
+ end
51
+
52
+ context 'yearly_start_month, yearly_start_month_day, yearly_end_month and yearly_end_month_day arguments', args: {
53
+ yearly_start_month: 3, yearly_start_month_day: 14,
54
+ yearly_end_month: 3, yearly_end_month_day: 17
55
+ } do
56
+ it { should == IceCube::Rule.daily.month_of_year(3) }
57
+ end
58
+ end
59
+
60
+ describe "#exceptions" do
61
+ subject { parser.exceptions }
62
+
63
+ context 'yearly_start_month, yearly_start_month_day, yearly_end_month and yearly_end_month_day arguments', args: {
64
+ yearly_start_month: 3, yearly_start_month_day: 4,
65
+ yearly_end_month: 3, yearly_end_month_day: 26
66
+ } do
67
+ it "returns exceptions for the leading & trailing days" do
68
+ should == [
69
+ IceCube::Rule.daily.month_of_year(3).day_of_month(*1..3),
70
+ IceCube::Rule.daily.month_of_year(3).day_of_month(*27..31)
71
+ ]
72
+ end
73
+ end
74
+
75
+ context 'yearly_start_month and yearly_end_month arguments', args: {yearly_start_month: 3, yearly_end_month: 3} do
76
+ it "has no trailing exceptions" do
77
+ should == []
78
+ end
79
+ end
80
+
81
+ context 'yearly_start_month, yearly_end_month and yearly_end_month_day arguments', args: {yearly_start_month: 3, yearly_end_month: 4, yearly_end_month_day: 25} do
82
+ it "returns exceptions for the trailing days of the last month only" do
83
+ should == [IceCube::Rule.daily.month_of_year(4).day_of_month(*26..31)]
84
+ end
85
+ end
86
+
87
+ context 'yearly_start_month, yearly_start_month_day and yearly_end_month arguments', args: {yearly_start_month: 3, yearly_start_month_day: 3, yearly_end_month: 4} do
88
+ it "returns exceptions for the leading days of the first month" do
89
+ should == [
90
+ IceCube::Rule.daily.month_of_year(3).day_of_month(*1..2)
91
+ ]
92
+ end
93
+ end
94
+
95
+ context 'yearly_start_month, yearly_end_month, yearly_start_month_day and yearly_end_month_day arguments', args: {yearly_start_month: 3, yearly_end_month: 4, yearly_start_month_day: 31, yearly_end_month_day: 1} do
96
+ it "excepts the first 30 days of first month and last 30 days of last month" do
97
+ should == [
98
+ IceCube::Rule.daily.month_of_year(3).day_of_month(*1..30),
99
+ IceCube::Rule.daily.month_of_year(4).day_of_month(*2..31)
100
+ ]
101
+ end
102
+ end
103
+
104
+ context 'yearly_start_month, yearly_end_month, yearly_start_month_day and yearly_end_month_day arguments', args: {yearly_start_month: 12, yearly_end_month: 1, yearly_start_month_day: 31, yearly_end_month_day: 1} do
105
+ it "excepts the first 30 days of first month and last 30 days of last month" do
106
+ should == [
107
+ IceCube::Rule.daily.month_of_year(12).day_of_month(*1..30),
108
+ IceCube::Rule.daily.month_of_year(1).day_of_month(*2..31)
109
+ ]
110
+ end
111
+ end
112
+ end
113
+ end