acts_as_span 1.1.0 → 1.1.1
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 +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
|