chrono_model 2.0.0 → 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/LICENSE +3 -3
- data/README.md +37 -55
- data/lib/active_record/connection_adapters/chronomodel_adapter.rb +2 -8
- data/lib/active_record/tasks/chronomodel_database_tasks.rb +7 -47
- data/lib/chrono_model/adapter/ddl.rb +3 -3
- data/lib/chrono_model/adapter/upgrade.rb +1 -1
- data/lib/chrono_model/adapter.rb +1 -8
- data/lib/chrono_model/conversions.rb +2 -15
- data/lib/chrono_model/db_console.rb +1 -5
- data/lib/chrono_model/patches/as_of_time_relation.rb +1 -11
- data/lib/chrono_model/patches/batches.rb +4 -8
- data/lib/chrono_model/patches/relation.rb +9 -11
- data/lib/chrono_model/railtie.rb +2 -21
- 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 +2 -4
- data/lib/chrono_model/time_machine.rb +19 -28
- data/lib/chrono_model/version.rb +1 -1
- data/lib/chrono_model.rb +2 -2
- metadata +7 -150
- data/lib/chrono_model/adapter/migrations_modules/legacy.rb +0 -41
- data/lib/chrono_model/adapter/tsrange.rb +0 -72
- data/lib/chrono_model/json.rb +0 -31
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/LICENSE
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2012-
|
|
4
|
-
Copyright (c) 2012-
|
|
5
|
-
Copyright (c) 2012-
|
|
3
|
+
Copyright (c) 2012-2024 Marcello Barnaba <m.barnaba@ifad.org>
|
|
4
|
+
Copyright (c) 2012-2024 Peter J. Brindisi <p.brindisi@ifad.org>
|
|
5
|
+
Copyright (c) 2012-2024 IFAD
|
|
6
6
|
|
|
7
7
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
8
|
of this software and associated documentation files (the "Software"), to deal
|
data/README.md
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# Temporal database system on PostgreSQL using [updatable views][pg-updatable-views], [table inheritance][pg-table-inheritance] and [INSTEAD OF triggers][pg-instead-of-triggers].
|
|
2
2
|
|
|
3
3
|
[![Build Status][build-status-badge]][build-status]
|
|
4
|
-
[![Legacy Build Status][legacy-build-status-badge]][build-status]
|
|
5
4
|
[![Code Climate][code-analysis-badge]][code-analysis]
|
|
6
5
|
[![Test Coverage][test-coverage-badge]][test-coverage]
|
|
7
6
|
[![Gem Version][gem-version-badge]][gem-version]
|
|
@@ -63,29 +62,24 @@ All timestamps are _forcibly_ stored in as UTC, bypassing the
|
|
|
63
62
|
|
|
64
63
|
## Requirements
|
|
65
64
|
|
|
66
|
-
* Ruby >=
|
|
67
|
-
* Active Record >=
|
|
68
|
-
* PostgreSQL >= 9.4
|
|
65
|
+
* Ruby >= 3.0
|
|
66
|
+
* Active Record >= 7.0. See the [detailed supported versions matrix on Ruby GitHub Actions workflows](https://github.com/ifad/chronomodel/blob/master/.github/workflows)
|
|
67
|
+
* PostgreSQL >= 9.4
|
|
69
68
|
* The `btree_gist` PostgreSQL extension
|
|
70
69
|
|
|
71
|
-
With Homebrew:
|
|
72
|
-
|
|
73
|
-
brew install postgres
|
|
74
|
-
|
|
75
|
-
With apt:
|
|
76
|
-
|
|
77
|
-
apt-get install postgresql-11
|
|
78
|
-
|
|
79
70
|
## Installation
|
|
80
71
|
|
|
81
72
|
Add this line to your application's Gemfile:
|
|
82
73
|
|
|
83
|
-
|
|
74
|
+
```ruby
|
|
75
|
+
gem 'chrono_model'
|
|
76
|
+
```
|
|
84
77
|
|
|
85
78
|
And then execute:
|
|
86
79
|
|
|
87
|
-
|
|
88
|
-
|
|
80
|
+
```sh
|
|
81
|
+
$ bundle
|
|
82
|
+
```
|
|
89
83
|
|
|
90
84
|
## Configuration
|
|
91
85
|
|
|
@@ -100,7 +94,7 @@ development:
|
|
|
100
94
|
Configure Active Record in your `config/application.rb` to use the `:sql` schema
|
|
101
95
|
format:
|
|
102
96
|
|
|
103
|
-
```
|
|
97
|
+
```ruby
|
|
104
98
|
config.active_record.schema_format = :sql
|
|
105
99
|
```
|
|
106
100
|
|
|
@@ -122,7 +116,7 @@ view and all the trigger machinery. Every other housekeeping of the temporal
|
|
|
122
116
|
structure is handled behind the scenes by the other schema statements. E.g.:
|
|
123
117
|
|
|
124
118
|
* `rename_table` - renames tables, views, sequences, indexes and triggers
|
|
125
|
-
* `drop_table` - drops the temporal table and all
|
|
119
|
+
* `drop_table` - drops the temporal table and all dependent objects
|
|
126
120
|
* `add_column` - adds the column to the current table and updates triggers
|
|
127
121
|
* `rename_column` - renames the current table column and updates the triggers
|
|
128
122
|
* `remove_column` - removes the current table column and updates the triggers
|
|
@@ -193,10 +187,12 @@ occur (see https://github.com/ifad/chronomodel/issues/71).
|
|
|
193
187
|
In such cases, ensure to add `no_journal: %w( your_counter_cache_column_name )`
|
|
194
188
|
to your `create_table`. Example:
|
|
195
189
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
+
```
|
|
200
196
|
|
|
201
197
|
## Data querying
|
|
202
198
|
|
|
@@ -283,12 +279,12 @@ cannot be deleted.
|
|
|
283
279
|
|
|
284
280
|
ChronoModel currently performs upgrades by dropping and re-creating the views
|
|
285
281
|
that give access to current data. If you have built other database objects on
|
|
286
|
-
these views, the upgrade cannot be performed automatically as the
|
|
282
|
+
these views, the upgrade cannot be performed automatically as the dependent
|
|
287
283
|
objects must be dropped first.
|
|
288
284
|
|
|
289
285
|
When booting, ChronoModel will issue a warning in your logs about the need of
|
|
290
286
|
a structure upgrade. Structure usually changes across versions. In this case,
|
|
291
|
-
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
|
|
292
288
|
ChronoModel.upgrade! and then re-creates them.
|
|
293
289
|
|
|
294
290
|
A migration system should be introduced, but it is seen as overkill for now,
|
|
@@ -305,7 +301,9 @@ You need to connect as a database superuser, because specs need to create the
|
|
|
305
301
|
|
|
306
302
|
To run the full test suite, use
|
|
307
303
|
|
|
308
|
-
|
|
304
|
+
```sh
|
|
305
|
+
$ rake
|
|
306
|
+
```
|
|
309
307
|
|
|
310
308
|
SQL queries are logged to `spec/debug.log`. If you want to see them in your
|
|
311
309
|
output, set the `VERBOSE=true` environment variable.
|
|
@@ -314,36 +312,22 @@ Some tests check the nominal execution of rake tasks within a test Rails app,
|
|
|
314
312
|
and those are quite time consuming. You can run the full ChronoModel tests
|
|
315
313
|
only against ActiveRecord by using
|
|
316
314
|
|
|
317
|
-
|
|
315
|
+
```sh
|
|
316
|
+
$ rspec spec/chrono_model
|
|
317
|
+
```
|
|
318
318
|
|
|
319
319
|
Ensure to run the full test suite before pushing.
|
|
320
320
|
|
|
321
|
-
## Usage with JSON (*not* JSONB) columns
|
|
322
|
-
|
|
323
|
-
**DEPRECATED**: Please migrate to JSONB. It has an equality operator built-in,
|
|
324
|
-
it's faster and stricter, and offers many more indexing abilities and better
|
|
325
|
-
performance than JSON. It is going to be desupported soon because PostgreSQL 10
|
|
326
|
-
does not support these anymore.
|
|
327
|
-
|
|
328
|
-
The [JSON][pg-json-type] does not provide an [equality operator][pg-json-func].
|
|
329
|
-
As both unnecessary update suppression and selective journaling require
|
|
330
|
-
comparing the OLD and NEW rows fields, this fails by default.
|
|
331
|
-
|
|
332
|
-
ChronoModel provides a naive and heavyweight JSON equality operator using
|
|
333
|
-
[pl/python][pg-json-opclass] and associated Postgres objects.
|
|
334
|
-
|
|
335
|
-
To set up you can use
|
|
336
|
-
|
|
337
|
-
```ruby
|
|
338
|
-
require 'chrono_model/json'
|
|
339
|
-
ChronoModel::Json.create
|
|
340
|
-
```
|
|
341
|
-
|
|
342
321
|
## Caveats
|
|
343
322
|
|
|
344
|
-
*
|
|
345
|
-
|
|
346
|
-
|
|
323
|
+
* Considering the nature of modern applications, it's crucial to understand
|
|
324
|
+
that the database time does not necessarily align with the application time
|
|
325
|
+
due to the delay introduced by communication between the application and
|
|
326
|
+
the database server. Consequently, there is no assurance that the application
|
|
327
|
+
time will always be less than the database time. Therefore, relying solely
|
|
328
|
+
on `created_at` and `updated_at` fields as timestamps to determine the state
|
|
329
|
+
of an object at a specific point in time within the application could
|
|
330
|
+
lead to inaccuracies.
|
|
347
331
|
|
|
348
332
|
* The triggers and temporal indexes cannot be saved in schema.rb. The AR
|
|
349
333
|
schema dumper is quite basic, and it isn't (currently) extensible.
|
|
@@ -364,13 +348,16 @@ ChronoModel::Json.create
|
|
|
364
348
|
* Foreign keys are not supported. [See issue #174][gh-issue-174]
|
|
365
349
|
|
|
366
350
|
* There may be unexpected results when combining eager loading and joins.
|
|
367
|
-
|
|
351
|
+
[See issue #186][gh-issue-186]
|
|
368
352
|
|
|
369
353
|
* Global ID ignores historical objects. [See issue #192][gh-issue-192]
|
|
370
354
|
|
|
371
355
|
* Different historical objects are considered the identical. [See issue
|
|
372
356
|
#206][gh-issue-206]
|
|
373
357
|
|
|
358
|
+
* Use with caution when implementing inline editing features, as Chronomodel
|
|
359
|
+
creates a new record for each modification. This will lead to increased
|
|
360
|
+
storage requirements and bloated history
|
|
374
361
|
|
|
375
362
|
## Contributing
|
|
376
363
|
|
|
@@ -402,7 +389,6 @@ This software is Made in Italy :it: :smile:.
|
|
|
402
389
|
[docs-analysis-badge]: https://inch-ci.org/github/ifad/chronomodel.svg?branch=master
|
|
403
390
|
[gem-version]: https://rubygems.org/gems/chrono_model
|
|
404
391
|
[gem-version-badge]: https://badge.fury.io/rb/chrono_model.svg
|
|
405
|
-
[legacy-build-status-badge]: https://github.com/ifad/chronomodel/actions/workflows/legacy_ruby.yml/badge.svg
|
|
406
392
|
[test-coverage]: https://codeclimate.com/github/ifad/chronomodel
|
|
407
393
|
[test-coverage-badge]: https://codeclimate.com/github/ifad/chronomodel/badges/coverage.svg
|
|
408
394
|
|
|
@@ -423,7 +409,6 @@ This software is Made in Italy :it: :smile:.
|
|
|
423
409
|
[pg-exclusion-constraints]: https://www.postgresql.org/docs/9.4/sql-createtable.html#SQL-CREATETABLE-EXCLUDE
|
|
424
410
|
[pg-btree-gist]: https://www.postgresql.org/docs/9.4/btree-gist.html
|
|
425
411
|
[pg-comment]: https://www.postgresql.org/docs/9.4/sql-comment.html
|
|
426
|
-
[pg-tsrange-and-ruby]: https://bugs.ruby-lang.org/issues/6864
|
|
427
412
|
[pg-ctes]: https://www.postgresql.org/docs/9.4/queries-with.html
|
|
428
413
|
[pg-cte-optimization-fence]: https://www.postgresql.org/message-id/201209191305.44674.db@kavod.com
|
|
429
414
|
[pg-cte-opt-out-fence]: https://www.postgresql.org/message-id/CAHyXU0zpM5+Dsb_pKxDmm-ZoWUAt=SkHHaiK_DBqcmtxTas6Nw@mail.gmail.com
|
|
@@ -431,9 +416,6 @@ This software is Made in Italy :it: :smile:.
|
|
|
431
416
|
[pg-json-func]: https://www.postgresql.org/docs/9.4/functions-json.html
|
|
432
417
|
[pg-json-opclass]: https://github.com/ifad/chronomodel/blob/master/sql/json_ops.sql
|
|
433
418
|
|
|
434
|
-
[r4-tsrange-broken]: https://github.com/rails/rails/pull/13793#issuecomment-34608093
|
|
435
|
-
[r4-tsrange-incomplete]: https://github.com/rails/rails/issues/14010
|
|
436
|
-
|
|
437
419
|
[cm-readme-sql]: https://github.com/ifad/chronomodel/blob/master/README.sql
|
|
438
420
|
[cm-timemachine]: https://github.com/ifad/chronomodel/blob/master/lib/chrono_model/time_machine.rb
|
|
439
421
|
[cm-cte-impl]: https://github.com/ifad/chronomodel/commit/18f4c4b
|
|
@@ -16,9 +16,7 @@ module ActiveRecord
|
|
|
16
16
|
def chronomodel_connection(config) # :nodoc:
|
|
17
17
|
return chronomodel_adapter_class.new(config) if ActiveRecord::VERSION::STRING >= '7.1'
|
|
18
18
|
|
|
19
|
-
conn_params = config.symbolize_keys
|
|
20
|
-
|
|
21
|
-
conn_params.delete_if { |_, v| v.nil? }
|
|
19
|
+
conn_params = config.symbolize_keys.compact
|
|
22
20
|
|
|
23
21
|
# Map ActiveRecords param names to PGs.
|
|
24
22
|
conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
|
|
@@ -28,7 +26,7 @@ module ActiveRecord
|
|
|
28
26
|
valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl]
|
|
29
27
|
conn_params.slice!(*valid_conn_param_keys)
|
|
30
28
|
|
|
31
|
-
conn = PG.connect(conn_params)
|
|
29
|
+
conn = PG.connect(conn_params)
|
|
32
30
|
|
|
33
31
|
adapter = ChronoModel::Adapter.new(conn, logger, conn_params, config)
|
|
34
32
|
|
|
@@ -40,10 +38,6 @@ module ActiveRecord
|
|
|
40
38
|
adapter.chrono_setup!
|
|
41
39
|
|
|
42
40
|
adapter
|
|
43
|
-
rescue ::PG::Error => e
|
|
44
|
-
raise ActiveRecord::NoDatabaseError if e.message.include?(conn_params[:dbname])
|
|
45
|
-
|
|
46
|
-
raise
|
|
47
41
|
end
|
|
48
42
|
end
|
|
49
43
|
end
|
|
@@ -24,8 +24,6 @@ module ActiveRecord
|
|
|
24
24
|
filename = arguments.first
|
|
25
25
|
sql = File.read(filename).gsub(/CREATE SCHEMA (?!IF NOT EXISTS)/, '\&IF NOT EXISTS ')
|
|
26
26
|
File.open(filename, 'w') { |file| file << sql }
|
|
27
|
-
|
|
28
|
-
remove_sql_header_comments(filename) if ActiveRecord::VERSION::STRING < '5.1'
|
|
29
27
|
end
|
|
30
28
|
|
|
31
29
|
def data_dump(target)
|
|
@@ -48,15 +46,8 @@ module ActiveRecord
|
|
|
48
46
|
|
|
49
47
|
private
|
|
50
48
|
|
|
51
|
-
# In Rails 6.1.x the configuration instance variable is not available
|
|
52
|
-
# and it's been replaced by @configuration_hash (which is frozen).
|
|
53
49
|
def chronomodel_configuration
|
|
54
|
-
@chronomodel_configuration ||=
|
|
55
|
-
if defined?(@configuration_hash)
|
|
56
|
-
@configuration_hash
|
|
57
|
-
else
|
|
58
|
-
configuration.with_indifferent_access
|
|
59
|
-
end
|
|
50
|
+
@chronomodel_configuration ||= @configuration_hash
|
|
60
51
|
end
|
|
61
52
|
|
|
62
53
|
# If a schema search path is defined in the configuration file, it will
|
|
@@ -77,46 +68,15 @@ module ActiveRecord
|
|
|
77
68
|
|
|
78
69
|
chronomodel_schema_search_path = "#{schema_search_path},#{CHRONOMODEL_SCHEMAS.join(',')}"
|
|
79
70
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
@configuration_hash.freeze
|
|
84
|
-
else
|
|
85
|
-
configuration['schema_search_path'] = chronomodel_schema_search_path
|
|
86
|
-
end
|
|
71
|
+
@configuration_hash = @configuration_hash.dup
|
|
72
|
+
@configuration_hash[:schema_search_path] = chronomodel_schema_search_path
|
|
73
|
+
@configuration_hash.freeze
|
|
87
74
|
end
|
|
88
75
|
|
|
89
76
|
def reset_configuration!
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
@configuration_hash.freeze
|
|
94
|
-
else
|
|
95
|
-
configuration['schema_search_path'] = @original_schema_search_path
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
unless private_instance_methods.include?(:remove_sql_header_comments)
|
|
100
|
-
def remove_sql_header_comments(filename)
|
|
101
|
-
sql_comment_begin = '--'
|
|
102
|
-
removing_comments = true
|
|
103
|
-
tempfile = Tempfile.open('uncommented_structure.sql')
|
|
104
|
-
begin
|
|
105
|
-
File.foreach(filename) do |line|
|
|
106
|
-
unless removing_comments && (line.start_with?(sql_comment_begin) || line.blank?)
|
|
107
|
-
tempfile << line
|
|
108
|
-
removing_comments = false
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
ensure
|
|
112
|
-
tempfile.close
|
|
113
|
-
end
|
|
114
|
-
FileUtils.mv(tempfile.path, filename)
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
unless private_instance_methods.include?(:psql_env)
|
|
119
|
-
alias psql_env set_psql_env
|
|
77
|
+
@configuration_hash = @configuration_hash.dup
|
|
78
|
+
@configuration_hash[:schema_search_path] = @original_schema_search_path
|
|
79
|
+
@configuration_hash.freeze
|
|
120
80
|
end
|
|
121
81
|
|
|
122
82
|
def schema_search_path
|
|
@@ -82,7 +82,7 @@ module ChronoModel
|
|
|
82
82
|
# allow setting the PK to a specific value (think migration scenario).
|
|
83
83
|
#
|
|
84
84
|
def chrono_create_INSERT_trigger(table, pk, current, history, fields, values)
|
|
85
|
-
execute <<-SQL.strip_heredoc # rubocop:disable Rails/SquishedSQLHeredocs
|
|
85
|
+
execute <<-SQL.strip_heredoc # rubocop:disable Rails/SquishedSQLHeredocs,Rails/StripHeredoc
|
|
86
86
|
CREATE OR REPLACE FUNCTION chronomodel_#{table}_insert() RETURNS TRIGGER AS $$
|
|
87
87
|
BEGIN
|
|
88
88
|
#{insert_sequence_sql(pk, current)} INTO #{current} ( #{pk}, #{fields} )
|
|
@@ -135,7 +135,7 @@ module ChronoModel
|
|
|
135
135
|
|
|
136
136
|
journal &= columns
|
|
137
137
|
|
|
138
|
-
execute <<-SQL.strip_heredoc # rubocop:disable Rails/SquishedSQLHeredocs
|
|
138
|
+
execute <<-SQL.strip_heredoc # rubocop:disable Rails/SquishedSQLHeredocs,Rails/StripHeredoc
|
|
139
139
|
CREATE OR REPLACE FUNCTION chronomodel_#{table}_update() RETURNS TRIGGER AS $$
|
|
140
140
|
DECLARE _now timestamp;
|
|
141
141
|
DECLARE _hid integer;
|
|
@@ -189,7 +189,7 @@ module ChronoModel
|
|
|
189
189
|
# DELETEd in the same transaction.
|
|
190
190
|
#
|
|
191
191
|
def chrono_create_DELETE_trigger(table, pk, current, history)
|
|
192
|
-
execute <<-SQL.strip_heredoc # rubocop:disable Rails/SquishedSQLHeredocs
|
|
192
|
+
execute <<-SQL.strip_heredoc # rubocop:disable Rails/SquishedSQLHeredocs,Rails/StripHeredoc
|
|
193
193
|
CREATE OR REPLACE FUNCTION chronomodel_#{table}_delete() RETURNS TRIGGER AS $$
|
|
194
194
|
DECLARE _now timestamp;
|
|
195
195
|
BEGIN
|
|
@@ -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
|
@@ -3,16 +3,10 @@
|
|
|
3
3
|
require 'active_record/connection_adapters/postgresql_adapter'
|
|
4
4
|
|
|
5
5
|
require 'chrono_model/adapter/migrations'
|
|
6
|
-
|
|
7
|
-
if ActiveRecord::VERSION::STRING >= '6.1'
|
|
8
|
-
require 'chrono_model/adapter/migrations_modules/stable'
|
|
9
|
-
else
|
|
10
|
-
require 'chrono_model/adapter/migrations_modules/legacy'
|
|
11
|
-
end
|
|
6
|
+
require 'chrono_model/adapter/migrations_modules/stable'
|
|
12
7
|
|
|
13
8
|
require 'chrono_model/adapter/ddl'
|
|
14
9
|
require 'chrono_model/adapter/indexes'
|
|
15
|
-
require 'chrono_model/adapter/tsrange'
|
|
16
10
|
require 'chrono_model/adapter/upgrade'
|
|
17
11
|
|
|
18
12
|
module ChronoModel
|
|
@@ -24,7 +18,6 @@ module ChronoModel
|
|
|
24
18
|
include ChronoModel::Adapter::Migrations
|
|
25
19
|
include ChronoModel::Adapter::DDL
|
|
26
20
|
include ChronoModel::Adapter::Indexes
|
|
27
|
-
include ChronoModel::Adapter::TSRange
|
|
28
21
|
include ChronoModel::Adapter::Upgrade
|
|
29
22
|
|
|
30
23
|
# The schema holding current data
|
|
@@ -4,23 +4,10 @@ module ChronoModel
|
|
|
4
4
|
module Conversions
|
|
5
5
|
module_function
|
|
6
6
|
|
|
7
|
-
ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(?:\.(\d+))?\z
|
|
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
|
|
7
|
+
ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(?:\.(\d+))?\z/
|
|
21
8
|
|
|
22
9
|
def time_to_utc_string(time)
|
|
23
|
-
time.
|
|
10
|
+
time.to_fs(:db) << '.' << format('%06d', time.usec)
|
|
24
11
|
end
|
|
25
12
|
end
|
|
26
13
|
end
|
|
@@ -2,8 +2,4 @@
|
|
|
2
2
|
|
|
3
3
|
require 'chrono_model/patches/db_console'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
Rails::DBConsole.prepend ChronoModel::Patches::DBConsole::Config
|
|
7
|
-
else
|
|
8
|
-
Rails::DBConsole.prepend ChronoModel::Patches::DBConsole::DbConfig
|
|
9
|
-
end
|
|
5
|
+
Rails::DBConsole.prepend ChronoModel::Patches::DBConsole::DbConfig
|
|
@@ -4,16 +4,6 @@ module ChronoModel
|
|
|
4
4
|
module Patches
|
|
5
5
|
# This class is a dummy relation whose scope is only to pass around the
|
|
6
6
|
# as_of_time parameters across ActiveRecord call chains.
|
|
7
|
-
|
|
8
|
-
# With AR 5.2 a simple relation can be used, as the only required argument
|
|
9
|
-
# is the model. 5.0 and 5.1 require more arguments, that are passed here.
|
|
10
|
-
#
|
|
11
|
-
class AsOfTimeRelation < ActiveRecord::Relation
|
|
12
|
-
if ActiveRecord::VERSION::STRING.to_f < 5.2
|
|
13
|
-
def initialize(klass, table: klass.arel_table, predicate_builder: klass.predicate_builder, values: {})
|
|
14
|
-
super(klass, table, predicate_builder, values)
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
7
|
+
class AsOfTimeRelation < ActiveRecord::Relation; end
|
|
18
8
|
end
|
|
19
9
|
end
|
|
@@ -3,14 +3,10 @@
|
|
|
3
3
|
module ChronoModel
|
|
4
4
|
module Patches
|
|
5
5
|
module Batches
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
else
|
|
11
|
-
super
|
|
12
|
-
end
|
|
13
|
-
end
|
|
6
|
+
def in_batches(**)
|
|
7
|
+
return super unless try(:history?)
|
|
8
|
+
|
|
9
|
+
with_hid_pkey { super }
|
|
14
10
|
end
|
|
15
11
|
end
|
|
16
12
|
end
|
|
@@ -5,17 +5,15 @@ module ChronoModel
|
|
|
5
5
|
module Relation
|
|
6
6
|
include ChronoModel::Patches::AsOfTimeHolder
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
).call
|
|
18
|
-
end
|
|
8
|
+
def preload_associations(records) # :nodoc:
|
|
9
|
+
preload = preload_values
|
|
10
|
+
preload += includes_values unless eager_loading?
|
|
11
|
+
scope = StrictLoadingScope if strict_loading_value
|
|
12
|
+
|
|
13
|
+
preload.each do |associations|
|
|
14
|
+
ActiveRecord::Associations::Preloader.new(
|
|
15
|
+
records: records, associations: associations, scope: scope, model: model, as_of_time: as_of_time
|
|
16
|
+
).call
|
|
19
17
|
end
|
|
20
18
|
end
|
|
21
19
|
|
data/lib/chrono_model/railtie.rb
CHANGED
|
@@ -7,30 +7,11 @@ module ChronoModel
|
|
|
7
7
|
TASKS_CLASS = ActiveRecord::Tasks::ChronomodelDatabaseTasks
|
|
8
8
|
|
|
9
9
|
# Register our database tasks under our adapter name
|
|
10
|
-
|
|
11
|
-
ActiveRecord::Tasks::DatabaseTasks.register_task(/chronomodel/, TASKS_CLASS)
|
|
12
|
-
else
|
|
13
|
-
ActiveRecord::Tasks::DatabaseTasks.register_task(/chronomodel/, TASKS_CLASS.to_s)
|
|
14
|
-
end
|
|
10
|
+
ActiveRecord::Tasks::DatabaseTasks.register_task(/chronomodel/, TASKS_CLASS.to_s)
|
|
15
11
|
|
|
16
12
|
rake_tasks do
|
|
17
13
|
def task_config
|
|
18
|
-
|
|
19
|
-
ActiveRecord::Tasks::DatabaseTasks.current_config.with_indifferent_access
|
|
20
|
-
else
|
|
21
|
-
ActiveRecord::Base.connection_db_config
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
if Rails.version < '6.1'
|
|
26
|
-
# Make schema:dump and schema:load invoke structure:dump and structure:load
|
|
27
|
-
Rake::Task['db:schema:dump'].clear.enhance(['environment']) do
|
|
28
|
-
Rake::Task['db:structure:dump'].invoke
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
Rake::Task['db:schema:load'].clear.enhance(['environment']) do
|
|
32
|
-
Rake::Task['db:structure:load'].invoke
|
|
33
|
-
end
|
|
14
|
+
ActiveRecord::Base.connection_db_config
|
|
34
15
|
end
|
|
35
16
|
|
|
36
17
|
desc 'Dumps database into db/data.NOW.sql or file specified via DUMP='
|
|
@@ -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
|
#
|
|
@@ -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])}'"
|
|
@@ -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
|
|
|
@@ -22,37 +22,28 @@ module ChronoModel
|
|
|
22
22
|
ChronoModel.history_models[table_name] = history
|
|
23
23
|
|
|
24
24
|
class << self
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def subclasses_with_history
|
|
33
|
-
subclasses(with_history: true)
|
|
34
|
-
end
|
|
25
|
+
def subclasses(with_history: false)
|
|
26
|
+
subclasses = super()
|
|
27
|
+
subclasses.reject!(&:history?) unless with_history
|
|
28
|
+
subclasses
|
|
29
|
+
end
|
|
35
30
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
alias_method :direct_descendants_with_history, :subclasses_with_history
|
|
40
|
-
alias_method :direct_descendants, :subclasses
|
|
41
|
-
end
|
|
31
|
+
def subclasses_with_history
|
|
32
|
+
subclasses(with_history: true)
|
|
33
|
+
end
|
|
42
34
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
end
|
|
50
|
-
else
|
|
51
|
-
alias_method :descendants_with_history, :descendants
|
|
35
|
+
# `direct_descendants` is deprecated method in 7.0 and has been
|
|
36
|
+
# removed in 7.1
|
|
37
|
+
if method_defined?(:direct_descendants)
|
|
38
|
+
alias_method :direct_descendants_with_history, :subclasses_with_history
|
|
39
|
+
alias_method :direct_descendants, :subclasses
|
|
40
|
+
end
|
|
52
41
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
42
|
+
# Ruby 3.1 has a native subclasses method and descendants is
|
|
43
|
+
# implemented with recursion of subclasses
|
|
44
|
+
if Class.method_defined?(:subclasses)
|
|
45
|
+
def descendants_with_history
|
|
46
|
+
subclasses_with_history.concat(subclasses.flat_map(&:descendants_with_history))
|
|
56
47
|
end
|
|
57
48
|
end
|
|
58
49
|
|
data/lib/chrono_model/version.rb
CHANGED
data/lib/chrono_model.rb
CHANGED
|
@@ -33,7 +33,7 @@ module ChronoModel
|
|
|
33
33
|
# Computed upon inclusion of the +TimeMachine+ module.
|
|
34
34
|
#
|
|
35
35
|
def self.history_models
|
|
36
|
-
@
|
|
36
|
+
@history_models ||= {}
|
|
37
37
|
end
|
|
38
38
|
end
|
|
39
39
|
|
|
@@ -56,7 +56,7 @@ ActiveSupport.on_load :active_record do
|
|
|
56
56
|
|
|
57
57
|
ActiveRecord::Associations::Preloader::ThroughAssociation.prepend ChronoModel::Patches::Preloader::ThroughAssociation
|
|
58
58
|
|
|
59
|
-
ActiveRecord::Batches
|
|
59
|
+
ActiveRecord::Batches.prepend ChronoModel::Patches::Batches
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
ActiveSupport.on_load :after_initialize do
|
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
|
|
@@ -17,14 +17,14 @@ dependencies:
|
|
|
17
17
|
requirements:
|
|
18
18
|
- - ">="
|
|
19
19
|
- !ruby/object:Gem::Version
|
|
20
|
-
version: '
|
|
20
|
+
version: '7.0'
|
|
21
21
|
type: :runtime
|
|
22
22
|
prerelease: false
|
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
|
24
24
|
requirements:
|
|
25
25
|
- - ">="
|
|
26
26
|
- !ruby/object:Gem::Version
|
|
27
|
-
version: '
|
|
27
|
+
version: '7.0'
|
|
28
28
|
- !ruby/object:Gem::Dependency
|
|
29
29
|
name: multi_json
|
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -41,152 +41,12 @@ dependencies:
|
|
|
41
41
|
version: '0'
|
|
42
42
|
- !ruby/object:Gem::Dependency
|
|
43
43
|
name: pg
|
|
44
|
-
requirement: !ruby/object:Gem::Requirement
|
|
45
|
-
requirements:
|
|
46
|
-
- - ">"
|
|
47
|
-
- !ruby/object:Gem::Version
|
|
48
|
-
version: '1.1'
|
|
49
|
-
type: :runtime
|
|
50
|
-
prerelease: false
|
|
51
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
52
|
-
requirements:
|
|
53
|
-
- - ">"
|
|
54
|
-
- !ruby/object:Gem::Version
|
|
55
|
-
version: '1.1'
|
|
56
|
-
- !ruby/object:Gem::Dependency
|
|
57
|
-
name: aruba
|
|
58
|
-
requirement: !ruby/object:Gem::Requirement
|
|
59
|
-
requirements:
|
|
60
|
-
- - ">="
|
|
61
|
-
- !ruby/object:Gem::Version
|
|
62
|
-
version: '0'
|
|
63
|
-
type: :development
|
|
64
|
-
prerelease: false
|
|
65
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
66
|
-
requirements:
|
|
67
|
-
- - ">="
|
|
68
|
-
- !ruby/object:Gem::Version
|
|
69
|
-
version: '0'
|
|
70
|
-
- !ruby/object:Gem::Dependency
|
|
71
|
-
name: bundler
|
|
72
|
-
requirement: !ruby/object:Gem::Requirement
|
|
73
|
-
requirements:
|
|
74
|
-
- - ">="
|
|
75
|
-
- !ruby/object:Gem::Version
|
|
76
|
-
version: '0'
|
|
77
|
-
type: :development
|
|
78
|
-
prerelease: false
|
|
79
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
80
|
-
requirements:
|
|
81
|
-
- - ">="
|
|
82
|
-
- !ruby/object:Gem::Version
|
|
83
|
-
version: '0'
|
|
84
|
-
- !ruby/object:Gem::Dependency
|
|
85
|
-
name: byebug
|
|
86
|
-
requirement: !ruby/object:Gem::Requirement
|
|
87
|
-
requirements:
|
|
88
|
-
- - ">="
|
|
89
|
-
- !ruby/object:Gem::Version
|
|
90
|
-
version: '0'
|
|
91
|
-
type: :development
|
|
92
|
-
prerelease: false
|
|
93
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
94
|
-
requirements:
|
|
95
|
-
- - ">="
|
|
96
|
-
- !ruby/object:Gem::Version
|
|
97
|
-
version: '0'
|
|
98
|
-
- !ruby/object:Gem::Dependency
|
|
99
|
-
name: fuubar
|
|
100
|
-
requirement: !ruby/object:Gem::Requirement
|
|
101
|
-
requirements:
|
|
102
|
-
- - ">="
|
|
103
|
-
- !ruby/object:Gem::Version
|
|
104
|
-
version: '0'
|
|
105
|
-
type: :development
|
|
106
|
-
prerelease: false
|
|
107
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
108
|
-
requirements:
|
|
109
|
-
- - ">="
|
|
110
|
-
- !ruby/object:Gem::Version
|
|
111
|
-
version: '0'
|
|
112
|
-
- !ruby/object:Gem::Dependency
|
|
113
|
-
name: hirb
|
|
114
|
-
requirement: !ruby/object:Gem::Requirement
|
|
115
|
-
requirements:
|
|
116
|
-
- - ">="
|
|
117
|
-
- !ruby/object:Gem::Version
|
|
118
|
-
version: '0'
|
|
119
|
-
type: :development
|
|
120
|
-
prerelease: false
|
|
121
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
122
|
-
requirements:
|
|
123
|
-
- - ">="
|
|
124
|
-
- !ruby/object:Gem::Version
|
|
125
|
-
version: '0'
|
|
126
|
-
- !ruby/object:Gem::Dependency
|
|
127
|
-
name: pry
|
|
128
|
-
requirement: !ruby/object:Gem::Requirement
|
|
129
|
-
requirements:
|
|
130
|
-
- - ">="
|
|
131
|
-
- !ruby/object:Gem::Version
|
|
132
|
-
version: '0'
|
|
133
|
-
type: :development
|
|
134
|
-
prerelease: false
|
|
135
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
136
|
-
requirements:
|
|
137
|
-
- - ">="
|
|
138
|
-
- !ruby/object:Gem::Version
|
|
139
|
-
version: '0'
|
|
140
|
-
- !ruby/object:Gem::Dependency
|
|
141
|
-
name: rails
|
|
142
44
|
requirement: !ruby/object:Gem::Requirement
|
|
143
45
|
requirements:
|
|
144
46
|
- - ">="
|
|
145
47
|
- !ruby/object:Gem::Version
|
|
146
48
|
version: '0'
|
|
147
|
-
type: :
|
|
148
|
-
prerelease: false
|
|
149
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
150
|
-
requirements:
|
|
151
|
-
- - ">="
|
|
152
|
-
- !ruby/object:Gem::Version
|
|
153
|
-
version: '0'
|
|
154
|
-
- !ruby/object:Gem::Dependency
|
|
155
|
-
name: rake
|
|
156
|
-
requirement: !ruby/object:Gem::Requirement
|
|
157
|
-
requirements:
|
|
158
|
-
- - ">="
|
|
159
|
-
- !ruby/object:Gem::Version
|
|
160
|
-
version: '0'
|
|
161
|
-
type: :development
|
|
162
|
-
prerelease: false
|
|
163
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
164
|
-
requirements:
|
|
165
|
-
- - ">="
|
|
166
|
-
- !ruby/object:Gem::Version
|
|
167
|
-
version: '0'
|
|
168
|
-
- !ruby/object:Gem::Dependency
|
|
169
|
-
name: rspec
|
|
170
|
-
requirement: !ruby/object:Gem::Requirement
|
|
171
|
-
requirements:
|
|
172
|
-
- - ">="
|
|
173
|
-
- !ruby/object:Gem::Version
|
|
174
|
-
version: '0'
|
|
175
|
-
type: :development
|
|
176
|
-
prerelease: false
|
|
177
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
178
|
-
requirements:
|
|
179
|
-
- - ">="
|
|
180
|
-
- !ruby/object:Gem::Version
|
|
181
|
-
version: '0'
|
|
182
|
-
- !ruby/object:Gem::Dependency
|
|
183
|
-
name: simplecov
|
|
184
|
-
requirement: !ruby/object:Gem::Requirement
|
|
185
|
-
requirements:
|
|
186
|
-
- - ">="
|
|
187
|
-
- !ruby/object:Gem::Version
|
|
188
|
-
version: '0'
|
|
189
|
-
type: :development
|
|
49
|
+
type: :runtime
|
|
190
50
|
prerelease: false
|
|
191
51
|
version_requirements: !ruby/object:Gem::Requirement
|
|
192
52
|
requirements:
|
|
@@ -211,14 +71,11 @@ files:
|
|
|
211
71
|
- lib/chrono_model/adapter/ddl.rb
|
|
212
72
|
- lib/chrono_model/adapter/indexes.rb
|
|
213
73
|
- lib/chrono_model/adapter/migrations.rb
|
|
214
|
-
- lib/chrono_model/adapter/migrations_modules/legacy.rb
|
|
215
74
|
- lib/chrono_model/adapter/migrations_modules/stable.rb
|
|
216
|
-
- lib/chrono_model/adapter/tsrange.rb
|
|
217
75
|
- lib/chrono_model/adapter/upgrade.rb
|
|
218
76
|
- lib/chrono_model/chrono.rb
|
|
219
77
|
- lib/chrono_model/conversions.rb
|
|
220
78
|
- lib/chrono_model/db_console.rb
|
|
221
|
-
- lib/chrono_model/json.rb
|
|
222
79
|
- lib/chrono_model/patches.rb
|
|
223
80
|
- lib/chrono_model/patches/as_of_time_holder.rb
|
|
224
81
|
- lib/chrono_model/patches/as_of_time_relation.rb
|
|
@@ -252,14 +109,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
252
109
|
requirements:
|
|
253
110
|
- - ">="
|
|
254
111
|
- !ruby/object:Gem::Version
|
|
255
|
-
version:
|
|
112
|
+
version: '3.0'
|
|
256
113
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
257
114
|
requirements:
|
|
258
115
|
- - ">="
|
|
259
116
|
- !ruby/object:Gem::Version
|
|
260
117
|
version: '0'
|
|
261
118
|
requirements: []
|
|
262
|
-
rubygems_version: 3.5.
|
|
119
|
+
rubygems_version: 3.5.9
|
|
263
120
|
signing_key:
|
|
264
121
|
specification_version: 4
|
|
265
122
|
summary: Temporal extensions (SCD Type II) for Active Record
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module ChronoModel
|
|
4
|
-
class Adapter
|
|
5
|
-
module MigrationsModules
|
|
6
|
-
module Legacy
|
|
7
|
-
# If adding an index to a temporal table, add it to the one in the
|
|
8
|
-
# temporal schema and to the history one. If the `:unique` option is
|
|
9
|
-
# present, it is removed from the index created in the history table.
|
|
10
|
-
#
|
|
11
|
-
def add_index(table_name, column_name, options = {})
|
|
12
|
-
return super unless is_chrono?(table_name)
|
|
13
|
-
|
|
14
|
-
transaction do
|
|
15
|
-
on_temporal_schema { super }
|
|
16
|
-
|
|
17
|
-
# Uniqueness constraints do not make sense in the history table
|
|
18
|
-
options = options.dup.tap { |o| o.delete(:unique) } if options[:unique].present?
|
|
19
|
-
|
|
20
|
-
on_history_schema { super(table_name, column_name, options) }
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
# If removing an index from a temporal table, remove it both from the
|
|
25
|
-
# temporal and the history schemas.
|
|
26
|
-
#
|
|
27
|
-
def remove_index(table_name, options = {})
|
|
28
|
-
return super unless is_chrono?(table_name)
|
|
29
|
-
|
|
30
|
-
transaction do
|
|
31
|
-
on_temporal_schema { super }
|
|
32
|
-
|
|
33
|
-
on_history_schema { super }
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
ChronoModel::Adapter::Migrations.include ChronoModel::Adapter::MigrationsModules::Legacy
|
|
@@ -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
|
data/lib/chrono_model/json.rb
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module ChronoModel
|
|
4
|
-
module Json
|
|
5
|
-
extend self
|
|
6
|
-
|
|
7
|
-
def create
|
|
8
|
-
ActiveSupport::Deprecation.warn <<-MSG.squish
|
|
9
|
-
ChronoModel: JSON ops are deprecated. Please migrate to JSONB.
|
|
10
|
-
MSG
|
|
11
|
-
|
|
12
|
-
adapter.execute 'CREATE OR REPLACE LANGUAGE plpythonu'
|
|
13
|
-
adapter.execute File.read(sql('json_ops.sql'))
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def drop
|
|
17
|
-
adapter.execute File.read(sql('uninstall-json_ops.sql'))
|
|
18
|
-
adapter.execute 'DROP LANGUAGE IF EXISTS plpythonu'
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
private
|
|
22
|
-
|
|
23
|
-
def sql(file)
|
|
24
|
-
"#{File.dirname(__FILE__)}/../../sql/#{file}"
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def adapter
|
|
28
|
-
ActiveRecord::Base.connection
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|