schedule_attributes 0.3.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 (40) hide show
  1. data/.gitignore +4 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +6 -0
  4. data/Gemfile.lock +57 -0
  5. data/README.markdown +69 -0
  6. data/Rakefile +7 -0
  7. data/lib/schedule_attributes.rb +18 -0
  8. data/lib/schedule_attributes/active_record.rb +50 -0
  9. data/lib/schedule_attributes/configuration.rb +22 -0
  10. data/lib/schedule_attributes/core.rb +144 -0
  11. data/lib/schedule_attributes/extensions/ice_cube.rb +11 -0
  12. data/lib/schedule_attributes/form_builder.rb +42 -0
  13. data/lib/schedule_attributes/input.rb +151 -0
  14. data/lib/schedule_attributes/model.rb +24 -0
  15. data/lib/schedule_attributes/railtie.rb +13 -0
  16. data/lib/schedule_attributes/rule_parser.rb +25 -0
  17. data/lib/schedule_attributes/rule_parser/base.rb +96 -0
  18. data/lib/schedule_attributes/rule_parser/day.rb +13 -0
  19. data/lib/schedule_attributes/rule_parser/month.rb +40 -0
  20. data/lib/schedule_attributes/rule_parser/week.rb +18 -0
  21. data/lib/schedule_attributes/rule_parser/year.rb +21 -0
  22. data/lib/schedule_attributes/serializer.rb +30 -0
  23. data/lib/schedule_attributes/time_helpers.rb +31 -0
  24. data/lib/schedule_attributes/version.rb +3 -0
  25. data/schedule_attributes.gemspec +28 -0
  26. data/spec/active_record_integration_spec.rb +45 -0
  27. data/spec/schedule_attributes/configuration_spec.rb +25 -0
  28. data/spec/schedule_attributes/input_spec.rb +174 -0
  29. data/spec/schedule_attributes/rule_parser/day_spec.rb +113 -0
  30. data/spec/schedule_attributes/rule_parser/month_spec.rb +47 -0
  31. data/spec/schedule_attributes/rule_parser/week_spec.rb +36 -0
  32. data/spec/schedule_attributes/rule_parser/year_spec.rb +53 -0
  33. data/spec/schedule_attributes/rule_parser_spec.rb +18 -0
  34. data/spec/schedule_attributes/time_helpers_spec.rb +39 -0
  35. data/spec/schedule_attributes_spec.rb +219 -0
  36. data/spec/spec_helper.rb +11 -0
  37. data/spec/support/parser_macros.rb +27 -0
  38. data/spec/support/scheduled_active_record_model.rb +42 -0
  39. data/spec/support/scheduled_model.rb +16 -0
  40. metadata +218 -0
@@ -0,0 +1,3 @@
1
+ module ScheduleAttributes
2
+ VERSION = "0.3.0"
3
+ end
@@ -0,0 +1,28 @@
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 = "schedule_attributes"
7
+ s.version = ScheduleAttributes::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Andrew Vit", "Mike Nicholaides"]
10
+ s.email = ["andrew@avit.ca", "mike@ablegray.com"]
11
+ s.homepage = "https://github.com/avit/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.10.0'
21
+ s.add_dependency 'activesupport'
22
+ s.add_dependency 'tzinfo' # this should be an activesupport dependency!
23
+
24
+ s.add_development_dependency 'rspec'
25
+ s.add_development_dependency 'pry'
26
+ s.add_development_dependency 'activerecord'
27
+ s.add_development_dependency 'sqlite3'
28
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+ require 'support/scheduled_active_record_model'
3
+
4
+
5
+ describe CustomScheduledActiveRecordModel do
6
+
7
+ it "should have a default schedule" do
8
+ subject.my_schedule.should == hourly
9
+ end
10
+
11
+ def hourly
12
+ IceCube::Schedule.new(Date.today.to_time).tap { |s|
13
+ s.add_recurrence_rule IceCube::Rule.hourly
14
+ }
15
+ end
16
+
17
+ end
18
+
19
+ describe DefaultScheduledActiveRecordModel do
20
+ alias :model :subject
21
+
22
+ it "should have a default schedule" do
23
+ subject.schedule.should be_a IceCube::Schedule
24
+ end
25
+
26
+
27
+ describe "schedule_attributes" do
28
+ it "round-trips a schedule from the database" do
29
+ model.schedule_attributes = {
30
+ "repeat"=>"1", "date"=>"",
31
+ "start_date"=>"2013-02-26", "start_time"=>"",
32
+ "end_date"=>"2016-07-07", "end_time"=>"",
33
+ "ordinal_day"=>"1", "interval"=>"3",
34
+ "ordinal_week"=>"1", "interval_unit"=>"day",
35
+ "sunday"=>"0", "monday"=>"0", "tuesday"=>"0", "wednesday"=>"0",
36
+ "thursday"=>"0", "friday"=>"0", "saturday"=>"0"
37
+ }
38
+ expected = IceCube::Schedule.new(Time.local(2013, 2, 26)) do |s|
39
+ s.rrule IceCube::Rule.daily(3).until(Time.local(2016, 7, 7))
40
+ end
41
+ model.schedule.should == expected
42
+ end
43
+ end
44
+
45
+ end
@@ -0,0 +1,25 @@
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
+ its(:time_format) { should == '%H:%M' }
20
+
21
+ it "#time_format is settable" do
22
+ subject.time_format = '%l:%M %P'
23
+ subject.time_format.should == '%l:%M %P'
24
+ end
25
+ end
@@ -0,0 +1,174 @@
1
+ require 'spec_helper'
2
+ require 'schedule_attributes/input'
3
+
4
+ describe ScheduleAttributes::Input do
5
+ let(:input) { described_class.new(example.metadata[:args]) }
6
+
7
+ describe "#repeat?" do
8
+ subject(:repeat) { input.repeat? }
9
+
10
+ context args: {} do
11
+ it { should be true }
12
+ end
13
+
14
+ [0, '0', 'false', 'f', 'F', 'no', 'none'].each do |cond|
15
+ context 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 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 args: {} do
31
+ it { should be nil }
32
+ end
33
+
34
+ context args: {date: '2000-12-31'} do
35
+ it { should == Time.new(2000,12,31,0,0,0) }
36
+ end
37
+
38
+ context args: {start_date: '2000-12-31'} do
39
+ it { should == Time.new(2000,12,31,0,0,0) }
40
+ end
41
+
42
+ context 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 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 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 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 args: {} do
67
+ it { should be nil }
68
+ end
69
+
70
+ context args: {end_time: '14:00'} do
71
+ it { should == Date.today.to_time + 14.hours }
72
+ end
73
+
74
+ context 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 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 args: {date: '2000-12-31', end_time: '14:00'} do
83
+ it { should == Time.new(2000,12,31,14,0,0) }
84
+ end
85
+ end
86
+
87
+ describe "#duration" do
88
+ subject(:duration) { input.duration }
89
+
90
+ context args: {} do
91
+ it { should be nil }
92
+ end
93
+
94
+ context args: {start_time: '8:00'} do
95
+ it { should be nil }
96
+ end
97
+
98
+ context args: {start_time: '8:00', end_time: '14:00'} do
99
+ it { should == 6.hours }
100
+ end
101
+ end
102
+
103
+ describe "#dates" do
104
+ subject(:dates) { input.dates }
105
+
106
+ context args: {} do
107
+ it { should == [] }
108
+ end
109
+
110
+ context args: {repeat: '0'} do
111
+ it { should == [] }
112
+ end
113
+
114
+ context args: {start_date: '2000-06-06'} do
115
+ it { should == [] }
116
+ end
117
+
118
+ context args: {repeat: '0', date: '2000-06-06', start_date: '2000-02-03'} do
119
+ it { should == [Time.new(2000,6,6)] }
120
+ end
121
+
122
+ context args: {dates: ['2000-01-02','2000-02-03']} do
123
+ it { should == [Time.new(2000,1,2), Time.new(2000,2,3)] }
124
+ end
125
+
126
+ context args: {repeat: '0', date: '2000-06-06', start_date: '2000-06-06', start_time: '12:00'} do
127
+ it { should == [Time.new(2000,6,6,12,0)] }
128
+ end
129
+ end
130
+
131
+ describe "#ends?" do
132
+ subject(:ends) { input.ends? }
133
+
134
+ context args: {repeat: '1', end_date: ''} do
135
+ it { should be_false }
136
+ end
137
+ end
138
+
139
+ describe "#yearly_start_month_day?" do
140
+ subject(:yearly_start_month_day) { input.yearly_start_month_day? }
141
+
142
+ context args: {repeat: '1', yearly_start_month_day: '2'} do
143
+ it { should be_false }
144
+ end
145
+
146
+ context args: {repeat: '1', yearly_start_month: '12', yearly_start_month_day: '2'} do
147
+ it { should be_true }
148
+ end
149
+
150
+ context args: {repeat: '1', yearly_start_month: '12', yearly_start_month_day: '1'} do
151
+ it { should be_false }
152
+ end
153
+ end
154
+
155
+ describe "#yearly_end_month_day?" do
156
+ subject(:yearly_end_month_day) { input.yearly_end_month_day? }
157
+
158
+ context args: {repeat: '1', yearly_end_month_day: '27'} do
159
+ it { should be_false }
160
+ end
161
+
162
+ context args: {repeat: '1', yearly_end_month: '12', yearly_end_month_day: '27'} do
163
+ it { should be_true }
164
+ end
165
+
166
+ context args: {repeat: '1', yearly_end_month: '12', yearly_end_month_day: '31'} do
167
+ it { should be_false }
168
+ end
169
+
170
+ context args: {repeat: '1', yearly_end_month: '11', yearly_end_month_day: '30'} do
171
+ it { should be_false }
172
+ end
173
+ end
174
+ 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(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 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 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 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 args: {yearly_start_month: 1, yearly_end_month: 12} do
37
+ it { should == IceCube::Rule.daily }
38
+ end
39
+
40
+ context 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 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 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 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 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 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 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 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 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 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
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+ require 'ice_cube'
3
+ require 'schedule_attributes/rule_parser'
4
+ require 'schedule_attributes/input'
5
+
6
+ describe ScheduleAttributes::RuleParser::Month do
7
+
8
+ describe "#rule" do
9
+ let(:input) { ScheduleAttributes::Input.new(example.metadata[:args]) }
10
+ let(:parser) { described_class.new(input) }
11
+ subject { parser.rule }
12
+
13
+ let(:t) { Date.current }
14
+ let(:n) { (t-14 >> 1).change(day: 14) }
15
+
16
+ let(:monthly) { [t, t >> 1, t >> 2, t >> 3].map(&:to_time) }
17
+ let(:bimonthly) { [t, t >> 2, t >> 4, t >> 6].map(&:to_time) }
18
+ let(:every_14th) { [n, n >> 1, n >> 2, n >> 3].map(&:to_time) }
19
+
20
+ context args: {} do
21
+ it { should == IceCube::Rule.monthly }
22
+ its_occurrences_until(3.months.from_now) { should == monthly }
23
+ end
24
+
25
+ context args: {"start_date" => "2000-03-14", "interval" => "2"} do
26
+ it { should == IceCube::Rule.monthly(2) }
27
+ its_occurrences_until(6.months.from_now) { should == bimonthly }
28
+ end
29
+
30
+ context args: {"ordinal_unit" => "day", "ordinal_day" => "14"} do
31
+ it { should == IceCube::Rule.monthly.day_of_month(14) }
32
+ its_occurrences_until(4.months.from_now) { should == every_14th }
33
+ end
34
+
35
+ context args: {"ordinal_unit" => "week", "ordinal_week" => "2", "tuesday" => "1"} do
36
+ it { should == IceCube::Rule.monthly.day_of_week(:tuesday => [2]) }
37
+ end
38
+
39
+ context args: {"ordinal_unit" => "week", "ordinal_week" => "2"} do
40
+ it { should == IceCube::Rule.monthly.day_of_week(0=>(w=[2]), 1=>w, 2=>w, 3=>w, 4=>w, 5=>w, 6=>w) }
41
+ end
42
+
43
+ context args: {"start_date" => "2000-03-14", "end_date" => "2000-06-14"} do
44
+ it { should == IceCube::Rule.monthly.until(Time.new(2000,6,14)) }
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+ require 'ice_cube'
3
+ require 'schedule_attributes/rule_parser'
4
+ require 'schedule_attributes/input'
5
+
6
+ describe ScheduleAttributes::RuleParser::Week do
7
+ let(:t) { Date.current.to_time }
8
+ let(:sun) { (Date.current - Date.current.cwday.days).to_time + 1.week }
9
+ let(:mon) { sun + 1.day }
10
+ let(:sat) { sun - 1.day }
11
+
12
+ let(:weekly) { [t, t+1.week, t+2.weeks, t+3.weeks, t+4.weeks, t+5.weeks] }
13
+ let(:every_2_weeks) { [t, t+2.week, t+4.weeks]}
14
+ let(:weekends) { [sat, sun, sat+1.week, sun+1.week, sat+2.weeks, sun+2.weeks] }
15
+
16
+ describe "#rule" do
17
+ let(:input) { ScheduleAttributes::Input.new(example.metadata[:args]) }
18
+ let(:parser) { described_class.new(input) }
19
+ subject { parser.rule }
20
+
21
+ context args: {} do
22
+ it { should == IceCube::Rule.weekly }
23
+ its_occurrences_until(5.weeks.from_now) { should == weekly }
24
+ end
25
+
26
+ context args: {"interval" => "2"} do
27
+ it { should == IceCube::Rule.weekly(2) }
28
+ its_occurrences_until(5.weeks.from_now) { should == every_2_weeks }
29
+ end
30
+
31
+ context args: {"monday" => "0", "saturday" => "1", "sunday" => "1"} do
32
+ it { should == IceCube::Rule.weekly.day(0,6) }
33
+ its_occurrences_until(Date.today.beginning_of_week+3.weeks) { subject[-4..-1].should == weekends[-4..-1] }
34
+ end
35
+ end
36
+ end