chrono_model 3.0.1 → 5.0.0

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.
@@ -2,10 +2,44 @@
2
2
 
3
3
  module ChronoModel
4
4
  module Patches
5
+ # Overrides the default batch methods for historical models
6
+ #
7
+ # In the default implementation, `cursor` defaults to `primary_key`, which is 'id'.
8
+ # However, historical models need to use 'hid' instead of 'id'.
9
+ #
10
+ # This patch addresses an issue where `with_hid_pkey` is called after the cursor
11
+ # is already set, potentially leading to incorrect behavior.
12
+ #
13
+ # Notes:
14
+ # - `find_each` and `find_in_batches` internally utilize `in_batches`.
15
+ # However, in the upcoming Rails 8.0, this implementation will be
16
+ # insufficient due to a new conditional branch using `enum_for`.
17
+ # - This approach prevents specifying 'id' as a cursor for historical models.
18
+ # If 'id' is needed, it must be handled separately.
19
+ #
20
+ # See: ifad/chronomodel#321 for more context
5
21
  module Batches
6
- def in_batches(**)
22
+ def find_each(**options)
7
23
  return super unless try(:history?)
8
24
 
25
+ options[:cursor] = 'hid' if options[:cursor] == 'id'
26
+
27
+ with_hid_pkey { super }
28
+ end
29
+
30
+ def find_in_batches(**options)
31
+ return super unless try(:history?)
32
+
33
+ options[:cursor] = 'hid' if options[:cursor] == 'id'
34
+
35
+ with_hid_pkey { super }
36
+ end
37
+
38
+ def in_batches(**options)
39
+ return super unless try(:history?)
40
+
41
+ options[:cursor] = 'hid' if options[:cursor] == 'id'
42
+
9
43
  with_hid_pkey { super }
10
44
  end
11
45
  end
@@ -2,28 +2,17 @@
2
2
 
3
3
  module ChronoModel
4
4
  module Patches
5
- # This class supports the AR 5.0 code that expects to receive an
6
- # Arel::Table as the left join node. We need to replace the node
7
- # with a virtual table that fetches from the history at a given
8
- # point in time, we replace the join node with a SqlLiteral node
9
- # that does not respond to the methods that AR expects.
10
- #
11
- # This class provides AR with an object implementing the methods
12
- # it expects, yet producing SQL that fetches from history tables
13
- # as-of-time.
14
- #
5
+ # Replaces the left side of an Arel join (table or table alias) with a SQL
6
+ # literal pointing to the history virtual table at the given as_of_time,
7
+ # preserving any existing table alias.
15
8
  class JoinNode < Arel::Nodes::SqlLiteral
16
- attr_reader :name, :table_name, :table_alias, :as_of_time
9
+ attr_reader :as_of_time
17
10
 
18
11
  def initialize(join_node, history_model, as_of_time)
19
- @name = join_node.table_name
20
- @table_name = join_node.table_name
21
- @table_alias = join_node.table_alias
22
-
23
12
  @as_of_time = as_of_time
24
13
 
25
- virtual_table = history_model
26
- .virtual_table_at(@as_of_time, table_name: @table_alias || @table_name)
14
+ table_name = join_node.table_alias || join_node.name
15
+ virtual_table = history_model.virtual_table_at(@as_of_time, table_name: table_name)
27
16
 
28
17
  super(virtual_table)
29
18
  end
@@ -9,43 +9,14 @@ module ChronoModel
9
9
  module Preloader
10
10
  attr_reader :chronomodel_options
11
11
 
12
- # We overwrite the initializer in order to pass the +as_of_time+
13
- # parameter above in the build_preloader
12
+ # Overwrite the initializer to set Chronomodel +as_of_time+ and +model+
13
+ # options.
14
14
  #
15
15
  def initialize(**options)
16
16
  @chronomodel_options = options.extract!(:as_of_time, :model)
17
17
  options[:scope] = chronomodel_scope(options[:scope]) if options.key?(:scope)
18
18
 
19
- if options.empty?
20
- super()
21
- else
22
- super(**options)
23
- end
24
- end
25
-
26
- # Patches the AR Preloader (lib/active_record/associations/preloader.rb)
27
- # in order to carry around the +as_of_time+ of the original invocation.
28
- #
29
- # * The +records+ are the parent records where the association is defined
30
- # * The +associations+ are the association names involved in preloading
31
- # * The +given_preload_scope+ is the preloading scope, that is used only
32
- # in the :through association and it holds the intermediate records
33
- # _through_ which the final associated records are eventually fetched.
34
- #
35
- # As the +preload_scope+ is passed around to all the different
36
- # incarnations of the preloader strategies, we are using it to pass
37
- # around the +as_of_time+ of the original query invocation, so that
38
- # preloaded records are preloaded honoring the +as_of_time+.
39
- #
40
- # The +preload_scope+ is present only in through associations, but the
41
- # preloader interfaces expect it to be always defined, for consistency.
42
- #
43
- # For `:through` associations, the +given_preload_scope+ is already a
44
- # +Relation+, that already has the +as_of_time+ getters and setters,
45
- # so we use it directly.
46
- #
47
- def preload(records, associations, given_preload_scope = nil)
48
- super(records, associations, chronomodel_scope(given_preload_scope))
19
+ super
49
20
  end
50
21
 
51
22
  private
@@ -62,6 +33,8 @@ module ChronoModel
62
33
  end
63
34
 
64
35
  module Association
36
+ private
37
+
65
38
  # Builds the preloader scope taking into account a potential
66
39
  # +as_of_time+ passed down the call chain starting at the
67
40
  # end user invocation.
@@ -78,13 +51,14 @@ module ChronoModel
78
51
  end
79
52
 
80
53
  module ThroughAssociation
54
+ private
55
+
81
56
  # Builds the preloader scope taking into account a potential
82
57
  # +as_of_time+ passed down the call chain starting at the
83
58
  # end user invocation.
84
59
  #
85
60
  def through_scope
86
61
  scope = super
87
- return unless scope # Rails 5.2 may not return a scope
88
62
 
89
63
  if preload_scope.try(:as_of_time)
90
64
  scope = scope.as_of(preload_scope.as_of_time)
@@ -23,10 +23,17 @@ module ChronoModel
23
23
  @values == klass.unscoped.as_of(as_of_time).values
24
24
  end
25
25
 
26
- def load
27
- return super unless @_as_of_time && !loaded?
26
+ def load(&block)
27
+ return super unless @_as_of_time && (!loaded? || scheduled?)
28
28
 
29
- super.each { |record| record.as_of_time!(@_as_of_time) }
29
+ records = super
30
+
31
+ records.each do |record|
32
+ record.as_of_time!(@_as_of_time)
33
+ propagate_as_of_time_to_includes(record)
34
+ end
35
+
36
+ self
30
37
  end
31
38
 
32
39
  def merge(*)
@@ -35,6 +42,20 @@ module ChronoModel
35
42
  super.as_of_time!(@_as_of_time)
36
43
  end
37
44
 
45
+ def find_nth(*)
46
+ return super unless try(:history?)
47
+
48
+ with_hid_pkey { super }
49
+ end
50
+
51
+ def last(*)
52
+ return super unless try(:history?)
53
+
54
+ with_hid_pkey { super }
55
+ end
56
+
57
+ private
58
+
38
59
  def build_arel(*)
39
60
  return super unless @_as_of_time
40
61
 
@@ -47,54 +68,71 @@ module ChronoModel
47
68
 
48
69
  # Replaces a join with the current data with another that
49
70
  # loads records As-Of time against the history data.
50
- #
51
71
  def chrono_join_history(join)
72
+ join_left = join.left
73
+
52
74
  # This case happens with nested includes, where the below
53
75
  # code has already replaced the join.left with a JoinNode.
54
- #
55
- return if join.left.respond_to?(:as_of_time)
56
-
57
- model =
58
- if join.left.respond_to?(:table_name)
59
- ChronoModel.history_models[join.left.table_name]
60
- else
61
- ChronoModel.history_models[join.left]
62
- end
76
+ return if join_left.is_a?(ChronoModel::Patches::JoinNode)
63
77
 
78
+ model = ChronoModel.history_models[join_left.name] if join_left.respond_to?(:name)
64
79
  return unless model
65
80
 
66
81
  join.left = ChronoModel::Patches::JoinNode.new(
67
- join.left, model.history, @_as_of_time
82
+ join_left, model.history, @_as_of_time
68
83
  )
69
84
  end
70
85
 
71
- # Build a preloader at the +as_of_time+ of this relation.
72
- # Pass the current model to define Relation
73
- #
74
- def build_preloader
75
- ActiveRecord::Associations::Preloader.new(
76
- model: model, as_of_time: as_of_time
77
- )
78
- end
79
-
80
- def find_nth(*)
86
+ def ordered_relation
81
87
  return super unless try(:history?)
82
88
 
83
89
  with_hid_pkey { super }
84
90
  end
85
91
 
86
- def last(*)
87
- return super unless try(:history?)
92
+ # Propagate as_of_time to associations that were eager loaded via includes/eager_load
93
+ def propagate_as_of_time_to_includes(record)
94
+ return unless eager_loading?
88
95
 
89
- with_hid_pkey { super }
96
+ assign_as_of_time_to_spec(record, includes_values)
90
97
  end
91
98
 
92
- private
99
+ def assign_as_of_time_to_spec(record, spec)
100
+ case spec
101
+ when Symbol, String
102
+ assign_as_of_time_to_association(record, spec.to_sym, nil)
103
+ when Array
104
+ spec.each { |s| assign_as_of_time_to_spec(record, s) }
105
+ when Hash
106
+ # This branch is difficult to trigger in practice due to Rails query optimization.
107
+ # Modern Rails versions tend to optimize eager loading in ways that make this specific
108
+ # code path challenging to reproduce in tests without artificial scenarios.
109
+ spec.each do |name, nested|
110
+ assign_as_of_time_to_association(record, name.to_sym, nested)
111
+ end
112
+ end
113
+ end
93
114
 
94
- def ordered_relation
95
- return super unless try(:history?)
115
+ def assign_as_of_time_to_association(record, name, nested)
116
+ reflection = record.class.reflect_on_association(name)
117
+ return unless reflection
96
118
 
97
- with_hid_pkey { super }
119
+ assoc = record.association(name)
120
+ return unless assoc.loaded?
121
+
122
+ target = assoc.target
123
+
124
+ if target.is_a?(Array)
125
+ target.each { |t| t.respond_to?(:as_of_time!) && t.as_of_time!(@_as_of_time) }
126
+ # This nested condition is difficult to trigger in practice as it requires specific
127
+ # association loading scenarios with Array targets and nested specs that Rails
128
+ # query optimization tends to handle differently in modern versions.
129
+ if nested.present?
130
+ target.each { |t| assign_as_of_time_to_spec(t, nested) }
131
+ end
132
+ else
133
+ target.respond_to?(:as_of_time!) && target.as_of_time!(@_as_of_time)
134
+ assign_as_of_time_to_spec(target, nested) if nested.present? && target
135
+ end
98
136
  end
99
137
  end
100
138
  end
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'chrono_model/patches/as_of_time_holder'
4
- require 'chrono_model/patches/as_of_time_relation'
3
+ require_relative 'patches/as_of_time_holder'
4
+ require_relative 'patches/as_of_time_relation'
5
5
 
6
- require 'chrono_model/patches/join_node'
7
- require 'chrono_model/patches/relation'
8
- require 'chrono_model/patches/preloader'
9
- require 'chrono_model/patches/association'
10
- require 'chrono_model/patches/batches'
6
+ require_relative 'patches/join_node'
7
+ require_relative 'patches/relation'
8
+ require_relative 'patches/preloader'
9
+ require_relative 'patches/association'
10
+ require_relative 'patches/batches'
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_record/tasks/chronomodel_database_tasks'
3
+ require_relative '../active_record/tasks/chronomodel_database_tasks'
4
4
 
5
5
  module ChronoModel
6
6
  class Railtie < ::Rails::Railtie
@@ -11,7 +11,6 @@ module ChronoModel
11
11
  scope :chronological, -> { order(Arel.sql('lower(validity) ASC')) }
12
12
  end
13
13
 
14
- # ACTIVE RECORD 7 does not call `class.find` but a new internal method called `_find_record`
15
14
  def _find_record(options)
16
15
  if options && options[:lock]
17
16
  self.class.preload(strict_loaded_associations).lock(options[:lock]).find_by!(hid: hid)
@@ -33,7 +32,7 @@ module ChronoModel
33
32
  #
34
33
  def with_hid_pkey
35
34
  old = primary_key
36
- self.primary_key = :hid
35
+ self.primary_key = 'hid'
37
36
 
38
37
  yield
39
38
  ensure
@@ -83,14 +82,18 @@ module ChronoModel
83
82
 
84
83
  # Fetches history record at the given time
85
84
  #
85
+ # Build on an unscoped relation to avoid leaking outer predicates
86
+ # (e.g., through association scopes) into the inner subquery.
87
+ #
88
+ # @see https://github.com/ifad/chronomodel/issues/295
86
89
  def at(time)
87
- time_query(:at, time).from(quoted_table_name).as_of_time!(time)
90
+ unscoped.time_query(:at, time).from(quoted_table_name).as_of_time!(time)
88
91
  end
89
92
 
90
93
  # Returns the history sorted by recorded_at
91
94
  #
92
95
  def sorted
93
- all.order(Arel.sql(%( #{quoted_table_name}."recorded_at" ASC, #{quoted_table_name}."hid" ASC )))
96
+ all.order(Arel.sql(%(#{quoted_table_name}."recorded_at" ASC, #{quoted_table_name}."hid" ASC)))
94
97
  end
95
98
 
96
99
  # Fetches the given +object+ history, sorted by history record time
@@ -107,7 +110,7 @@ module ChronoModel
107
110
  # name has the "::History" suffix but that is never going to be
108
111
  # present in the data.
109
112
  #
110
- # As such it is overriden here to return the same contents that
113
+ # As such it is overridden here to return the same contents that
111
114
  # the parent would have returned.
112
115
  delegate :sti_name, to: :superclass
113
116
 
@@ -132,7 +135,7 @@ module ChronoModel
132
135
  end
133
136
 
134
137
  # The history id is `hid`, but this cannot set as primary key
135
- # or temporal assocations will break. Solutions are welcome.
138
+ # or temporal associations will break. Solutions are welcome.
136
139
  def id
137
140
  hid
138
141
  end
@@ -204,26 +207,49 @@ module ChronoModel
204
207
  self.class.superclass.find(rid)
205
208
  end
206
209
 
207
- def record # :nodoc:
208
- ActiveSupport::Deprecation.warn '.record is deprecated in favour of .current_version'
209
- current_version
210
- end
211
-
210
+ # Return `nil` instead of -Infinity/Infinity to preserve current
211
+ # Chronomodel behaviour and avoid failures with Rails 7.0 and
212
+ # unbounded time ranges
213
+ #
214
+ # Check if `begin` and `end` are `Time` because validity is a `tsrange`
215
+ # column, so it is either `Time`, `nil`, and in some cases Infinity.
216
+ #
217
+ # Ref: rails/rails#45099
218
+ # TODO: consider removing when Rails 7.0 support will be dropped
212
219
  def valid_from
213
- validity.first
220
+ validity.begin if validity.begin.is_a?(Time)
214
221
  end
215
222
 
216
223
  def valid_to
217
- validity.last
224
+ validity.end if validity.end.is_a?(Time)
218
225
  end
219
- alias as_of_time valid_to
220
226
 
221
- def recorded_at
222
- ChronoModel::Conversions.string_to_utc_time attributes_before_type_cast['recorded_at']
227
+ # Computes an `as_of_time` strictly inside this record's validity period
228
+ # for historical queries.
229
+ #
230
+ # Ensures association queries return versions that existed during this
231
+ # record's validity, not ones that became valid exactly at the boundary
232
+ # time. When objects are updated in the same transaction, they can share
233
+ # the same `valid_to`, which would otherwise cause boundary
234
+ # mis-selection.
235
+ #
236
+ # PostgreSQL ranges are half-open `[start, end)` by default.
237
+ #
238
+ # @return [Time, nil] `valid_to - ChronoModel::VALIDITY_TSRANGE_PRECISION`
239
+ # when `valid_to` is a Time; otherwise returns `valid_to` unchanged
240
+ # (which may be `nil` for open-ended validity).
241
+ #
242
+ # @see https://github.com/ifad/chronomodel/issues/283
243
+ def as_of_time
244
+ if valid_to.is_a?(Time)
245
+ valid_to - ChronoModel::VALIDITY_TSRANGE_PRECISION
246
+ else
247
+ valid_to
248
+ end
223
249
  end
224
250
 
225
- # Starting from Rails 6.0, `.read_attribute` will use the memoized
226
- # `primary_key` if it detects that the attribute name is `id`.
251
+ # `.read_attribute` uses the memoized `primary_key` if it detects
252
+ # that the attribute name is `id`.
227
253
  #
228
254
  # Since the `primary key` may have been changed to `hid` because of
229
255
  # `.find` overload, the new behavior may break relations where `id` is
@@ -240,7 +266,7 @@ module ChronoModel
240
266
 
241
267
  def with_hid_pkey(&block)
242
268
  old_primary_key = @primary_key
243
- @primary_key = :hid
269
+ @primary_key = 'hid'
244
270
 
245
271
  self.class.with_hid_pkey(&block)
246
272
  ensure
@@ -46,7 +46,8 @@ module ChronoModel
46
46
  end
47
47
 
48
48
  def time_for_time_query(t, column)
49
- if t == :now || t == :today
49
+ case t
50
+ when :now, :today
50
51
  now_for_column(column)
51
52
  else
52
53
  quoted_t = connection.quote(connection.quoted_date(t))
@@ -92,9 +93,9 @@ module ChronoModel
92
93
 
93
94
  def build_time_query(time, range, op = '&&')
94
95
  if time.is_a?(Array)
95
- Arel.sql %[ #{range.type}(#{time.first}, #{time.last}) #{op} #{table_name}.#{range.name} ]
96
+ Arel.sql %[#{range.type}(#{time.first}, #{time.last}) #{op} #{table_name}.#{range.name} ]
96
97
  else
97
- Arel.sql %( #{time} <@ #{table_name}.#{range.name} )
98
+ Arel.sql %(#{time} <@ #{table_name}.#{range.name})
98
99
  end
99
100
  end
100
101
  end
@@ -59,7 +59,7 @@ module ChronoModel
59
59
  relation = relation.from("public.#{quoted_table_name}") unless chrono?
60
60
  relation = relation.where(id: rid) if rid
61
61
 
62
- sql = +"SELECT ts FROM ( #{relation.to_sql} ) AS foo WHERE ts IS NOT NULL"
62
+ sql = "SELECT ts FROM (#{relation.to_sql}) AS foo WHERE ts IS NOT NULL"
63
63
 
64
64
  if options.key?(:before)
65
65
  sql << " AND ts < '#{Conversions.time_to_utc_string(options[:before])}'"
@@ -72,18 +72,16 @@ module ChronoModel
72
72
  if rid && !options[:with]
73
73
  sql <<
74
74
  if chrono?
75
- %{ AND ts <@ ( SELECT tsrange(min(lower(validity)), max(upper(validity)), '[]') FROM #{quoted_table_name} WHERE id = #{rid} ) }
75
+ %{ AND ts <@ (SELECT tsrange(min(lower(validity)), max(upper(validity)), '[]') FROM #{quoted_table_name} WHERE id = #{rid})}
76
76
  else
77
- %[ AND ts < NOW() ]
77
+ ' AND ts < NOW()'
78
78
  end
79
79
  end
80
80
 
81
81
  sql << " LIMIT #{options[:limit].to_i}" if options.key?(:limit)
82
82
 
83
83
  connection.on_schema(Adapter::HISTORY_SCHEMA) do
84
- connection.select_values(sql, "#{name} periods").map! do |ts|
85
- Conversions.string_to_utc_time ts
86
- end
84
+ connection.select_values(sql, "#{name} periods")
87
85
  end
88
86
  end
89
87
 
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'chrono_model/time_machine/time_query'
4
- require 'chrono_model/time_machine/timeline'
5
- require 'chrono_model/time_machine/history_model'
3
+ require_relative 'time_machine/time_query'
4
+ require_relative 'time_machine/timeline'
5
+ require_relative 'time_machine/history_model'
6
6
 
7
7
  module ChronoModel
8
8
  module TimeMachine
@@ -12,7 +12,7 @@ module ChronoModel
12
12
 
13
13
  included do
14
14
  if table_exists? && !chrono?
15
- logger.warn <<-MSG.squish
15
+ logger.warn <<~MSG.squish
16
16
  ChronoModel: #{table_name} is not a temporal table.
17
17
  Please use `change_table :#{table_name}, temporal: true` in a migration.
18
18
  MSG
@@ -176,7 +176,7 @@ module ChronoModel
176
176
  else
177
177
  return nil unless (ts = pred_timestamp(options))
178
178
 
179
- order_clause = Arel.sql %[ LOWER(#{options[:table] || self.class.quoted_table_name}."validity") DESC ]
179
+ order_clause = Arel.sql %[LOWER(#{options[:table] || self.class.quoted_table_name}."validity") DESC]
180
180
 
181
181
  self.class.as_of(ts).order(order_clause).find(options[:id] || id)
182
182
  end
@@ -16,12 +16,12 @@ module ChronoModel
16
16
  raise 'Can amend history only with UTC timestamps'
17
17
  end
18
18
 
19
- connection.execute %[
19
+ connection.execute <<~SQL.squish
20
20
  UPDATE #{quoted_table_name}
21
- SET "validity" = tsrange(#{connection.quote(from)}, #{connection.quote(to)}),
21
+ SET "validity" = tsrange(#{connection.quote(from)}, #{connection.quote(to)}),
22
22
  "recorded_at" = #{connection.quote(from)}
23
23
  WHERE "hid" = #{hid.to_i}
24
- ]
24
+ SQL
25
25
  end
26
26
  end
27
27
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ChronoModel
4
- VERSION = '3.0.1'
4
+ VERSION = '5.0.0'
5
5
  end
data/lib/chrono_model.rb CHANGED
@@ -2,21 +2,26 @@
2
2
 
3
3
  require 'active_record'
4
4
 
5
- require 'chrono_model/chrono'
6
- require 'chrono_model/conversions'
7
- require 'chrono_model/patches'
8
- require 'chrono_model/adapter'
9
- require 'chrono_model/time_machine'
10
- require 'chrono_model/time_gate'
11
- require 'chrono_model/version'
5
+ require_relative 'chrono_model/chrono'
6
+ require_relative 'chrono_model/conversions'
7
+ require_relative 'chrono_model/patches'
8
+ require_relative 'chrono_model/adapter'
9
+ require_relative 'chrono_model/time_machine'
10
+ require_relative 'chrono_model/time_gate'
11
+ require_relative 'chrono_model/version'
12
12
 
13
- require 'chrono_model/railtie' if defined?(Rails::Railtie)
14
- require 'chrono_model/db_console' if defined?(Rails::DBConsole) && Rails.version < '7.1'
13
+ require_relative 'chrono_model/railtie' if defined?(Rails::Railtie)
14
+ require_relative 'chrono_model/db_console' if defined?(Rails::DBConsole) && Rails.version < '7.1'
15
15
 
16
16
  module ChronoModel
17
17
  class Error < ActiveRecord::ActiveRecordError # :nodoc:
18
18
  end
19
19
 
20
+ # ChronoModel uses default timestamp precision (p=6) for tsrange columns.
21
+ # PostgreSQL timestamp precision can range from 0 to 6 fractional digits,
22
+ # where 6 provides microsecond resolution (1 microsecond = 10^-6 seconds).
23
+ VALIDITY_TSRANGE_PRECISION = Rational(1, 10**6)
24
+
20
25
  # Performs structure upgrade.
21
26
  #
22
27
  def self.upgrade!
metadata CHANGED
@@ -1,15 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chrono_model
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.1
4
+ version: 5.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marcello Barnaba
8
8
  - Peter Joseph Brindisi
9
- autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2024-02-26 00:00:00.000000000 Z
11
+ date: 1980-01-02 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: activerecord
@@ -72,7 +71,6 @@ files:
72
71
  - lib/chrono_model/adapter/indexes.rb
73
72
  - lib/chrono_model/adapter/migrations.rb
74
73
  - lib/chrono_model/adapter/migrations_modules/stable.rb
75
- - lib/chrono_model/adapter/tsrange.rb
76
74
  - lib/chrono_model/adapter/upgrade.rb
77
75
  - lib/chrono_model/chrono.rb
78
76
  - lib/chrono_model/conversions.rb
@@ -102,7 +100,6 @@ metadata:
102
100
  homepage_uri: https://github.com/ifad/chronomodel
103
101
  source_code_uri: https://github.com/ifad/chronomodel
104
102
  rubygems_mfa_required: 'true'
105
- post_install_message:
106
103
  rdoc_options: []
107
104
  require_paths:
108
105
  - lib
@@ -117,8 +114,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
117
114
  - !ruby/object:Gem::Version
118
115
  version: '0'
119
116
  requirements: []
120
- rubygems_version: 3.5.5
121
- signing_key:
117
+ rubygems_version: 4.0.3
122
118
  specification_version: 4
123
119
  summary: Temporal extensions (SCD Type II) for Active Record
124
120
  test_files: []