temporality 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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: []