acts_as_span 1.1.0 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/acts_as_span/no_overlap_validator.rb +52 -20
- data/lib/acts_as_span/version.rb +1 -1
- data/spec/lib/no_overlap_validator_spec.rb +23 -0
- data/spec/spec_models.rb +9 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fec6db2024d649082face6a15ed22acffb3828ca67de3c54c509f940ade832eb
|
4
|
+
data.tar.gz: 67c0277e44d09fb7b5b73e4eb73327c4e8ff796109975f77f5afe42dc2120db3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9fbe5d893be0359e262ed8cfb5d1fc58551317d103fc07098c1d5b6f5955fc95a520129c6d0d2493fda975681a9508484bc5550e17a187af9827fc867564ccc6
|
7
|
+
data.tar.gz: 9fc6775556dca3cd990b2988de635beb11311c683e4a104951faf33245b1f5b36ecdd3d66d501c57092a517e6bbba15ec181def50cbea01c6de5b50271bc377a
|
@@ -1,51 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'active_model'
|
2
4
|
|
3
5
|
module ActsAsSpan
|
6
|
+
# Validator that checks whether a record is overlapping with others
|
7
|
+
#
|
8
|
+
# Takes options `:instance_scope` (optional) and `:scope` (required):
|
9
|
+
# * `instance_scope` is a proc which, when evaluated by the record, returns
|
10
|
+
# a boolean value. When false, the validatior will not check for overlap.
|
11
|
+
# When true, the validator checks normally.
|
12
|
+
# * `scope` is also a proc. This is must return an ActiveRecord Relation that
|
13
|
+
# determines which records' spans to compare.
|
14
|
+
#
|
15
|
+
# Usage:
|
16
|
+
# Given a record with `siblings` defined, the most basic use case is:
|
17
|
+
# ```
|
18
|
+
# validates_with ActsAsSpan::NoOverlapValidator,
|
19
|
+
# scope: proc { siblings }
|
20
|
+
# ```
|
21
|
+
# When this record is validated, every record in the ActiveRecord relation
|
22
|
+
# `record.siblings` is checked for mutual overlap with `record`.
|
23
|
+
#
|
24
|
+
# Use `instance_scope` if there is some condition where a record oughtn't be
|
25
|
+
# validated for whatever reason:
|
26
|
+
# ```
|
27
|
+
# validates_with ActsAsSpan::NoOverlapValidator,
|
28
|
+
# scope: proc { siblings }, instance_scope: proc { favorite? }
|
29
|
+
# ```
|
30
|
+
# Now, when this record is validated, if `record.favorite?` is `true`,
|
31
|
+
# `record` must pass the overlap check with its siblings.
|
32
|
+
# If `record.favorite?` is `false`, it is under less scrutiny.
|
33
|
+
#
|
4
34
|
class NoOverlapValidator < ActiveModel::Validator
|
5
35
|
def validate(record)
|
6
36
|
overlapping_records = temporally_overlapping_for(record)
|
7
37
|
instance_scope = if options[:instance_scope].is_a? Proc
|
8
|
-
record.instance_eval&options[:instance_scope]
|
38
|
+
record.instance_eval(&options[:instance_scope])
|
9
39
|
else
|
10
40
|
true
|
11
41
|
end
|
12
42
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
end
|
43
|
+
return unless overlapping_records.any? && instance_scope
|
44
|
+
|
45
|
+
record.errors.add(
|
46
|
+
:base,
|
47
|
+
:no_overlap,
|
48
|
+
model_name: record.class.model_name.human,
|
49
|
+
model_name_plural: record.class.model_name.plural.humanize,
|
50
|
+
start_date: record.span.start_date,
|
51
|
+
end_date: record.span.end_date,
|
52
|
+
count: overlapping_records.size,
|
53
|
+
overlapping_records_s: overlapping_records.join(', ')
|
54
|
+
)
|
26
55
|
end
|
27
56
|
|
28
|
-
#TODO add back condition for start_date nil
|
29
|
-
#TODO add support for multiple spans (currently only checks :default)
|
57
|
+
# TODO: add back condition for start_date nil
|
58
|
+
# TODO: add support for multiple spans (currently only checks :default)
|
30
59
|
def temporally_overlapping_for(record)
|
31
60
|
scope = record.instance_eval(&options[:scope])
|
32
61
|
|
33
62
|
start_date = record.span.start_date || Date.current
|
63
|
+
|
34
64
|
end_date = record.span.end_date
|
65
|
+
end_field = record.span.end_field
|
66
|
+
|
35
67
|
arel_table = record.class.arel_table
|
36
68
|
|
37
69
|
if end_date
|
38
70
|
scope.where(
|
39
71
|
arel_table[record.span.start_field].lteq(end_date)
|
40
72
|
.and(
|
41
|
-
arel_table[
|
42
|
-
.or(arel_table[
|
73
|
+
arel_table[end_field].gteq(start_date)
|
74
|
+
.or(arel_table[end_field].eq(nil))
|
43
75
|
)
|
44
76
|
)
|
45
77
|
else
|
46
78
|
scope.where(
|
47
|
-
arel_table[
|
48
|
-
.or(arel_table[
|
79
|
+
arel_table[end_field].gteq(start_date)
|
80
|
+
.or(arel_table[end_field].eq(nil))
|
49
81
|
)
|
50
82
|
end
|
51
83
|
end
|
data/lib/acts_as_span/version.rb
CHANGED
@@ -42,6 +42,29 @@ RSpec.describe ActsAsSpan::NoOverlapValidator do
|
|
42
42
|
|
43
43
|
let(:all_siblings) { [brother, sister, brother_from_another_mother] }
|
44
44
|
|
45
|
+
describe 'instance_scope' do
|
46
|
+
let(:child_class) { OneParentChild }
|
47
|
+
|
48
|
+
let(:new_child_start_date) { Date.current - 2 }
|
49
|
+
let(:new_child_end_date) { Date.current + 3 }
|
50
|
+
|
51
|
+
before { new_child.favorite = favorite }
|
52
|
+
|
53
|
+
context 'when instance_scope evaluates to false' do
|
54
|
+
let(:favorite) { false }
|
55
|
+
it 'skips validation on the record for which instance_scope is false' do
|
56
|
+
expect(new_child).to be_valid
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'when instance_scope evaluates to true' do
|
61
|
+
let(:favorite) { true }
|
62
|
+
it 'validates normally' do
|
63
|
+
expect(new_child).not_to be_valid
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
45
68
|
describe 'an object with a single parent' do
|
46
69
|
let(:child_class) { OneParentChild }
|
47
70
|
|
data/spec/spec_models.rb
CHANGED
@@ -28,14 +28,22 @@ Temping.create :one_parent_child do
|
|
28
28
|
|
29
29
|
t.date :start_date
|
30
30
|
t.date :end_date
|
31
|
+
|
32
|
+
# every one-parent child is a favorite! ...by default
|
33
|
+
t.boolean :favorite, default: true
|
31
34
|
end
|
32
35
|
|
33
36
|
acts_as_span
|
34
37
|
|
38
|
+
def favorite?
|
39
|
+
favorite
|
40
|
+
end
|
41
|
+
|
35
42
|
belongs_to :mama
|
36
43
|
has_siblings through: [:mama]
|
37
44
|
|
38
|
-
validates_with ActsAsSpan::NoOverlapValidator,
|
45
|
+
validates_with ActsAsSpan::NoOverlapValidator,
|
46
|
+
scope: proc { siblings }, instance_scope: proc { favorite? }
|
39
47
|
validates_with ActsAsSpan::WithinParentDateSpanValidator, parents: [:mama]
|
40
48
|
end
|
41
49
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: acts_as_span
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eric Sullivan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-04-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -199,7 +199,7 @@ requirements: []
|
|
199
199
|
rubygems_version: 3.1.2
|
200
200
|
signing_key:
|
201
201
|
specification_version: 4
|
202
|
-
summary: acts_as_span 1.1.
|
202
|
+
summary: acts_as_span 1.1.1
|
203
203
|
test_files:
|
204
204
|
- spec/lib/acts_as_span_spec.rb
|
205
205
|
- spec/lib/delegation_spec.rb
|