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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 57404b0d110113b5f9c52537aa90970a560d69f4a5b7d5d318caacc229157679
4
- data.tar.gz: 5702fafe912d1bc56b633aec32dd2ae76da9f886608c182eed29c0ca7d2ed509
3
+ metadata.gz: fec6db2024d649082face6a15ed22acffb3828ca67de3c54c509f940ade832eb
4
+ data.tar.gz: 67c0277e44d09fb7b5b73e4eb73327c4e8ff796109975f77f5afe42dc2120db3
5
5
  SHA512:
6
- metadata.gz: b1e7265d3cfc7433b331f407fe991eb5549091e10c5dcef0298468099f1769dfaca71a7355363af8f6f88c381362d518fa494366d3f1dead8de8072458d95b2c
7
- data.tar.gz: 6304cb8600c553e60bc7e470ed4f87e6cc145a4239243f5109556278f76d88b24d1624bbc904e499d7ce95d7196967d4d8822c962571c9184c875dbd20d6d57f
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
- if overlapping_records.any? && instance_scope
14
-
15
- record.errors.add(
16
- :base,
17
- :no_overlap,
18
- model_name: record.class.model_name.human,
19
- model_name_plural: record.class.model_name.plural.humanize,
20
- start_date: record.span.start_date,
21
- end_date: record.span.end_date,
22
- count: overlapping_records.size,
23
- overlapping_records_s: overlapping_records.join(",")
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[record.span.end_field].gteq(start_date)
42
- .or(arel_table[record.span.end_field].eq(nil))
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[record.span.end_field].gteq(start_date)
48
- .or(arel_table[record.span.end_field].eq(nil))
79
+ arel_table[end_field].gteq(start_date)
80
+ .or(arel_table[end_field].eq(nil))
49
81
  )
50
82
  end
51
83
  end
@@ -2,7 +2,7 @@ module ActsAsSpan
2
2
  module VERSION
3
3
  MAJOR = 1
4
4
  MINOR = 1
5
- TINY = 0
5
+ TINY = 1
6
6
  PRE = nil
7
7
 
8
8
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
@@ -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
 
@@ -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, scope: proc { siblings }
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.0
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-03-31 00:00:00.000000000 Z
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.0
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