acts_as_span 0.0.5 → 1.2.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. checksums.yaml +7 -0
  2. data/.gitignore +11 -3
  3. data/.rspec +2 -0
  4. data/.tool-versions +1 -0
  5. data/.travis.yml +12 -0
  6. data/Gemfile +0 -3
  7. data/README.rdoc +4 -16
  8. data/Rakefile +5 -1
  9. data/acts_as_span.gemspec +31 -14
  10. data/config/locales/en/acts_as_span.yml +15 -0
  11. data/lib/acts_as_span.rb +69 -98
  12. data/lib/acts_as_span/end_date_propagator.rb +198 -0
  13. data/lib/acts_as_span/no_overlap_validator.rb +86 -0
  14. data/lib/acts_as_span/span_instance.rb +24 -32
  15. data/lib/acts_as_span/span_instance/status.rb +12 -27
  16. data/lib/acts_as_span/span_instance/validations.rb +11 -53
  17. data/lib/acts_as_span/span_klass.rb +11 -19
  18. data/lib/acts_as_span/span_klass/status.rb +43 -12
  19. data/lib/acts_as_span/version.rb +6 -4
  20. data/lib/acts_as_span/within_parent_date_span_validator.rb +44 -0
  21. data/spec/lib/acts_as_span_spec.rb +38 -35
  22. data/spec/lib/delegation_spec.rb +45 -78
  23. data/spec/lib/end_date_propagator_spec.rb +319 -0
  24. data/spec/lib/no_overlap_validator_spec.rb +129 -0
  25. data/spec/lib/span_instance/named_scopes_on_spec.rb +193 -193
  26. data/spec/lib/span_instance/named_scopes_spec.rb +193 -191
  27. data/spec/lib/span_instance/overlap_spec.rb +193 -253
  28. data/spec/lib/span_instance/status_spec.rb +22 -35
  29. data/spec/lib/span_instance/validations_spec.rb +8 -44
  30. data/spec/lib/span_instance_spec.rb +17 -30
  31. data/spec/lib/span_klass/status_spec.rb +38 -0
  32. data/spec/lib/within_parent_date_span_validator_spec.rb +126 -0
  33. data/spec/spec_helper.rb +19 -6
  34. data/spec/spec_models.rb +226 -0
  35. metadata +167 -61
  36. data/Gemfile.lock +0 -47
  37. data/lib/acts_as_span/span_instance/overlap.rb +0 -17
  38. data/lib/acts_as_span/span_klass/overlap.rb +0 -21
  39. data/spec/lib/negative_spec.rb +0 -30
  40. data/spec/spec.opts +0 -1
@@ -1,49 +1,36 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe "Span" do
4
- before(:each) do
5
- build_model :span_model do
6
- date :start_date
7
- date :end_date
8
-
9
- acts_as_span
10
- end
11
- end
12
-
13
- let(:span_model) { SpanModel.new(:start_date => Date.today, :end_date => nil) }
3
+ RSpec.describe "Span" do
4
+ let(:span_model) { SpanModel.new(:start_date => Date.current, :end_date => nil) }
14
5
  let(:span) { span_model.span }
15
-
16
- context "#span_status & #span_status_to_s" do
6
+
7
+ context "#span_status" do
17
8
  before(:each) do
18
- span.stub!(:current?).and_return(false)
19
- span.stub!(:future?).and_return(false)
20
- span.stub!(:expired?).and_return(false)
9
+ allow(span).to receive(:current?).and_return(false)
10
+ allow(span).to receive(:future?).and_return(false)
11
+ allow(span).to receive(:expired?).and_return(false)
21
12
  end
22
-
13
+
23
14
  it "should return :unknown when all_conditions == false" do
24
- span.span_status.should == :unknown
25
- span.span_status_to_s.should == 'Unknown'
15
+ expect(span.span_status).to eq(:unknown)
26
16
  end
27
-
17
+
28
18
  it "should return :current when current? == true" do
29
- span.should_receive(:current?).twice.and_return(true)
30
-
31
- span.span_status.should == :current
32
- span.span_status_to_s.should == 'Current'
19
+ expect(span).to receive(:current?).once.and_return(true)
20
+
21
+ expect(span.span_status).to eq(:current)
33
22
  end
34
-
23
+
35
24
  it "should return :current when future? == true" do
36
- span.should_receive(:future?).twice.and_return(true)
37
-
38
- span.span_status.should == :future
39
- span.span_status_to_s.should == 'Future'
25
+ expect(span).to receive(:future?).once.and_return(true)
26
+
27
+ expect(span.span_status).to eq(:future)
40
28
  end
41
-
29
+
42
30
  it "should return :current when expired? == true" do
43
- span.should_receive(:expired?).twice.and_return(true)
44
-
45
- span.span_status.should == :expired
46
- span.span_status_to_s.should == 'Expired'
31
+ expect(span).to receive(:expired?).once.and_return(true)
32
+
33
+ expect(span.span_status).to eq(:expired)
47
34
  end
48
35
  end
49
- end
36
+ end
@@ -1,54 +1,18 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe "Span" do
4
- before(:each) do
5
- build_model :span_model do
6
- date :start_date
7
- date :end_date
8
- end
9
- end
10
-
11
- #let(:span_model) { SpanModel.new(:start_date => Date.today, :end_date => Date.today + 1) }
12
- #let(:span) { span_model.span }
13
-
3
+ RSpec.describe "Span" do
14
4
  it "should be valid" do
15
5
  SpanModel.acts_as_span
16
6
  span_model = SpanModel.new(:start_date => nil, :end_date => nil)
17
-
18
- span_model.should be_valid
19
- end
20
-
21
- context ":start_date_field_required => true" do
22
- before do
23
- SpanModel.acts_as_span :start_date_field_required => true
24
- end
25
-
26
- it "should require a start_date" do
27
- span_model = SpanModel.new(:start_date => nil, :end_date => Date.today + 1)
28
-
29
- span_model.should_not be_valid
30
- span_model.errors[:start_date].should have(1).error
31
- end
32
- end
33
-
34
- context ":end_date_field_required => true" do
35
- before do
36
- SpanModel.acts_as_span :end_date_field_required => true
37
- end
38
-
39
- it "should require an end_date" do
40
- span_model = SpanModel.new(:start_date => Date.today, :end_date => nil)
41
-
42
- span_model.should_not be_valid
43
- span_model.errors[:end_date].should have(1).error
44
- end
7
+
8
+ expect(span_model).to be_valid
45
9
  end
46
-
10
+
47
11
  it "should require a start_date before the end_date" do
48
12
  SpanModel.acts_as_span
49
- span_model = SpanModel.new(:start_date => Date.today, :end_date => Date.today - 1)
50
-
51
- span_model.should_not be_valid
52
- span_model.errors[:end_date].should have(1).error
13
+ span_model = SpanModel.new(:start_date => Date.current, :end_date => Date.current - 1)
14
+
15
+ expect(span_model).not_to be_valid
16
+ expect(span_model.errors[:end_date].size).to eq(1)
53
17
  end
54
18
  end
@@ -1,41 +1,28 @@
1
1
  require 'spec_helper'
2
2
 
3
- #NOTE: we're also testing :start_date_field & :end_date_field options work
4
-
5
- describe "Span" do
6
- before(:each) do
7
- build_model :span_model do
8
- date :start_date_x
9
- date :end_date_x
10
-
11
- acts_as_span :start_date_field => :start_date_x,
12
- :end_date_field => :end_date_x
13
- end
14
- end
15
-
16
- context "#close!" do
17
- let(:span_model) { SpanModel.new(:start_date_x => Date.today, :end_date_x => nil) }
18
- let(:span) { span_model.span }
19
-
20
- it "should set end_date? to today" do
21
- lambda { span.close! }.should change(span_model, :end_date_x).from(nil).to(Date.today)
22
- end
23
-
24
- it "should set end_date? to the parameter" do
25
- lambda { span.close_on!(Date.today + 1.day) }.should change(span_model, :end_date_x).from(nil).to(Date.today + 1.day)
26
- end
27
- end
28
-
3
+ RSpec.describe "Span" do
29
4
  context "start_date & end_date" do
30
- let(:span_model) { SpanModel.new(:start_date_x => Date.today, :end_date_x => Date.today + 1) }
5
+ let(:span_model) { SpanModel.new(:start_date => Date.current, :end_date => Date.current + 1) }
31
6
  let(:span) { span_model.span }
32
7
 
33
8
  it "should return the start_date" do
34
- span.start_date.should == span_model.start_date_x
9
+ expect(span.start_date).to eq(span_model.start_date)
35
10
  end
36
11
 
37
12
  it "should return the end_date" do
38
- span.end_date.should == span_model.end_date_x
13
+ expect(span.end_date).to eq(span_model.end_date)
14
+ end
15
+
16
+ context "changed?" do
17
+ it 'returns true when start_date has changed' do
18
+ span_model.start_date = span_model.start_date + 5
19
+ expect(span.start_date_changed?).to eq(true)
20
+ end
21
+
22
+ it 'returns true when end_date has changed' do
23
+ span_model.end_date = span_model.end_date + 5
24
+ expect(span.end_date_changed?).to eq(true)
25
+ end
39
26
  end
40
27
  end
41
- end
28
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe ActsAsSpan::SpanKlass::Status do
6
+ let(:span_klass) { ::SpanModel }
7
+
8
+ let!(:today) { Date.current }
9
+
10
+ describe '.current' do
11
+ let!(:record) do
12
+ span_klass.create(
13
+ start_date: today - 1.week,
14
+ end_date: today + 1.week,
15
+ )
16
+ end
17
+
18
+ subject { span_klass.current(query_date) }
19
+
20
+ context "when query_date is within the record's span" do
21
+ let(:query_date) { record.start_date + 3.days }
22
+
23
+ it { is_expected.not_to be_empty }
24
+ end
25
+
26
+ context "when query_date is before the record's span" do
27
+ let(:query_date) { record.start_date - 1.week }
28
+
29
+ it { is_expected.to be_empty }
30
+ end
31
+
32
+ context "when query_date is after the record's span" do
33
+ let(:query_date) { record.end_date + 1.week }
34
+
35
+ it { is_expected.to be_empty }
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,126 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe ActsAsSpan::WithinParentDateSpanValidator do
4
+ let(:new_child) do
5
+ child_class.new(
6
+ start_date: new_child_start_date,
7
+ end_date: new_child_end_date,
8
+ mama: mama)
9
+ end
10
+
11
+ let!(:mama) { Mama.create(start_date: mama_start_date, end_date: mama_end_date) }
12
+ let!(:papa) { Papa.create(start_date: papa_start_date, end_date: papa_end_date) }
13
+
14
+ let(:thirty_years_ago) { Date.current - 30.years }
15
+ let(:seventy_years_in_the_future) { Date.current + 70.years }
16
+
17
+ let(:mama_start_date) { thirty_years_ago }
18
+ let(:mama_end_date) { seventy_years_in_the_future }
19
+
20
+ let(:papa_start_date) { thirty_years_ago + 1.year }
21
+ let(:papa_end_date) { seventy_years_in_the_future - 1.year }
22
+
23
+ describe 'an object that validates against a single parent' do
24
+ let(:child_class) { OneParentChild }
25
+
26
+ context 'inside parent date span' do
27
+ let(:new_child_start_date) { mama_start_date }
28
+ let(:new_child_end_date) { mama_end_date }
29
+
30
+ it do
31
+ expect(new_child).to be_valid
32
+ end
33
+ end
34
+
35
+ context 'child_record_without_start_date' do
36
+ let(:new_child_start_date) { nil }
37
+ let(:new_child_end_date) { mama_end_date }
38
+
39
+ it do
40
+ expect(new_child).to be_invalid
41
+ end
42
+ end
43
+
44
+ context 'child_record_without_start_date' do
45
+ let(:new_child_start_date) { mama_start_date }
46
+ let(:new_child_end_date) { nil }
47
+
48
+ it do
49
+ expect(new_child).to be_invalid
50
+ end
51
+ end
52
+
53
+ context 'child_record_started_before_parent_record' do
54
+ let(:new_child_start_date) { mama_start_date - 1 }
55
+ let(:new_child_end_date) { mama_end_date }
56
+
57
+ it do
58
+ expect(new_child).to be_invalid
59
+ end
60
+ end
61
+
62
+ context 'child_record_ended_after_parent_record' do
63
+ let(:new_child_start_date) { mama_start_date }
64
+ let(:new_child_end_date) { mama_end_date + 1 }
65
+
66
+ it do
67
+ expect(new_child).to be_invalid
68
+ end
69
+ end
70
+
71
+ context 'with unassigned parent start_date' do
72
+ let(:new_child_start_date) { thirty_years_ago }
73
+ let(:new_child_end_date) { mama_end_date }
74
+ let(:mama_start_date) { nil }
75
+
76
+ it do
77
+ expect(new_child).to be_valid
78
+ end
79
+ end
80
+
81
+ context 'with unassigned parent end_date' do
82
+ let(:new_child_start_date) { mama_start_date }
83
+ let(:new_child_end_date) { seventy_years_in_the_future }
84
+ let(:mama_end_date) { nil }
85
+
86
+ it do
87
+ expect(new_child).to be_valid
88
+ end
89
+ end
90
+ end
91
+
92
+ describe 'an object with multiple parents' do
93
+ let(:child_class) { TwoParentChild }
94
+
95
+ before { new_child.papa = papa }
96
+
97
+ context 'valid cases' do
98
+ let(:new_child_start_date) { papa_start_date }
99
+ let(:new_child_end_date) { papa_end_date }
100
+
101
+ it 'allows date spans that lie within all respective parental date spans' do
102
+ expect(new_child).to be_valid
103
+ end
104
+ end
105
+
106
+ context 'invalid cases' do
107
+ let(:new_child_start_date) { papa_start_date - 1 }
108
+ let(:new_child_end_date) { papa_end_date + 1 }
109
+
110
+ it 'does not allow date spans that lie outside of respective parental date spans' do
111
+ expect(new_child).not_to be_valid
112
+ end
113
+ end
114
+ end
115
+
116
+ describe 'error messages' do
117
+ let(:child_class) { OneParentChildCustom }
118
+ let(:new_child_start_date) { mama_start_date - 3.days }
119
+ let(:new_child_end_date) { mama_end_date + 5.days }
120
+
121
+ it 'allows using custom error messages' do
122
+ expect(new_child).not_to be_valid
123
+ expect(new_child.errors.messages[:base]).to include('Custom error message')
124
+ end
125
+ end
126
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,10 +1,23 @@
1
- require 'rubygems'
2
1
  require 'bundler/setup'
3
- require 'acts_as_fu'
4
- require 'active_support'
5
-
6
2
  require 'acts_as_span'
3
+ require 'pry'
4
+
5
+ require 'active_record'
6
+ require 'active_support'
7
+ require 'spec_models'
7
8
 
8
9
  RSpec.configure do |config|
9
- config.include ActsAsFu
10
- end
10
+ # Enable flags like --only-failures and --next-failure
11
+ config.example_status_persistence_file_path = ".rspec_status"
12
+
13
+ # Disable RSpec exposing methods globally on `Module` and `main`
14
+ config.disable_monkey_patching!
15
+
16
+ config.expect_with :rspec do |c|
17
+ c.syntax = :expect
18
+ end
19
+
20
+ config.after do
21
+ Temping.cleanup
22
+ end
23
+ end
@@ -0,0 +1,226 @@
1
+ require 'temping'
2
+ require 'has_siblings'
3
+
4
+ ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
5
+ ActiveRecord::Base.logger = Logger.new(STDOUT) if ENV["DEBUG"]
6
+
7
+ Temping.create :spannable_model do
8
+ with_columns do |t|
9
+ t.date :starting_date
10
+ t.date :ending_date
11
+
12
+ t.integer :unique_by_date_range
13
+ end
14
+ end
15
+
16
+ Temping.create :span_model do
17
+ with_columns do |t|
18
+ t.date :start_date
19
+ t.date :end_date
20
+ end
21
+
22
+ acts_as_span
23
+ end
24
+
25
+ Temping.create :one_parent_child do
26
+ with_columns do |t|
27
+ t.belongs_to :mama
28
+
29
+ t.date :start_date
30
+ t.date :end_date
31
+
32
+ # every one-parent child is a favorite! ...by default
33
+ t.boolean :favorite, default: true
34
+ end
35
+
36
+ acts_as_span
37
+
38
+ def favorite?
39
+ favorite
40
+ end
41
+
42
+ belongs_to :mama
43
+ has_siblings through: [:mama]
44
+
45
+ validates_with ActsAsSpan::NoOverlapValidator,
46
+ scope: proc { siblings }, instance_scope: proc { favorite? }
47
+ validates_with ActsAsSpan::WithinParentDateSpanValidator, parents: [:mama]
48
+ end
49
+
50
+ Temping.create :one_parent_child_custom do
51
+ with_columns do |t|
52
+ t.belongs_to :mama
53
+
54
+ t.date :start_date
55
+ t.date :end_date
56
+
57
+ # every one-parent child is a favorite! ...by default
58
+ t.boolean :favorite, default: true
59
+ end
60
+
61
+ acts_as_span
62
+
63
+ def favorite?
64
+ favorite
65
+ end
66
+
67
+ belongs_to :mama
68
+ has_siblings through: [:mama]
69
+
70
+ validates_with ActsAsSpan::NoOverlapValidator,
71
+ scope: proc { siblings }, instance_scope: proc { favorite? }, message: 'Custom error message'
72
+ validates_with ActsAsSpan::WithinParentDateSpanValidator, parents: [:mama], message: 'Custom error message'
73
+ end
74
+
75
+
76
+ Temping.create :two_parent_child do
77
+ with_columns do |t|
78
+ t.belongs_to :mama
79
+ t.belongs_to :papa
80
+
81
+ t.date :start_date
82
+ t.date :end_date
83
+ end
84
+
85
+ acts_as_span
86
+
87
+ belongs_to :mama
88
+ belongs_to :papa
89
+ has_siblings through: [:mama, :papa]
90
+
91
+ validates_with ActsAsSpan::NoOverlapValidator, scope: proc { siblings }
92
+ validates_with ActsAsSpan::WithinParentDateSpanValidator, parents: [:mama, :papa]
93
+ end
94
+
95
+ Temping.create :mama do
96
+ with_columns do |t|
97
+ t.date :start_date
98
+ t.date :end_date
99
+ end
100
+
101
+ acts_as_span
102
+
103
+ has_many :one_parent_children
104
+ has_many :two_parent_children
105
+ has_many :one_parent_child_customs
106
+ end
107
+
108
+ Temping.create :papa do
109
+ with_columns do |t|
110
+ t.date :start_date
111
+ t.date :end_date
112
+ end
113
+
114
+ acts_as_span
115
+
116
+ has_many :one_parent_children
117
+ end
118
+
119
+ # fulfill association requirements for EndDatePropagator
120
+ Temping.create :base do
121
+ has_many :children, dependent: :destroy
122
+ has_many :dogs, dependent: :destroy
123
+ has_many :birds, through: :children
124
+ has_many :tales, dependent: :destroy
125
+
126
+ acts_as_span
127
+
128
+ with_columns do |t|
129
+ t.date :end_date
130
+ t.date :start_date
131
+ end
132
+ end
133
+
134
+ Temping.create :cat_owner do
135
+ has_many :cats, dependent: :destroy
136
+
137
+ acts_as_span
138
+
139
+ with_columns do |t|
140
+ t.date :start_date
141
+ t.date :end_date
142
+ end
143
+ end
144
+
145
+ Temping.create :cat do
146
+ belongs_to :cat_owner
147
+
148
+ with_columns do |t|
149
+ t.belongs_to :cat_owner
150
+ end
151
+ end
152
+
153
+ Temping.create :other_base do
154
+ has_many :children, dependent: :destroy
155
+
156
+ acts_as_span
157
+
158
+ with_columns do |t|
159
+ t.date :end_date
160
+ t.date :start_date
161
+ end
162
+ end
163
+
164
+ # has non-standard start_ and end_field names
165
+ Temping.create :child do
166
+ belongs_to :base
167
+ belongs_to :other_base
168
+ has_many :birds, dependent: :destroy
169
+
170
+ validates_with ActsAsSpan::WithinParentDateSpanValidator,
171
+ parents: [:base]
172
+
173
+ acts_as_span(
174
+ start_field: :date_of_birth,
175
+ end_field: :emancipation_date
176
+ )
177
+
178
+ with_columns do |t|
179
+ t.date :date_of_birth
180
+ t.date :emancipation_date
181
+ t.string :manual_invalidation
182
+ t.belongs_to :base
183
+ end
184
+ end
185
+
186
+ Temping.create :dog do
187
+ belongs_to :base
188
+
189
+ acts_as_span
190
+
191
+ with_columns do |t|
192
+ t.date :start_date
193
+ t.date :end_date
194
+ t.belongs_to :base
195
+ end
196
+ end
197
+
198
+
199
+ Temping.create :bird do
200
+ belongs_to :child
201
+
202
+ validates_with ActsAsSpan::WithinParentDateSpanValidator,
203
+ parents: [:child]
204
+
205
+ acts_as_span
206
+
207
+ with_columns do |t|
208
+ t.date :end_date
209
+ t.date :start_date
210
+ t.belongs_to :child
211
+ end
212
+ end
213
+
214
+ Temping.create :tale do
215
+ belongs_to :base
216
+
217
+ acts_as_span
218
+
219
+ with_columns do |t|
220
+ t.date :end_date
221
+ t.date :start_date
222
+ t.belongs_to :base
223
+ end
224
+ end
225
+
226
+