temporality 0.0.4

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8fd5d7006b68f06a13dd0f46790440a31d179384
4
+ data.tar.gz: f4e6b4354ede8fc9c0ad6fa8717fe0a6b080f290
5
+ SHA512:
6
+ metadata.gz: d9dec839f133d29763adab32b68c017ecc12ef960398c8156c84f40e8bb4640c7161dcfa8d0e0f5268ff8e6d82ce7ed2b31e33bab330c3cbf979ada953a224ac
7
+ data.tar.gz: ecd202980a60ec146c429a45750dfffeff4337bb6aeac56c94f8a43aa4907f6fc058a59315587b850fcba471c670b4b253f3eedf72e557cd2052bc862b0a20fe
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2016 David FRANCOIS
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,39 @@
1
+ Temporality [![Build Status](https://secure.travis-ci.org/davout/temporality.png?branch=master)](http://travis-ci.org/davout/temporality) [![Coverage Status](https://img.shields.io/coveralls/davout/temporality.svg)](https://coveralls.io/r/davout/temporality?branch=master) [![Gem Version](https://badge.fury.io/rb/temporality.svg)](http://badge.fury.io/rb/temporality)
2
+ =
3
+
4
+ ## What is Temporality
5
+ Temporality adds the ability to `ActiveRecord::Base` descendants to validate temporal data on themselves, and their associations.
6
+
7
+ ## Target features
8
+ - Completeness implique prevent_overlap! implique également d'être deferred jusqu'au commit de la transaction
9
+ - Enforce this is only used on belong_to associations
10
+ - Doit permettre de valider que le parent est rempli
11
+ - Doit permettre au parent de valider que les enfants sont inclus
12
+ - Doit ne pas permettre l'overlap des records enfants
13
+
14
+ - Doit faire l'auto-close des records précédents, avec callbacks
15
+ - Doit faire l'auto-close des enfants, avec callbacks
16
+ - Doit permettre d'initialiser l'enfant avec les bornes du parent en faisant par exemple parent.children.build
17
+ - Scopes AR : intersecting, contained, englobant
18
+ - Quand les bornes du parent sont changées on doit jouer les validations de l'enfant, sinon dans les autres cas, c'est l'enfant qu'on valide
19
+ - Que se passe-t-il quand on modifie les bornes du parent, mais qu'on modifie en même temps les bornes de l'enfant ?
20
+
21
+ ## Examples
22
+
23
+ ````ruby
24
+ Temporality.configure do |config|
25
+ config.error_strategy = :exception
26
+ end
27
+
28
+ Temporality.configure do |config|
29
+ config.error_strategy = :active_model
30
+ end
31
+
32
+ class Contract < ActiveRecord::Base
33
+ has_many :compensations
34
+ end
35
+
36
+ class Compensation < ActiveRecord::Base
37
+ belongs_to :contract, temporality: { inclusion: true, auto_close_previous: true, allow_overlap: false, completeness: true }
38
+ end
39
+ ````
@@ -0,0 +1,29 @@
1
+ module Temporality
2
+ module Associations
3
+
4
+ # TODO : Use class-inheritable instance variables
5
+
6
+ DEFAULTS = { inclusion: true, completeness: false, prevent_overlap: false, auto_close: false }
7
+
8
+ def belongs_to(*args, &block)
9
+ @temporality ||= {}
10
+ assoc_name = args.first
11
+
12
+ if args.last.is_a?(Hash)
13
+ if opts = args.last.delete(:temporality)
14
+ opts.keys.each do |key|
15
+ unless DEFAULTS.keys.include?(key)
16
+ raise "Unknown option '#{key}', valid options are #{DEFAULTS.keys.map(&:to_s).join(', ')}"
17
+ end
18
+ end
19
+
20
+ @temporality[assoc_name] = DEFAULTS.merge(opts)
21
+ end
22
+ end
23
+
24
+ super(*args, &block)
25
+ end
26
+
27
+ end
28
+ end
29
+
@@ -0,0 +1,42 @@
1
+ module Temporality
2
+
3
+ module AttributeOverrides
4
+
5
+ def starts_on
6
+ floor_to_temporal_infinity(super)
7
+ end
8
+
9
+ def ends_on
10
+ ceil_to_temporal_infinity(super)
11
+ end
12
+
13
+ def starts_on=(d)
14
+ super(floor_to_temporal_infinity(d))
15
+ end
16
+
17
+ def ends_on=(d)
18
+ super(ceil_to_temporal_infinity(d))
19
+ end
20
+
21
+ private
22
+
23
+ #
24
+ # Floors the parameter to `Temporality::PAST_INFINITY`
25
+ #
26
+ def floor_to_temporal_infinity(d)
27
+ [d, Temporality::PAST_INFINITY].compact.max
28
+ end
29
+
30
+ #
31
+ # Ceils the parameter to `Temporality::FUTURE_INFINITY`
32
+ #
33
+ def ceil_to_temporal_infinity(d)
34
+ [d, Temporality::FUTURE_INFINITY].compact.min
35
+ end
36
+
37
+
38
+
39
+ end
40
+
41
+ end
42
+
@@ -0,0 +1,11 @@
1
+ require 'temporality/validation_strategy'
2
+
3
+ module Temporality
4
+ class AutoClose < ValidationStrategy
5
+
6
+ def validate
7
+ end
8
+
9
+ end
10
+ end
11
+
@@ -0,0 +1,21 @@
1
+ require 'temporality/validation_strategy'
2
+
3
+ module Temporality
4
+ class Completeness < ValidationStrategy
5
+
6
+ def validate
7
+ days = @model.day_count + (inverse.where('id <> ?', @model.id || -1).map(&:day_count).inject(&:+) || 0)
8
+ parent_days = @model.send(@assoc).day_count
9
+
10
+ raise Temporality::Violation.new(error_message) unless (parent_days == days)
11
+ end
12
+
13
+ def error_message
14
+ "#{@model.send(@assoc).class} record must have a temporally complete children collection for assocation #{inverse_name}"
15
+ end
16
+
17
+ # TODO : Check if in transaction ActiveRecord::Base.connection.open_transactions
18
+
19
+ end
20
+ end
21
+
@@ -0,0 +1,10 @@
1
+ module Temporality
2
+ module DayCount
3
+
4
+ def day_count
5
+ (ends_on - starts_on).to_i
6
+ end
7
+
8
+ end
9
+ end
10
+
@@ -0,0 +1,15 @@
1
+ module Temporality
2
+ module DefaultBoundaryValues
3
+
4
+ def self.included(base)
5
+ base.after_initialize :set_time_boundaries_defaults
6
+
7
+ def set_time_boundaries_defaults
8
+ self.starts_on = starts_on
9
+ self.ends_on = ends_on
10
+ end
11
+ end
12
+
13
+ end
14
+ end
15
+
@@ -0,0 +1,14 @@
1
+ module Temporality
2
+ class Inclusion < ValidationStrategy
3
+
4
+ def validate
5
+ parent = @model.send(@assoc)
6
+
7
+ if parent && (parent.starts_on > @model.starts_on || parent.ends_on < @model.ends_on)
8
+ raise Temporality::Violation.new("Record of class #{self.class} is not temporally included in parent of class #{parent.class}, [#{@model.starts_on} - #{@model.ends_on}] is not included in [#{parent.starts_on} - #{parent.ends_on}]")
9
+ end
10
+ end
11
+
12
+ end
13
+ end
14
+
@@ -0,0 +1,22 @@
1
+ require 'temporality/validation_strategy'
2
+
3
+ module Temporality
4
+ class Overlap < ValidationStrategy
5
+
6
+ def validate
7
+ overlapping = inverse.intersecting(@model)
8
+
9
+ if @model.id
10
+ overlapping = overlapping.where('id <> ?', @model.id)
11
+ end
12
+
13
+ raise Temporality::Violation.new(error_message) if overlapping.exists?
14
+ end
15
+
16
+ def error_message
17
+ "Found overlapping records for range [#{@model.starts_on} - #{@model.ends_on}]"
18
+ end
19
+
20
+ end
21
+ end
22
+
@@ -0,0 +1,33 @@
1
+ #
2
+ # Defines a `temporality` migration helper for use in a `create_table` block as well
3
+ # as a `temporality(:table)` helper to be used to alter existing table definitions.
4
+ #
5
+ module Temporality
6
+ module Schema
7
+
8
+ def self.include_helpers!
9
+ if Object.const_defined?(:ActiveRecord)
10
+ ActiveRecord::Migration.send(:include, MigrationHelper)
11
+ ActiveRecord::ConnectionAdapters::TableDefinition.send(:include, TableDefinitionHelper)
12
+ end
13
+ end
14
+
15
+ module MigrationHelper
16
+ def temporality(table)
17
+ add_column(table, :starts_on, :date, null: false)
18
+ add_column(table, :ends_on, :date, null: false)
19
+ end
20
+ end
21
+
22
+ module TableDefinitionHelper
23
+ def temporality
24
+ date(:starts_on, null: false)
25
+ date(:ends_on, null: false)
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+
32
+ Temporality::Schema.include_helpers!
33
+
@@ -0,0 +1,10 @@
1
+ module Temporality
2
+ module Scopes
3
+
4
+ def intersecting(time_span)
5
+ where('starts_on <= ?', time_span.ends_on).where('ends_on >= ?', time_span.starts_on)
6
+ end
7
+
8
+ end
9
+ end
10
+
@@ -0,0 +1,27 @@
1
+ require 'temporality/time_span'
2
+
3
+ module Temporality
4
+ class Slice < TimeSpan
5
+ attr_accessor :value
6
+
7
+ def initialize(starts_on, ends_on, value)
8
+ @value = value
9
+ super(starts_on, ends_on)
10
+ end
11
+
12
+ def to_s
13
+ "(SL : #{super} -> #{@value})"
14
+ end
15
+
16
+ def intersect(time_span)
17
+ if time_span.is_a? TimeSpan
18
+ intersects?(time_span) ? Slice.new([@starts_on, time_span.starts_on].max, [@ends_on, time_span.ends_on].min, @value) : nil
19
+ elsif time_span.is_a? SliceCollection
20
+ r = time_span.map { |s| intersect s }.compact
21
+ r.size > 1 ? SliceCollection.new(s) : s
22
+ else
23
+ raise TypeError, 'Argument should be a TimeSpan or a SliceCollection'
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,82 @@
1
+ require 'temporality/time_span_collection'
2
+ require 'temporality/slice'
3
+
4
+ module Temporality
5
+ class SliceCollection < TimeSpanCollection
6
+ attr_accessor :slices
7
+
8
+ def initialize(values = nil)
9
+ collection_from_history(values) if values
10
+ end
11
+
12
+ def to_s
13
+ "{SLC : #{map(&:to_s) .join(', ')}}"
14
+ end
15
+
16
+ # TODO : Méthodes nécessaires ?
17
+ # def starts_on
18
+ # first.starts_on unless empty?
19
+ # end
20
+
21
+ # def ends_on
22
+ # last.ends_on unless empty?
23
+ # end
24
+
25
+ # Remplit les 'trous' d'une collection de slices par rapport à un time_span de référence
26
+ # en utilisant les valeurs disponibles dans la collection de slices passée en paramètre
27
+ def fill_missing_with!(slices, time_span)
28
+ self << slices.intersect(time_span.substract(self))
29
+ flatten!
30
+ sort!
31
+ end
32
+
33
+ def value_for(time_span)
34
+ detect do |slice|
35
+ slice.covers? time_span
36
+ end.value
37
+ end
38
+
39
+ def intersect(tsc)
40
+ if tsc.is_a? TimeSpanCollection
41
+ tsc.inject(SliceCollection.new) do |r, s|
42
+ r << intersect(s)
43
+ end.compact.sort
44
+ elsif tsc.is_a? TimeSpan
45
+ map { |s| s.intersect tsc }.compact
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def collection_from_history(values)
52
+ sorted_values = values.sort do |a, b|
53
+ a.starts_on <=> b.starts_on
54
+ end
55
+
56
+ previous_slice = nil
57
+
58
+ sorted_values.each_with_index do |v, _i|
59
+ slice = Slice.new(v.starts_on, v.ends_on, v.value)
60
+
61
+ if previous_slice
62
+ raise 'Overlapping slices!' if slice.starts_on <= previous_slice.ends_on
63
+ not_period_complete! if previous_slice.ends_on.advance(days: 1) < slice.starts_on
64
+ end
65
+
66
+ self.<<(slice)
67
+ end
68
+ end
69
+
70
+ def self.not_period_complete!
71
+ raise 'Not period complete'
72
+ end
73
+
74
+ def <=>(slice_collection)
75
+ (size == slice_collection.slices.size) && all? { |slice| slice_collection.include?(slice) }
76
+ end
77
+
78
+ def method_missing(_method, *_args)
79
+ raise SlicedConceptInterrupt
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,127 @@
1
+ module Temporality
2
+ class TimeSpan
3
+ include Comparable
4
+
5
+ attr_accessor :starts_on, :ends_on
6
+
7
+ def initialize(starts_on, ends_on, _options = {})
8
+ @starts_on = starts_on
9
+ @ends_on = ends_on
10
+
11
+ check!
12
+ end
13
+
14
+ def to_s
15
+ "{ TS : [#{starts_on} | #{ends_on}]}"
16
+ end
17
+
18
+ def inspect
19
+ to_s
20
+ end
21
+
22
+ # TODO : Refactorer éventuellement pour avoir un algo ~o(1) au lieu de o(n)
23
+ # Utiliser Date#ld comme base pour un algo ~o(1)
24
+ def day_count(excluded = [])
25
+ if excluded.empty?
26
+ 1 + (@ends_on - @starts_on).to_i
27
+ else
28
+ (@starts_on..@ends_on).inject(0) { |c, day| c += excluded.include?(day.cwday) ? 0 : 1 }
29
+ end
30
+ end
31
+
32
+ def calendaires
33
+ day_count
34
+ end
35
+
36
+ def ouvres
37
+ day_count [6, 7]
38
+ end
39
+
40
+ def ouvrables
41
+ day_count [7]
42
+ end
43
+
44
+ def covers?(time_span)
45
+ (@starts_on <= time_span.starts_on) && (@ends_on >= time_span.ends_on)
46
+ end
47
+
48
+ def ==(time_span)
49
+ @starts_on == time_span.starts_on && @ends_on == time_span.ends_on
50
+ end
51
+
52
+ def eql?(time_span)
53
+ self.==(time_span) && (self.class == time_span.class)
54
+ end
55
+
56
+ def <=>(time_span)
57
+ if @starts_on != time_span.starts_on
58
+ @starts_on <=> time_span.starts_on
59
+ else
60
+ @ends_on <=> time_span.ends_on
61
+ end
62
+ end
63
+
64
+ def intersects?(time_span)
65
+ if time_span.respond_to?(:starts_on) && time_span.respond_to?(:ends_on)
66
+ (time_span.ends_on >= @starts_on) && (time_span.starts_on <= @ends_on)
67
+ elsif time_span.is_a?(TimeSpanCollection)
68
+ time_span.any? { |ts| intersects? ts }
69
+ end
70
+ end
71
+
72
+ def intersect(time_span)
73
+ if time_span.is_a? TimeSpan
74
+ intersects?(time_span) ? TimeSpan.new([@starts_on, time_span.starts_on].max, [@ends_on, time_span.ends_on].min) : nil
75
+ elsif time_span.is_a? TimeSpanCollection
76
+ # byebug
77
+ r = time_span.map { |ts| intersect ts }.compact
78
+ r.size > 1 ? TimeSpanCollection.new(r) : r.first
79
+ end
80
+ end
81
+
82
+ def substract(time_span)
83
+ if time_span.is_a?(TimeSpan)
84
+ if time_span.covers? self
85
+ nil
86
+ elsif !time_span.intersects? self
87
+ dup
88
+ elsif starts_on < time_span.starts_on && ends_on > time_span.ends_on
89
+ TimeSpanCollection.new([
90
+ TimeSpan.new(starts_on, time_span.starts_on - 1),
91
+ TimeSpan.new(time_span.ends_on + 1, ends_on)
92
+ ])
93
+ elsif starts_on < time_span.starts_on && ends_on <= time_span.ends_on
94
+ TimeSpan.new(starts_on, time_span.starts_on - 1)
95
+ elsif starts_on >= time_span.starts_on && ends_on > time_span.ends_on
96
+ TimeSpan.new(time_span.ends_on + 1, ends_on)
97
+ else
98
+ raise "That's one fucked up edge case"
99
+ end
100
+ elsif time_span.is_a?(TimeSpanCollection)
101
+ TimeSpanCollection.new(
102
+ time_span.inject(dup) do |res, ts|
103
+ res.substract(ts)
104
+ end
105
+ )
106
+ else
107
+ raise TypeError, 'Wrong type supplied, should be TimeSpan or TimeSpanCollection'
108
+ end
109
+ end
110
+
111
+ def intersect!(time_span)
112
+ @starts_on = [@starts_on, time_span.starts_on].max
113
+ @ends_on = [@ends_on, time_span.ends_on].min
114
+ check!
115
+ end
116
+
117
+ private
118
+
119
+ def valid?
120
+ @starts_on <= @ends_on
121
+ end
122
+
123
+ def check!
124
+ raise "Invalid dates : #{self}" unless valid?
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,61 @@
1
+ require 'temporality/time_span'
2
+
3
+ module Temporality
4
+ class TimeSpanCollection < Array
5
+ def initialize(collection = [])
6
+ super()
7
+ collection = [collection].flatten # Support pour ne passer qu'un seul TS
8
+ collection.map { |ts| self << (ts.is_a?(TimeSpan) ? ts : raise(TypeError, "Element is not a Temporality::TimeSpan")) }
9
+ sort!
10
+ end
11
+
12
+ def to_s
13
+ "{TSC : #{map { |ts| ts.to_s }.join(", ")}}"
14
+ end
15
+
16
+ def inspect
17
+ to_s
18
+ end
19
+
20
+ def limit_to!(time_span)
21
+ reject! { |ts| !ts.intersects?(time_span) }
22
+ first.starts_on = [first.starts_on, time_span.starts_on].max
23
+ last.ends_on = [last.ends_on, time_span.ends_on].min
24
+ self
25
+ end
26
+
27
+ def intersect!(time_span)
28
+ if time_span.is_a? TimeSpan
29
+ map! { |ts| ts.intersect(time_span) }.compact!
30
+ elsif time_span.is_a? TimeSpanCollection
31
+ raise 'Not handling time_span collections yet'
32
+ else
33
+ raise TypeError, "Element is not a Temporality::TimeSpan or a Temporality::TimeSpanCollection"
34
+ end
35
+
36
+ self
37
+ end
38
+
39
+ def intersect(tsc)
40
+ res = tsc.inject(TimeSpanCollection.new) do |r, ts|
41
+ r << ts.intersect(self)
42
+ end.compact.sort
43
+
44
+ TimeSpanCollection.new(res)
45
+ end
46
+
47
+ def intersects?(time_span)
48
+ any? { |ts| ts.intersects? time_span }
49
+ end
50
+
51
+ def substract(time_span)
52
+ raise TypeError, "Not a TimeSpan" unless time_span.is_a? TimeSpan
53
+
54
+ TimeSpanCollection.new(
55
+ map do |ts|
56
+ ts.substract(time_span)
57
+ end
58
+ )
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,54 @@
1
+ require 'temporality/violation'
2
+
3
+ require 'temporality/auto_close'
4
+ require 'temporality/completeness'
5
+ require 'temporality/overlap'
6
+ require 'temporality/inclusion'
7
+
8
+ module Temporality
9
+ module Validation
10
+
11
+ VALIDATIONS = {
12
+ inclusion: Inclusion,
13
+ prevent_overlap: Overlap,
14
+ completeness: Completeness,
15
+ auto_close: AutoClose
16
+ }
17
+
18
+ DEFAULTS = { inclusion: true, completeness: false, prevent_overlap: false, auto_close: false }
19
+
20
+ def valid?(*args, &block)
21
+ validate_temporality_contraints!
22
+ super(*args, &block)
23
+ end
24
+
25
+
26
+ private
27
+
28
+ def validate_temporality_contraints!
29
+ validate_bounds_order
30
+
31
+ temporal_associations.each do |assoc, constraints|
32
+ constraints.select { |k,v| v }.keys.each do |constraint|
33
+ validate_constraint(assoc, constraint)
34
+ end
35
+ end
36
+ end
37
+
38
+ def validate_constraint(assoc, constraint)
39
+ VALIDATIONS[constraint].new(self, assoc).validate
40
+ end
41
+
42
+ def validate_bounds_order
43
+ if starts_on > ends_on
44
+ raise Temporality::Violation.new("Start date is after end date [#{starts_on} - #{ends_on}]")
45
+ end
46
+ end
47
+
48
+ def temporal_associations
49
+ self.class.instance_variable_get(:@temporality) || {}
50
+ end
51
+
52
+ end
53
+ end
54
+
@@ -0,0 +1,23 @@
1
+ module Temporality
2
+ class ValidationStrategy
3
+
4
+ def initialize(model, assoc)
5
+ @model = model
6
+ @assoc = assoc
7
+ end
8
+
9
+
10
+ protected
11
+
12
+ def inverse
13
+ raise "Unable to validate temporality overlap for #{@model.class} without inverse for association '#{@assoc}'" unless inverse_name
14
+ @model.send(@assoc).send(inverse_name)
15
+ end
16
+
17
+ def inverse_name
18
+ @model.class.reflect_on_association(@assoc).send(:inverse_name)
19
+ end
20
+
21
+ end
22
+ end
23
+
@@ -0,0 +1,7 @@
1
+ module Temporality
2
+
3
+ # Temporality gem version
4
+ VERSION = '0.0.4'
5
+
6
+ end
7
+
@@ -0,0 +1,5 @@
1
+ module Temporality
2
+ class Violation < RuntimeError
3
+ end
4
+ end
5
+
@@ -0,0 +1,36 @@
1
+ require 'date'
2
+
3
+ require 'temporality/version'
4
+
5
+ require 'temporality/slice_collection'
6
+
7
+ require 'temporality/validation'
8
+ require 'temporality/default_boundary_values'
9
+ require 'temporality/attribute_overrides'
10
+ require 'temporality/associations'
11
+ require 'temporality/schema'
12
+ require 'temporality/scopes'
13
+ require 'temporality/day_count'
14
+
15
+ module Temporality
16
+
17
+ # Used when no start date is defined
18
+ PAST_INFINITY = Date.new(1500, 1, 1)
19
+
20
+ # Used when no end date is defined
21
+ FUTURE_INFINITY = Date.new(5000, 1, 1)
22
+
23
+ PREPENDS = [ AttributeOverrides, Validation ]
24
+ EXTENDS = [ Associations, Scopes ]
25
+ INCLUDES = [ DefaultBoundaryValues ]
26
+
27
+ def self.included(base)
28
+ PREPENDS.each { |mod| base.prepend(mod) }
29
+ EXTENDS.each { |mod| base.extend(mod) }
30
+ INCLUDES.each { |mod| base.include(mod) }
31
+
32
+ ActiveRecord::Base.send(:include, DayCount)
33
+ end
34
+
35
+ end
36
+
metadata ADDED
@@ -0,0 +1,192 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: temporality
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.4
5
+ platform: ruby
6
+ authors:
7
+ - David FRANCOIS
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-10-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.1'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: yard
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.8'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.8'
55
+ - !ruby/object:Gem::Dependency
56
+ name: redcarpet
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.1'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: simplecov
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.9'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.9'
83
+ - !ruby/object:Gem::Dependency
84
+ name: coveralls
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.7'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.7'
97
+ - !ruby/object:Gem::Dependency
98
+ name: activerecord
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: sqlite3
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: byebug
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ description: Give records the ability to validate temporal constraints on themselves
140
+ and associations
141
+ email:
142
+ - david@paygun.fr
143
+ executables: []
144
+ extensions: []
145
+ extra_rdoc_files: []
146
+ files:
147
+ - LICENSE
148
+ - README.md
149
+ - lib/temporality.rb
150
+ - lib/temporality/associations.rb
151
+ - lib/temporality/attribute_overrides.rb
152
+ - lib/temporality/auto_close.rb
153
+ - lib/temporality/completeness.rb
154
+ - lib/temporality/day_count.rb
155
+ - lib/temporality/default_boundary_values.rb
156
+ - lib/temporality/inclusion.rb
157
+ - lib/temporality/overlap.rb
158
+ - lib/temporality/schema.rb
159
+ - lib/temporality/scopes.rb
160
+ - lib/temporality/slice.rb
161
+ - lib/temporality/slice_collection.rb
162
+ - lib/temporality/time_span.rb
163
+ - lib/temporality/time_span_collection.rb
164
+ - lib/temporality/validation.rb
165
+ - lib/temporality/validation_strategy.rb
166
+ - lib/temporality/version.rb
167
+ - lib/temporality/violation.rb
168
+ homepage: https://paygun.fr
169
+ licenses:
170
+ - MIT
171
+ metadata: {}
172
+ post_install_message:
173
+ rdoc_options: []
174
+ require_paths:
175
+ - lib
176
+ required_ruby_version: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ required_rubygems_version: !ruby/object:Gem::Requirement
182
+ requirements:
183
+ - - ">="
184
+ - !ruby/object:Gem::Version
185
+ version: 1.3.6
186
+ requirements: []
187
+ rubyforge_project:
188
+ rubygems_version: 2.5.1
189
+ signing_key:
190
+ specification_version: 4
191
+ summary: Make records temporal
192
+ test_files: []