acts_as_span 0.0.5 → 0.0.6
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.
- checksums.yaml +7 -0
- data/.gitignore +11 -3
- data/.rspec +2 -0
- data/.travis.yml +6 -0
- data/Gemfile +0 -3
- data/README.rdoc +4 -16
- data/Rakefile +5 -1
- data/acts_as_span.gemspec +21 -5
- data/lib/acts_as_span.rb +55 -98
- data/lib/acts_as_span/no_overlap_validator.rb +51 -0
- data/lib/acts_as_span/span_instance.rb +17 -33
- data/lib/acts_as_span/span_instance/status.rb +12 -27
- data/lib/acts_as_span/span_instance/validations.rb +5 -53
- data/lib/acts_as_span/span_klass.rb +11 -19
- data/lib/acts_as_span/span_klass/status.rb +33 -12
- data/lib/acts_as_span/version.rb +2 -2
- data/lib/acts_as_span/within_parent_date_span_validator.rb +42 -0
- data/spec/lib/acts_as_span_spec.rb +18 -36
- data/spec/lib/delegation_spec.rb +45 -78
- data/spec/lib/no_overlap_validator_spec.rb +96 -0
- data/spec/lib/span_instance/named_scopes_on_spec.rb +193 -193
- data/spec/lib/span_instance/named_scopes_spec.rb +193 -191
- data/spec/lib/span_instance/overlap_spec.rb +193 -253
- data/spec/lib/span_instance/status_spec.rb +22 -35
- data/spec/lib/span_instance/validations_spec.rb +8 -44
- data/spec/lib/span_instance_spec.rb +5 -30
- data/spec/lib/within_parent_date_span_validator_spec.rb +115 -0
- data/spec/spec_helper.rb +19 -6
- data/spec/spec_models.rb +69 -0
- metadata +158 -58
- data/Gemfile.lock +0 -47
- data/lib/acts_as_span/span_instance/overlap.rb +0 -17
- data/lib/acts_as_span/span_klass/overlap.rb +0 -21
- data/spec/lib/negative_spec.rb +0 -30
- data/spec/spec.opts +0 -1
@@ -1,12 +1,10 @@
|
|
1
|
-
require 'active_support'
|
2
|
-
|
3
1
|
module ActsAsSpan
|
4
2
|
class SpanInstance
|
5
3
|
module Status
|
6
4
|
extend ActiveSupport::Concern
|
7
|
-
|
8
|
-
|
9
|
-
def span_status(query_date = Date.
|
5
|
+
|
6
|
+
included do
|
7
|
+
def span_status(query_date = Date.current)
|
10
8
|
if future?(query_date)
|
11
9
|
:future
|
12
10
|
elsif expired?(query_date)
|
@@ -20,39 +18,26 @@ module ActsAsSpan
|
|
20
18
|
|
21
19
|
alias_method :span_status_on, :span_status
|
22
20
|
|
23
|
-
def
|
24
|
-
case span_status(query_date)
|
25
|
-
when :future
|
26
|
-
"Future"
|
27
|
-
when :expired
|
28
|
-
"Expired"
|
29
|
-
when :current
|
30
|
-
"Current"
|
31
|
-
when :unknown
|
32
|
-
"Unknown"
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
alias_method :span_status_to_s_on, :span_status_to_s
|
37
|
-
|
38
|
-
def current?(query_date = Date.today)
|
21
|
+
def current?(query_date = Date.current)
|
39
22
|
!future?(query_date) && !expired?(query_date)
|
40
23
|
end
|
41
|
-
|
24
|
+
|
42
25
|
alias_method :current_on?, :current?
|
43
26
|
|
44
|
-
def future?(query_date = Date.
|
27
|
+
def future?(query_date = Date.current)
|
45
28
|
start_date && start_date > query_date
|
46
29
|
end
|
47
|
-
|
30
|
+
|
48
31
|
alias_method :future_on?, :future?
|
49
32
|
|
50
|
-
def expired?(query_date = Date.
|
33
|
+
def expired?(query_date = Date.current)
|
51
34
|
end_date && end_date < query_date
|
52
35
|
end
|
53
|
-
|
36
|
+
|
54
37
|
alias_method :expired_on?, :expired?
|
38
|
+
alias_method :past_on?, :expired?
|
39
|
+
alias_method :past?, :expired?
|
55
40
|
end
|
56
41
|
end
|
57
42
|
end
|
58
|
-
end
|
43
|
+
end
|
@@ -1,67 +1,19 @@
|
|
1
|
-
require 'active_support'
|
2
|
-
|
3
1
|
module ActsAsSpan
|
4
2
|
class SpanInstance
|
5
3
|
module Validations
|
6
4
|
extend ActiveSupport::Concern
|
7
|
-
|
8
|
-
|
5
|
+
|
6
|
+
included do
|
9
7
|
def validate
|
10
|
-
validate_start_date
|
11
|
-
validate_end_date
|
12
8
|
validate_start_date_less_than_or_equal_to_end_date
|
13
|
-
validate_overlap
|
14
|
-
end
|
15
|
-
|
16
|
-
def validate_start_date
|
17
|
-
if start_date_field_required && start_date.blank?
|
18
|
-
span_model.errors.add(start_date_field, :blank)
|
19
|
-
end
|
20
9
|
end
|
21
|
-
|
22
|
-
def validate_end_date
|
23
|
-
if end_date_field_required && end_date.blank?
|
24
|
-
span_model.errors.add(end_date_field, :blank)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
10
|
+
|
28
11
|
def validate_start_date_less_than_or_equal_to_end_date
|
29
12
|
if start_date && end_date && end_date < start_date
|
30
|
-
span_model.errors.add(
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def validate_overlap
|
35
|
-
if span_overlap_count && span_model.errors[start_date_field].empty? && span_model.errors[end_date_field].empty? # && ( respond_to?('archived?') ? !archived? : true )
|
36
|
-
conditions = {}
|
37
|
-
|
38
|
-
if span_overlap_scope.is_a?(Array)
|
39
|
-
span_overlap_scope.each do |symbol|
|
40
|
-
conditions[symbol] = span_model.send(symbol)
|
41
|
-
end
|
42
|
-
elsif span_overlap_scope.is_a?(Symbol)
|
43
|
-
conditions[span_overlap_scope] = span_model.send(span_overlap_scope)
|
44
|
-
end
|
45
|
-
|
46
|
-
records = span_klass.span_for(name).overlap(self).where(conditions)
|
47
|
-
|
48
|
-
if span_klass.respond_to?('not_archived')
|
49
|
-
records.not_archived
|
50
|
-
end
|
51
|
-
|
52
|
-
#TODO - This will have to be an after_save callback...
|
53
|
-
#if span_overlap_auto_close
|
54
|
-
# records.each do |record|
|
55
|
-
# record.close!(start_date)
|
56
|
-
# end
|
57
|
-
#end
|
58
|
-
|
59
|
-
if records.count > span_overlap_count
|
60
|
-
span_model.errors.add(:base, "date range overlaps with #{records.count} other record(s)")
|
61
|
-
end
|
13
|
+
span_model.errors.add(end_field, "Must be on or after #{start_field}")
|
62
14
|
end
|
63
15
|
end
|
64
16
|
end
|
65
17
|
end
|
66
18
|
end
|
67
|
-
end
|
19
|
+
end
|
@@ -1,31 +1,23 @@
|
|
1
|
-
require '
|
1
|
+
require 'acts_as_span/span_klass/status'
|
2
2
|
|
3
|
-
require
|
4
|
-
require ACTS_AS_SPAN_PATH + 'span_klass/overlap'
|
3
|
+
require 'active_support/core_ext/module/delegation'
|
5
4
|
|
6
5
|
module ActsAsSpan
|
7
6
|
class SpanKlass
|
8
|
-
extend Forwardable
|
9
|
-
|
10
7
|
include ActsAsSpan::SpanKlass::Status
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
:span_overlap_scope,
|
19
|
-
:span_overlap_count
|
20
|
-
|
21
|
-
def_delegators :klass, :table_name
|
22
|
-
|
8
|
+
|
9
|
+
delegate :start_field,
|
10
|
+
:end_field,
|
11
|
+
:exclude_end, to: :@acts_as_span_definition
|
12
|
+
|
13
|
+
delegate :table_name, :arel_table, to: :klass, allow_nil: true
|
14
|
+
|
23
15
|
attr_reader :name, :klass, :acts_as_span_definition
|
24
|
-
|
16
|
+
|
25
17
|
def initialize(name, klass, acts_as_span_definition)
|
26
18
|
@name = name
|
27
19
|
@klass = klass
|
28
20
|
@acts_as_span_definition = acts_as_span_definition
|
29
21
|
end
|
30
22
|
end
|
31
|
-
end
|
23
|
+
end
|
@@ -4,26 +4,47 @@ module ActsAsSpan
|
|
4
4
|
class SpanKlass
|
5
5
|
module Status
|
6
6
|
extend ActiveSupport::Concern
|
7
|
-
|
8
|
-
|
9
|
-
def current(query_date = Date.
|
10
|
-
klass.where(
|
7
|
+
|
8
|
+
included do
|
9
|
+
def current(query_date = Date.current)
|
10
|
+
klass.where(
|
11
|
+
(arel_table[start_field].lteq(query_date).or(arel_table[start_field].eq(nil))).
|
12
|
+
and(
|
13
|
+
arel_table[end_field].eq(nil).or(arel_table[end_field].gteq(query_date))
|
14
|
+
)
|
15
|
+
)
|
11
16
|
end
|
12
|
-
|
17
|
+
|
13
18
|
alias_method :current_on, :current
|
14
19
|
|
15
|
-
def future(query_date = Date.
|
16
|
-
klass.where([
|
20
|
+
def future(query_date = Date.current)
|
21
|
+
klass.where(arel_table[start_field].gt(query_date))
|
17
22
|
end
|
18
|
-
|
23
|
+
|
19
24
|
alias_method :future_on, :future
|
20
25
|
|
21
|
-
def expired(query_date = Date.
|
22
|
-
klass.where([
|
26
|
+
def expired(query_date = Date.current)
|
27
|
+
klass.where(arel_table[end_field].lt(query_date))
|
23
28
|
end
|
24
|
-
|
29
|
+
|
25
30
|
alias_method :expired_on, :expired
|
31
|
+
alias_method :past_on, :expired
|
32
|
+
alias_method :past, :expired
|
33
|
+
|
34
|
+
|
35
|
+
def current_or_future_on(query_date = Date.current)
|
36
|
+
klass.where(
|
37
|
+
arel_table[start_field].lteq(query_date).
|
38
|
+
and(
|
39
|
+
arel_table[end_field].eq(nil).
|
40
|
+
or(arel_table[end_field].gteq(query_date))
|
41
|
+
).
|
42
|
+
or(arel_table[start_field].gt(query_date))
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
alias_method :current_or_future, :current_or_future_on
|
26
47
|
end
|
27
48
|
end
|
28
49
|
end
|
29
|
-
end
|
50
|
+
end
|
data/lib/acts_as_span/version.rb
CHANGED
@@ -0,0 +1,42 @@
|
|
1
|
+
module ActsAsSpan
|
2
|
+
class WithinParentDateSpanValidator < ActiveModel::Validator
|
3
|
+
def validate(record)
|
4
|
+
parents = options[:parent] || options[:parents]
|
5
|
+
|
6
|
+
Array(parents).each do |parent|
|
7
|
+
record.errors.add(:base, :not_within_parent_date_span, parent: record.class.human_attribute_name(parent)) if outside_of_parent_date_span?(record, parent)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def outside_of_parent_date_span?(record, parent_sym)
|
12
|
+
parent = record.send(parent_sym)
|
13
|
+
|
14
|
+
return false if parent.nil?
|
15
|
+
|
16
|
+
child_record_without_start_date(record, parent) ||
|
17
|
+
child_record_without_end_date(record, parent) ||
|
18
|
+
child_record_started_before_parent_record(record, parent) ||
|
19
|
+
child_record_ended_after_parent_record(record, parent)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def child_record_started_before_parent_record(record, parent)
|
25
|
+
record.start_date.present? && parent.start_date.present? &&
|
26
|
+
record.start_date < parent.start_date
|
27
|
+
end
|
28
|
+
|
29
|
+
def child_record_ended_after_parent_record(record, parent)
|
30
|
+
record.end_date.present? && parent.end_date.present? &&
|
31
|
+
record.end_date > parent.end_date
|
32
|
+
end
|
33
|
+
|
34
|
+
def child_record_without_start_date(record, parent)
|
35
|
+
record.start_date.nil? && parent.start_date.present?
|
36
|
+
end
|
37
|
+
|
38
|
+
def child_record_without_end_date(record, parent)
|
39
|
+
record.end_date.nil? && parent.end_date.present?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -1,64 +1,46 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe "acts_as_span" do
|
4
|
-
before(:each) do
|
5
|
-
build_model :span_model do
|
6
|
-
acts_as_span
|
7
|
-
end
|
8
|
-
end
|
9
|
-
|
3
|
+
RSpec.describe "acts_as_span" do
|
10
4
|
context "ClassMethods" do
|
11
|
-
it "should return true for acts_as_span?" do
|
12
|
-
SpanModel.acts_as_span?.should be_true
|
13
|
-
end
|
14
|
-
|
15
5
|
it "should have 1 acts_as_span_definition" do
|
16
|
-
SpanModel.
|
6
|
+
expect(SpanModel.acts_as_span_definitions.size).to eq(1)
|
17
7
|
end
|
18
|
-
|
8
|
+
|
19
9
|
it "should set default options for acts_as_span_definition" do
|
20
10
|
span_definition = SpanModel.acts_as_span_definitions[:default]
|
21
|
-
|
22
|
-
span_definition.
|
23
|
-
span_definition.
|
24
|
-
span_definition.
|
25
|
-
span_definition.
|
26
|
-
span_definition.exclude_end.should be_false
|
27
|
-
span_definition.span_overlap_scope.should be_nil
|
28
|
-
span_definition.span_overlap_count.should be_nil
|
29
|
-
span_definition.name.should == :default
|
11
|
+
|
12
|
+
expect(span_definition.start_field).to eq(:start_date)
|
13
|
+
expect(span_definition.end_field).to eq(:end_date)
|
14
|
+
expect(span_definition.exclude_end).to be_falsey
|
15
|
+
expect(span_definition.name).to eq(:default)
|
30
16
|
end
|
31
|
-
|
17
|
+
|
32
18
|
it "should return a SpanKlass w/ span" do
|
33
|
-
SpanModel.span.
|
19
|
+
expect(SpanModel.span).to be_instance_of(ActsAsSpan::SpanKlass)
|
34
20
|
end
|
35
21
|
|
36
22
|
it "should return a SpanKlass w/ span_for(:default)" do
|
37
|
-
SpanModel.span_for(:default).
|
23
|
+
expect(SpanModel.span_for(:default)).to be_instance_of(ActsAsSpan::SpanKlass)
|
38
24
|
end
|
39
|
-
|
25
|
+
|
40
26
|
it "should have (1) spans" do
|
41
|
-
SpanModel.spans.
|
27
|
+
expect(SpanModel.spans.size).to eq(1)
|
42
28
|
end
|
43
29
|
end
|
44
|
-
|
30
|
+
|
45
31
|
context "InstanceMethods" do
|
46
32
|
let(:span_model) { SpanModel.new }
|
47
|
-
|
48
|
-
it "should return true for acts_as_span?" do
|
49
|
-
span_model.acts_as_span?.should be_true
|
50
|
-
end
|
51
33
|
|
52
34
|
it "should return a SpanInstance w/ span" do
|
53
|
-
span_model.span.
|
35
|
+
expect(span_model.span).to be_instance_of(ActsAsSpan::SpanInstance)
|
54
36
|
end
|
55
37
|
|
56
38
|
it "should return a SpanInstance w/ span_for(:default)" do
|
57
|
-
span_model.span_for(:default).
|
39
|
+
expect(span_model.span_for(:default)).to be_instance_of(ActsAsSpan::SpanInstance)
|
58
40
|
end
|
59
|
-
|
41
|
+
|
60
42
|
it "should have (1) spans" do
|
61
|
-
span_model.spans.
|
43
|
+
expect(span_model.spans.size).to eq(1)
|
62
44
|
end
|
63
45
|
end
|
64
46
|
end
|
data/spec/lib/delegation_spec.rb
CHANGED
@@ -1,128 +1,95 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe "Span" do
|
4
|
-
|
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_klass) { SpanModel.span }
|
15
6
|
let(:span_instance) { span_model.span }
|
16
|
-
|
7
|
+
|
17
8
|
context "ClassMethods" do
|
18
9
|
it "should delegate current" do
|
19
|
-
span_klass.
|
20
|
-
|
10
|
+
expect(span_klass).to receive(:current).and_return(true)
|
11
|
+
|
21
12
|
SpanModel.current
|
22
13
|
end
|
23
|
-
|
14
|
+
|
24
15
|
it "should delegate current_on" do
|
25
|
-
span_klass.
|
26
|
-
|
16
|
+
expect(span_klass).to receive(:current_on).and_return(true)
|
17
|
+
|
27
18
|
SpanModel.current_on
|
28
19
|
end
|
29
|
-
|
20
|
+
|
30
21
|
it "should delegate future" do
|
31
|
-
span_klass.
|
32
|
-
|
22
|
+
expect(span_klass).to receive(:future).and_return(true)
|
23
|
+
|
33
24
|
SpanModel.future
|
34
25
|
end
|
35
|
-
|
26
|
+
|
36
27
|
it "should delegate future_on" do
|
37
|
-
span_klass.
|
38
|
-
|
28
|
+
expect(span_klass).to receive(:future_on).and_return(true)
|
29
|
+
|
39
30
|
SpanModel.future_on
|
40
31
|
end
|
41
|
-
|
32
|
+
|
42
33
|
it "should delegate expired" do
|
43
|
-
span_klass.
|
44
|
-
|
34
|
+
expect(span_klass).to receive(:expired).and_return(true)
|
35
|
+
|
45
36
|
SpanModel.expired
|
46
37
|
end
|
47
|
-
|
38
|
+
|
48
39
|
it "should delegate expired_on" do
|
49
|
-
span_klass.
|
50
|
-
|
40
|
+
expect(span_klass).to receive(:expired_on).and_return(true)
|
41
|
+
|
51
42
|
SpanModel.expired_on
|
52
43
|
end
|
53
44
|
end
|
54
|
-
|
45
|
+
|
55
46
|
context "InstanceMethods" do
|
56
|
-
it "should delegate close!" do
|
57
|
-
span_instance.should_receive(:close!).and_return(true)
|
58
|
-
|
59
|
-
span_model.close!
|
60
|
-
end
|
61
|
-
|
62
|
-
it "should delegate close_on!" do
|
63
|
-
span_instance.should_receive(:close_on!).and_return(true)
|
64
|
-
|
65
|
-
span_model.close_on!
|
66
|
-
end
|
67
|
-
|
68
47
|
it "should delegate span_status" do
|
69
|
-
span_instance.
|
70
|
-
|
48
|
+
expect(span_instance).to receive(:span_status).and_return(true)
|
49
|
+
|
71
50
|
span_model.span_status
|
72
51
|
end
|
73
|
-
|
52
|
+
|
74
53
|
it "should delegate span_status_on" do
|
75
|
-
span_instance.
|
76
|
-
|
54
|
+
expect(span_instance).to receive(:span_status_on).and_return(true)
|
55
|
+
|
77
56
|
span_model.span_status_on
|
78
57
|
end
|
79
|
-
|
80
|
-
it "should delegate span_status_to_s" do
|
81
|
-
span_instance.should_receive(:span_status_to_s).and_return(true)
|
82
|
-
|
83
|
-
span_model.span_status_to_s
|
84
|
-
end
|
85
|
-
|
86
|
-
it "should delegate span_status_to_s_on" do
|
87
|
-
span_instance.should_receive(:span_status_to_s_on).and_return(true)
|
88
|
-
|
89
|
-
span_model.span_status_to_s_on
|
90
|
-
end
|
91
|
-
|
58
|
+
|
92
59
|
it "should delegate current?" do
|
93
|
-
span_instance.
|
94
|
-
|
60
|
+
expect(span_instance).to receive(:current?).and_return(true)
|
61
|
+
|
95
62
|
span_model.current?
|
96
63
|
end
|
97
|
-
|
64
|
+
|
98
65
|
it "should delegate current_on?" do
|
99
|
-
span_instance.
|
100
|
-
|
66
|
+
expect(span_instance).to receive(:current_on?).and_return(true)
|
67
|
+
|
101
68
|
span_model.current_on?
|
102
69
|
end
|
103
|
-
|
70
|
+
|
104
71
|
it "should delegate future?" do
|
105
|
-
span_instance.
|
106
|
-
|
72
|
+
expect(span_instance).to receive(:future?).and_return(true)
|
73
|
+
|
107
74
|
span_model.future?
|
108
75
|
end
|
109
|
-
|
76
|
+
|
110
77
|
it "should delegate future_on?" do
|
111
|
-
span_instance.
|
112
|
-
|
78
|
+
expect(span_instance).to receive(:future_on?).and_return(true)
|
79
|
+
|
113
80
|
span_model.future_on?
|
114
81
|
end
|
115
|
-
|
82
|
+
|
116
83
|
it "should delegate expired?" do
|
117
|
-
span_instance.
|
118
|
-
|
84
|
+
expect(span_instance).to receive(:expired?).and_return(true)
|
85
|
+
|
119
86
|
span_model.expired?
|
120
87
|
end
|
121
|
-
|
88
|
+
|
122
89
|
it "should delegate expired_on?" do
|
123
|
-
span_instance.
|
124
|
-
|
90
|
+
expect(span_instance).to receive(:expired_on?).and_return(true)
|
91
|
+
|
125
92
|
span_model.expired_on?
|
126
93
|
end
|
127
94
|
end
|
128
|
-
end
|
95
|
+
end
|