chrono_model 3.0.1 → 4.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.
- checksums.yaml +4 -4
- data/README.md +23 -30
- data/lib/chrono_model/adapter/upgrade.rb +1 -1
- data/lib/chrono_model/adapter.rb +0 -2
- data/lib/chrono_model/conversions.rb +0 -13
- data/lib/chrono_model/time_machine/history_model.rb +13 -13
- data/lib/chrono_model/time_machine/time_query.rb +2 -1
- data/lib/chrono_model/time_machine/timeline.rb +1 -3
- data/lib/chrono_model/version.rb +1 -1
- metadata +3 -4
- data/lib/chrono_model/adapter/tsrange.rb +0 -72
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b8e1a4cdc29f1fa0435f82c9277674a7085e6caf445ea45673995864038d8a1b
|
4
|
+
data.tar.gz: 60df7e87f659a392cf22e8a754b608d7d1fc030d4f63a30dde4af1644ab7917d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 22333be09e65a5347f3413976eea41581b76c7ad94dc138924cd7e5e8f6cb36cd572567f6b195c896a14262d96a3623f7b49116ed2856dcf58441ad4f43ceff2
|
7
|
+
data.tar.gz: 9d96851d9dc3947e485a1c09c89eb06c0e5b07a0ddf9c43e75bdd1c3bb13ca4df599e7f4e8050268ba30c00de7878d1a6c496db1f70b2e3e9c76f0bb10d3d63c
|
data/README.md
CHANGED
@@ -67,24 +67,19 @@ All timestamps are _forcibly_ stored in as UTC, bypassing the
|
|
67
67
|
* PostgreSQL >= 9.4
|
68
68
|
* The `btree_gist` PostgreSQL extension
|
69
69
|
|
70
|
-
With Homebrew:
|
71
|
-
|
72
|
-
brew install postgres
|
73
|
-
|
74
|
-
With apt:
|
75
|
-
|
76
|
-
apt-get install postgresql-11
|
77
|
-
|
78
70
|
## Installation
|
79
71
|
|
80
72
|
Add this line to your application's Gemfile:
|
81
73
|
|
82
|
-
|
74
|
+
```ruby
|
75
|
+
gem 'chrono_model'
|
76
|
+
```
|
83
77
|
|
84
78
|
And then execute:
|
85
79
|
|
86
|
-
|
87
|
-
|
80
|
+
```sh
|
81
|
+
$ bundle
|
82
|
+
```
|
88
83
|
|
89
84
|
## Configuration
|
90
85
|
|
@@ -99,7 +94,7 @@ development:
|
|
99
94
|
Configure Active Record in your `config/application.rb` to use the `:sql` schema
|
100
95
|
format:
|
101
96
|
|
102
|
-
```
|
97
|
+
```ruby
|
103
98
|
config.active_record.schema_format = :sql
|
104
99
|
```
|
105
100
|
|
@@ -121,7 +116,7 @@ view and all the trigger machinery. Every other housekeeping of the temporal
|
|
121
116
|
structure is handled behind the scenes by the other schema statements. E.g.:
|
122
117
|
|
123
118
|
* `rename_table` - renames tables, views, sequences, indexes and triggers
|
124
|
-
* `drop_table` - drops the temporal table and all
|
119
|
+
* `drop_table` - drops the temporal table and all dependent objects
|
125
120
|
* `add_column` - adds the column to the current table and updates triggers
|
126
121
|
* `rename_column` - renames the current table column and updates the triggers
|
127
122
|
* `remove_column` - removes the current table column and updates the triggers
|
@@ -192,10 +187,12 @@ occur (see https://github.com/ifad/chronomodel/issues/71).
|
|
192
187
|
In such cases, ensure to add `no_journal: %w( your_counter_cache_column_name )`
|
193
188
|
to your `create_table`. Example:
|
194
189
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
190
|
+
```ruby
|
191
|
+
create_table 'sections', temporal: true, no_journal: %w[articles_count] do |t|
|
192
|
+
t.string :name
|
193
|
+
t.integer :articles_count, default: 0
|
194
|
+
end
|
195
|
+
```
|
199
196
|
|
200
197
|
## Data querying
|
201
198
|
|
@@ -282,12 +279,12 @@ cannot be deleted.
|
|
282
279
|
|
283
280
|
ChronoModel currently performs upgrades by dropping and re-creating the views
|
284
281
|
that give access to current data. If you have built other database objects on
|
285
|
-
these views, the upgrade cannot be performed automatically as the
|
282
|
+
these views, the upgrade cannot be performed automatically as the dependent
|
286
283
|
objects must be dropped first.
|
287
284
|
|
288
285
|
When booting, ChronoModel will issue a warning in your logs about the need of
|
289
286
|
a structure upgrade. Structure usually changes across versions. In this case,
|
290
|
-
you need to set up a rake task that drops your
|
287
|
+
you need to set up a rake task that drops your dependent objects, runs
|
291
288
|
ChronoModel.upgrade! and then re-creates them.
|
292
289
|
|
293
290
|
A migration system should be introduced, but it is seen as overkill for now,
|
@@ -304,7 +301,9 @@ You need to connect as a database superuser, because specs need to create the
|
|
304
301
|
|
305
302
|
To run the full test suite, use
|
306
303
|
|
307
|
-
|
304
|
+
```sh
|
305
|
+
$ rake
|
306
|
+
```
|
308
307
|
|
309
308
|
SQL queries are logged to `spec/debug.log`. If you want to see them in your
|
310
309
|
output, set the `VERBOSE=true` environment variable.
|
@@ -313,7 +312,9 @@ Some tests check the nominal execution of rake tasks within a test Rails app,
|
|
313
312
|
and those are quite time consuming. You can run the full ChronoModel tests
|
314
313
|
only against ActiveRecord by using
|
315
314
|
|
316
|
-
|
315
|
+
```sh
|
316
|
+
$ rspec spec/chrono_model
|
317
|
+
```
|
317
318
|
|
318
319
|
Ensure to run the full test suite before pushing.
|
319
320
|
|
@@ -328,10 +329,6 @@ Ensure to run the full test suite before pushing.
|
|
328
329
|
of an object at a specific point in time within the application could
|
329
330
|
lead to inaccuracies.
|
330
331
|
|
331
|
-
* Rails 4+ support requires disabling tsrange parsing support, as it
|
332
|
-
[is broken][r4-tsrange-broken] and [incomplete][r4-tsrange-incomplete]
|
333
|
-
as of now, mainly due to a [design clash with ruby][pg-tsrange-and-ruby].
|
334
|
-
|
335
332
|
* The triggers and temporal indexes cannot be saved in schema.rb. The AR
|
336
333
|
schema dumper is quite basic, and it isn't (currently) extensible.
|
337
334
|
As we're using many database-specific features, Chronomodel forces the
|
@@ -351,7 +348,7 @@ Ensure to run the full test suite before pushing.
|
|
351
348
|
* Foreign keys are not supported. [See issue #174][gh-issue-174]
|
352
349
|
|
353
350
|
* There may be unexpected results when combining eager loading and joins.
|
354
|
-
|
351
|
+
[See issue #186][gh-issue-186]
|
355
352
|
|
356
353
|
* Global ID ignores historical objects. [See issue #192][gh-issue-192]
|
357
354
|
|
@@ -412,7 +409,6 @@ This software is Made in Italy :it: :smile:.
|
|
412
409
|
[pg-exclusion-constraints]: https://www.postgresql.org/docs/9.4/sql-createtable.html#SQL-CREATETABLE-EXCLUDE
|
413
410
|
[pg-btree-gist]: https://www.postgresql.org/docs/9.4/btree-gist.html
|
414
411
|
[pg-comment]: https://www.postgresql.org/docs/9.4/sql-comment.html
|
415
|
-
[pg-tsrange-and-ruby]: https://bugs.ruby-lang.org/issues/6864
|
416
412
|
[pg-ctes]: https://www.postgresql.org/docs/9.4/queries-with.html
|
417
413
|
[pg-cte-optimization-fence]: https://www.postgresql.org/message-id/201209191305.44674.db@kavod.com
|
418
414
|
[pg-cte-opt-out-fence]: https://www.postgresql.org/message-id/CAHyXU0zpM5+Dsb_pKxDmm-ZoWUAt=SkHHaiK_DBqcmtxTas6Nw@mail.gmail.com
|
@@ -420,9 +416,6 @@ This software is Made in Italy :it: :smile:.
|
|
420
416
|
[pg-json-func]: https://www.postgresql.org/docs/9.4/functions-json.html
|
421
417
|
[pg-json-opclass]: https://github.com/ifad/chronomodel/blob/master/sql/json_ops.sql
|
422
418
|
|
423
|
-
[r4-tsrange-broken]: https://github.com/rails/rails/pull/13793#issuecomment-34608093
|
424
|
-
[r4-tsrange-incomplete]: https://github.com/rails/rails/issues/14010
|
425
|
-
|
426
419
|
[cm-readme-sql]: https://github.com/ifad/chronomodel/blob/master/README.sql
|
427
420
|
[cm-timemachine]: https://github.com/ifad/chronomodel/blob/master/lib/chrono_model/time_machine.rb
|
428
421
|
[cm-cte-impl]: https://github.com/ifad/chronomodel/commit/18f4c4b
|
@@ -42,7 +42,7 @@ module ChronoModel
|
|
42
42
|
return if upgrade.empty?
|
43
43
|
|
44
44
|
logger.warn 'ChronoModel: There are tables needing a structure upgrade, and ChronoModel structures need to be recreated.'
|
45
|
-
logger.warn 'ChronoModel: Please run ChronoModel.upgrade! to attempt the upgrade. If you have
|
45
|
+
logger.warn 'ChronoModel: Please run ChronoModel.upgrade! to attempt the upgrade. If you have dependent database objects'
|
46
46
|
logger.warn 'ChronoModel: the upgrade will fail and you have to drop the dependent objects, run .upgrade! and create them'
|
47
47
|
logger.warn 'ChronoModel: again. Sorry. Some features or the whole library may not work correctly until upgrade is complete.'
|
48
48
|
logger.warn "ChronoModel: Tables pending upgrade: #{upgrade}"
|
data/lib/chrono_model/adapter.rb
CHANGED
@@ -7,7 +7,6 @@ require 'chrono_model/adapter/migrations_modules/stable'
|
|
7
7
|
|
8
8
|
require 'chrono_model/adapter/ddl'
|
9
9
|
require 'chrono_model/adapter/indexes'
|
10
|
-
require 'chrono_model/adapter/tsrange'
|
11
10
|
require 'chrono_model/adapter/upgrade'
|
12
11
|
|
13
12
|
module ChronoModel
|
@@ -19,7 +18,6 @@ module ChronoModel
|
|
19
18
|
include ChronoModel::Adapter::Migrations
|
20
19
|
include ChronoModel::Adapter::DDL
|
21
20
|
include ChronoModel::Adapter::Indexes
|
22
|
-
include ChronoModel::Adapter::TSRange
|
23
21
|
include ChronoModel::Adapter::Upgrade
|
24
22
|
|
25
23
|
# The schema holding current data
|
@@ -6,19 +6,6 @@ module ChronoModel
|
|
6
6
|
|
7
7
|
ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(?:\.(\d+))?\z/
|
8
8
|
|
9
|
-
# rubocop:disable Style/PerlBackrefs
|
10
|
-
def string_to_utc_time(string)
|
11
|
-
return string if string.is_a?(Time)
|
12
|
-
|
13
|
-
return unless string =~ ISO_DATETIME
|
14
|
-
|
15
|
-
# .1 is .100000, not .000001
|
16
|
-
usec = $7.ljust(6, '0') unless $7.nil?
|
17
|
-
|
18
|
-
Time.utc $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, usec.to_i
|
19
|
-
end
|
20
|
-
# rubocop:enable Style/PerlBackrefs
|
21
|
-
|
22
9
|
def time_to_utc_string(time)
|
23
10
|
time.to_fs(:db) << '.' << format('%06d', time.usec)
|
24
11
|
end
|
@@ -107,7 +107,7 @@ module ChronoModel
|
|
107
107
|
# name has the "::History" suffix but that is never going to be
|
108
108
|
# present in the data.
|
109
109
|
#
|
110
|
-
# As such it is
|
110
|
+
# As such it is overridden here to return the same contents that
|
111
111
|
# the parent would have returned.
|
112
112
|
delegate :sti_name, to: :superclass
|
113
113
|
|
@@ -132,7 +132,7 @@ module ChronoModel
|
|
132
132
|
end
|
133
133
|
|
134
134
|
# The history id is `hid`, but this cannot set as primary key
|
135
|
-
# or temporal
|
135
|
+
# or temporal associations will break. Solutions are welcome.
|
136
136
|
def id
|
137
137
|
hid
|
138
138
|
end
|
@@ -204,24 +204,24 @@ module ChronoModel
|
|
204
204
|
self.class.superclass.find(rid)
|
205
205
|
end
|
206
206
|
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
207
|
+
# Return `nil` instead of -Infinity/Infinity to preserve current
|
208
|
+
# Chronomodel behaviour and avoid failures with Rails 7.0 and
|
209
|
+
# unbounded time ranges
|
210
|
+
#
|
211
|
+
# Check if `begin` and `end` are `Time` because validity is a `tsrange`
|
212
|
+
# column, so it is either `Time`, `nil`, and in some cases Infinity.
|
213
|
+
#
|
214
|
+
# Ref: rails/rails#45099
|
215
|
+
# TODO: consider removing when Rails 7.0 support will be dropped
|
212
216
|
def valid_from
|
213
|
-
validity.
|
217
|
+
validity.begin if validity.begin.is_a?(Time)
|
214
218
|
end
|
215
219
|
|
216
220
|
def valid_to
|
217
|
-
validity.
|
221
|
+
validity.end if validity.end.is_a?(Time)
|
218
222
|
end
|
219
223
|
alias as_of_time valid_to
|
220
224
|
|
221
|
-
def recorded_at
|
222
|
-
ChronoModel::Conversions.string_to_utc_time attributes_before_type_cast['recorded_at']
|
223
|
-
end
|
224
|
-
|
225
225
|
# Starting from Rails 6.0, `.read_attribute` will use the memoized
|
226
226
|
# `primary_key` if it detects that the attribute name is `id`.
|
227
227
|
#
|
@@ -81,9 +81,7 @@ module ChronoModel
|
|
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")
|
85
|
-
Conversions.string_to_utc_time ts
|
86
|
-
end
|
84
|
+
connection.select_values(sql, "#{name} periods")
|
87
85
|
end
|
88
86
|
end
|
89
87
|
|
data/lib/chrono_model/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chrono_model
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 4.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Marcello Barnaba
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2024-
|
12
|
+
date: 2024-05-12 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
@@ -72,7 +72,6 @@ files:
|
|
72
72
|
- lib/chrono_model/adapter/indexes.rb
|
73
73
|
- lib/chrono_model/adapter/migrations.rb
|
74
74
|
- lib/chrono_model/adapter/migrations_modules/stable.rb
|
75
|
-
- lib/chrono_model/adapter/tsrange.rb
|
76
75
|
- lib/chrono_model/adapter/upgrade.rb
|
77
76
|
- lib/chrono_model/chrono.rb
|
78
77
|
- lib/chrono_model/conversions.rb
|
@@ -117,7 +116,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
117
116
|
- !ruby/object:Gem::Version
|
118
117
|
version: '0'
|
119
118
|
requirements: []
|
120
|
-
rubygems_version: 3.5.
|
119
|
+
rubygems_version: 3.5.9
|
121
120
|
signing_key:
|
122
121
|
specification_version: 4
|
123
122
|
summary: Temporal extensions (SCD Type II) for Active Record
|
@@ -1,72 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ChronoModel
|
4
|
-
class Adapter < ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
|
5
|
-
module TSRange
|
6
|
-
# HACK: Redefine tsrange parsing support, as it is broken currently.
|
7
|
-
#
|
8
|
-
# This self-made API is here because currently AR4 does not support
|
9
|
-
# open-ended ranges. The reasons are poor support in Ruby:
|
10
|
-
#
|
11
|
-
# https://bugs.ruby-lang.org/issues/6864
|
12
|
-
#
|
13
|
-
# and an instable interface in Active Record:
|
14
|
-
#
|
15
|
-
# https://github.com/rails/rails/issues/13793
|
16
|
-
# https://github.com/rails/rails/issues/14010
|
17
|
-
#
|
18
|
-
# so, for now, we are implementing our own.
|
19
|
-
#
|
20
|
-
class Type < ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Range
|
21
|
-
OID = 3908
|
22
|
-
|
23
|
-
def cast_value(value)
|
24
|
-
return if value == 'empty'
|
25
|
-
return value if value.is_a?(::Array)
|
26
|
-
|
27
|
-
extracted = extract_bounds(value)
|
28
|
-
|
29
|
-
from = Conversions.string_to_utc_time extracted[:from]
|
30
|
-
to = Conversions.string_to_utc_time extracted[:to]
|
31
|
-
|
32
|
-
[from, to]
|
33
|
-
end
|
34
|
-
|
35
|
-
def extract_bounds(value)
|
36
|
-
from, to = value[1..-2].split(',')
|
37
|
-
|
38
|
-
from_bound =
|
39
|
-
if value[1] == ',' || from == '-infinity'
|
40
|
-
nil
|
41
|
-
else
|
42
|
-
from[1..-2]
|
43
|
-
end
|
44
|
-
|
45
|
-
to_bound =
|
46
|
-
if value[-2] == ',' || to == 'infinity'
|
47
|
-
nil
|
48
|
-
else
|
49
|
-
to[1..-2]
|
50
|
-
end
|
51
|
-
|
52
|
-
{
|
53
|
-
from: from_bound,
|
54
|
-
to: to_bound
|
55
|
-
}
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
def initialize_type_map(m = type_map)
|
60
|
-
super.tap do
|
61
|
-
typ = ChronoModel::Adapter::TSRange::Type
|
62
|
-
oid = typ::OID
|
63
|
-
|
64
|
-
ar_type = type_map.fetch(oid)
|
65
|
-
cm_type = typ.new(ar_type.subtype, ar_type.type)
|
66
|
-
|
67
|
-
type_map.register_type oid, cm_type
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|