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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 222db0e6673e7171d44dac2163b5048ebf6b459c18fa2a2609a3051fce8c6591
4
- data.tar.gz: 871fdf28717c85267e6b783f1f2d6ba3a24b1261e974e53a25de28d745bc8579
3
+ metadata.gz: 6465bfc1eca17fb499f1826a7b938969bc7306b388ac93bce746a09c75a2c1a4
4
+ data.tar.gz: 6958a3018c4a29dbd4b81a56c60bc02d4fc8696780b62fe1ec8778725152e12d
5
5
  SHA512:
6
- metadata.gz: 0bf942259052f134ea38de898307a65f20a6b448d8e8ff99a09ae06cd751f9834e1eb22abd4498c880005b8fc91f44ecebea4702be8c174570c50027dce13a75
7
- data.tar.gz: 2b894f82a0cb9687d59dca9c3427a854cf6dc02191a08df71a215dfc577a8d1dd58632d44eb179395f097aa7d2391a7c738968d0f4c68f16cc14fffd2d18e034
6
+ metadata.gz: 4f0cf88eb3e21ec2cee85ba744a426e5ab125a71c18e4c7cbe31b45cbb5198f8f8dcbd1e17b87766cf3b386ebaf789fcb9dfd5190bb7e47bf61c3d9824fda490
7
+ data.tar.gz: e852d433e7d5c28f69e50511d0f094ae16fcb0f28cfc825273485c607b6e5fb2adc6d9cc82f0ec78c680c6e07392f296bfa27afcfe54d05595c3ac04a1a9e2cb
data/README.md CHANGED
@@ -1,8 +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
- [![Code Climate][code-analysis-badge]][code-analysis]
5
- [![Test Coverage][test-coverage-badge]][test-coverage]
6
4
  [![Gem Version][gem-version-badge]][gem-version]
7
5
  [![Inlinedocs][docs-analysis-badge]][docs-analysis]
8
6
 
@@ -67,24 +65,19 @@ All timestamps are _forcibly_ stored in as UTC, bypassing the
67
65
  * PostgreSQL >= 9.4
68
66
  * The `btree_gist` PostgreSQL extension
69
67
 
70
- With Homebrew:
71
-
72
- brew install postgres
73
-
74
- With apt:
75
-
76
- apt-get install postgresql-11
77
-
78
68
  ## Installation
79
69
 
80
70
  Add this line to your application's Gemfile:
81
71
 
82
- gem 'chrono_model'
72
+ ```ruby
73
+ gem 'chrono_model'
74
+ ```
83
75
 
84
76
  And then execute:
85
77
 
86
- $ bundle
87
-
78
+ ```sh
79
+ $ bundle
80
+ ```
88
81
 
89
82
  ## Configuration
90
83
 
@@ -99,10 +92,83 @@ development:
99
92
  Configure Active Record in your `config/application.rb` to use the `:sql` schema
100
93
  format:
101
94
 
102
- ```rb
95
+ ```ruby
103
96
  config.active_record.schema_format = :sql
104
97
  ```
105
98
 
99
+ ## Database Permissions (PostgreSQL)
100
+
101
+ ChronoModel creates and manages data in the `temporal` and `history` schemas. Your application database user needs appropriate privileges on these schemas and their objects.
102
+
103
+ ### Required Privileges
104
+
105
+ Grant the following privileges to your application database user (replace `app_user` with your actual username):
106
+
107
+ ```sql
108
+ -- Schema access
109
+ GRANT USAGE ON SCHEMA temporal TO app_user;
110
+ GRANT USAGE ON SCHEMA history TO app_user;
111
+
112
+ -- Table privileges for existing objects
113
+ GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA temporal TO app_user;
114
+ GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA history TO app_user;
115
+
116
+ -- Sequence privileges for existing objects
117
+ GRANT USAGE, SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA temporal TO app_user;
118
+ GRANT USAGE, SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA history TO app_user;
119
+
120
+ -- Default privileges for future objects
121
+ ALTER DEFAULT PRIVILEGES IN SCHEMA temporal GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_user;
122
+ ALTER DEFAULT PRIVILEGES IN SCHEMA history GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_user;
123
+ ALTER DEFAULT PRIVILEGES IN SCHEMA temporal GRANT USAGE, SELECT, UPDATE ON SEQUENCES TO app_user;
124
+ ALTER DEFAULT PRIVILEGES IN SCHEMA history GRANT USAGE, SELECT, UPDATE ON SEQUENCES TO app_user;
125
+ ```
126
+
127
+ ### Quick Diagnostics
128
+
129
+ You can verify your privileges are correctly set up by running these queries as your application user:
130
+
131
+ ```sql
132
+ -- Check schema access
133
+ SELECT
134
+ schema_name,
135
+ has_schema_privilege(current_user, schema_name, 'USAGE') AS has_usage
136
+ FROM information_schema.schemata
137
+ WHERE schema_name IN ('temporal', 'history');
138
+
139
+ -- Check table privileges (run after creating temporal tables)
140
+ SELECT
141
+ schemaname,
142
+ tablename,
143
+ has_table_privilege(current_user, schemaname||'.'||tablename, 'SELECT') AS has_select,
144
+ has_table_privilege(current_user, schemaname||'.'||tablename, 'INSERT') AS has_insert,
145
+ has_table_privilege(current_user, schemaname||'.'||tablename, 'UPDATE') AS has_update,
146
+ has_table_privilege(current_user, schemaname||'.'||tablename, 'DELETE') AS has_delete
147
+ FROM pg_tables
148
+ WHERE schemaname IN ('temporal', 'history');
149
+
150
+ -- Check sequence privileges (run after creating temporal tables)
151
+ SELECT
152
+ schemaname,
153
+ sequencename,
154
+ has_sequence_privilege(current_user, schemaname||'.'||sequencename, 'USAGE') AS has_usage,
155
+ has_sequence_privilege(current_user, schemaname||'.'||sequencename, 'SELECT') AS has_select,
156
+ has_sequence_privilege(current_user, schemaname||'.'||sequencename, 'UPDATE') AS has_update
157
+ FROM pg_sequences
158
+ WHERE schemaname IN ('temporal', 'history');
159
+ ```
160
+
161
+ ### Troubleshooting
162
+
163
+ If you encounter these symptoms, check your database permissions:
164
+
165
+ - **`ActiveRecord::UnknownPrimaryKey`** errors on temporalized models
166
+ - **`Model.chrono?` returns `false`** for models that should be temporal
167
+ - **Unexpected primary key or temporalization issues** during schema operations
168
+ - **Permission denied errors** when ChronoModel tries to access temporal/history objects
169
+
170
+ These issues often indicate insufficient privileges on the `temporal` and `history` schemas. Run the diagnostic queries above to identify missing privileges, then apply the appropriate `GRANT` statements.
171
+
106
172
  ## Schema creation
107
173
 
108
174
  ChronoModel hooks all `ActiveRecord::Migration` methods to make them temporal
@@ -121,7 +187,7 @@ view and all the trigger machinery. Every other housekeeping of the temporal
121
187
  structure is handled behind the scenes by the other schema statements. E.g.:
122
188
 
123
189
  * `rename_table` - renames tables, views, sequences, indexes and triggers
124
- * `drop_table` - drops the temporal table and all dependant objects
190
+ * `drop_table` - drops the temporal table and all dependent objects
125
191
  * `add_column` - adds the column to the current table and updates triggers
126
192
  * `rename_column` - renames the current table column and updates the triggers
127
193
  * `remove_column` - removes the current table column and updates the triggers
@@ -151,8 +217,7 @@ the `:validity` option:
151
217
  change_table :your_table, temporal: true, copy_data: true, validity: '1977-01-01'
152
218
  ```
153
219
 
154
- Please note that `change_table` requires you to use *old_style* `up` and
155
- `down` migrations. It cannot work with Rails 3-style `change` migrations.
220
+ Please note that `change_table` requires you to use `up` and `down` migrations.
156
221
 
157
222
 
158
223
  ## Selective Journaling
@@ -192,10 +257,12 @@ occur (see https://github.com/ifad/chronomodel/issues/71).
192
257
  In such cases, ensure to add `no_journal: %w( your_counter_cache_column_name )`
193
258
  to your `create_table`. Example:
194
259
 
195
- create_table 'sections', temporal: true, no_journal: %w( articles_count ) do |t|
196
- t.string :name
197
- t.integer :articles_count, default: 0
198
- end
260
+ ```ruby
261
+ create_table 'sections', temporal: true, no_journal: %w[articles_count] do |t|
262
+ t.string :name
263
+ t.integer :articles_count, default: 0
264
+ end
265
+ ```
199
266
 
200
267
  ## Data querying
201
268
 
@@ -282,12 +349,12 @@ cannot be deleted.
282
349
 
283
350
  ChronoModel currently performs upgrades by dropping and re-creating the views
284
351
  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 dependant
352
+ these views, the upgrade cannot be performed automatically as the dependent
286
353
  objects must be dropped first.
287
354
 
288
355
  When booting, ChronoModel will issue a warning in your logs about the need of
289
356
  a structure upgrade. Structure usually changes across versions. In this case,
290
- you need to set up a rake task that drops your dependant objects, runs
357
+ you need to set up a rake task that drops your dependent objects, runs
291
358
  ChronoModel.upgrade! and then re-creates them.
292
359
 
293
360
  A migration system should be introduced, but it is seen as overkill for now,
@@ -304,7 +371,9 @@ You need to connect as a database superuser, because specs need to create the
304
371
 
305
372
  To run the full test suite, use
306
373
 
307
- rake
374
+ ```sh
375
+ $ rake
376
+ ```
308
377
 
309
378
  SQL queries are logged to `spec/debug.log`. If you want to see them in your
310
379
  output, set the `VERBOSE=true` environment variable.
@@ -313,7 +382,9 @@ Some tests check the nominal execution of rake tasks within a test Rails app,
313
382
  and those are quite time consuming. You can run the full ChronoModel tests
314
383
  only against ActiveRecord by using
315
384
 
316
- rspec spec/chrono_model
385
+ ```sh
386
+ $ rspec spec/chrono_model
387
+ ```
317
388
 
318
389
  Ensure to run the full test suite before pushing.
319
390
 
@@ -328,10 +399,6 @@ Ensure to run the full test suite before pushing.
328
399
  of an object at a specific point in time within the application could
329
400
  lead to inaccuracies.
330
401
 
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
402
  * The triggers and temporal indexes cannot be saved in schema.rb. The AR
336
403
  schema dumper is quite basic, and it isn't (currently) extensible.
337
404
  As we're using many database-specific features, Chronomodel forces the
@@ -350,9 +417,6 @@ Ensure to run the full test suite before pushing.
350
417
 
351
418
  * Foreign keys are not supported. [See issue #174][gh-issue-174]
352
419
 
353
- * There may be unexpected results when combining eager loading and joins.
354
- [See issue #186][gh-issue-186]
355
-
356
420
  * Global ID ignores historical objects. [See issue #192][gh-issue-192]
357
421
 
358
422
  * Different historical objects are considered the identical. [See issue
@@ -362,6 +426,12 @@ Ensure to run the full test suite before pushing.
362
426
  creates a new record for each modification. This will lead to increased
363
427
  storage requirements and bloated history
364
428
 
429
+ * `*_by_sql` query methods are not supported. [See issue #313][gh-issue-313]
430
+
431
+ * `self.table_name` must be set before `include ChronoModel::TimeMachine`.
432
+ [See issue #336][gh-issue-336]
433
+
434
+
365
435
  ## Contributing
366
436
 
367
437
  1. Fork it
@@ -386,14 +456,10 @@ This software is Made in Italy :it: :smile:.
386
456
 
387
457
  [build-status]: https://github.com/ifad/chronomodel/actions
388
458
  [build-status-badge]: https://github.com/ifad/chronomodel/actions/workflows/ruby.yml/badge.svg
389
- [code-analysis]: https://codeclimate.com/github/ifad/chronomodel/maintainability
390
- [code-analysis-badge]: https://api.codeclimate.com/v1/badges/cdee7327938dc2eaff99/maintainability
391
459
  [docs-analysis]: https://inch-ci.org/github/ifad/chronomodel
392
460
  [docs-analysis-badge]: https://inch-ci.org/github/ifad/chronomodel.svg?branch=master
393
461
  [gem-version]: https://rubygems.org/gems/chrono_model
394
462
  [gem-version-badge]: https://badge.fury.io/rb/chrono_model.svg
395
- [test-coverage]: https://codeclimate.com/github/ifad/chronomodel
396
- [test-coverage-badge]: https://codeclimate.com/github/ifad/chronomodel/badges/coverage.svg
397
463
 
398
464
  [delorean-image]: https://i.imgur.com/DD77F4s.jpg
399
465
 
@@ -412,7 +478,6 @@ This software is Made in Italy :it: :smile:.
412
478
  [pg-exclusion-constraints]: https://www.postgresql.org/docs/9.4/sql-createtable.html#SQL-CREATETABLE-EXCLUDE
413
479
  [pg-btree-gist]: https://www.postgresql.org/docs/9.4/btree-gist.html
414
480
  [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
481
  [pg-ctes]: https://www.postgresql.org/docs/9.4/queries-with.html
417
482
  [pg-cte-optimization-fence]: https://www.postgresql.org/message-id/201209191305.44674.db@kavod.com
418
483
  [pg-cte-opt-out-fence]: https://www.postgresql.org/message-id/CAHyXU0zpM5+Dsb_pKxDmm-ZoWUAt=SkHHaiK_DBqcmtxTas6Nw@mail.gmail.com
@@ -420,15 +485,13 @@ This software is Made in Italy :it: :smile:.
420
485
  [pg-json-func]: https://www.postgresql.org/docs/9.4/functions-json.html
421
486
  [pg-json-opclass]: https://github.com/ifad/chronomodel/blob/master/sql/json_ops.sql
422
487
 
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
488
  [cm-readme-sql]: https://github.com/ifad/chronomodel/blob/master/README.sql
427
489
  [cm-timemachine]: https://github.com/ifad/chronomodel/blob/master/lib/chrono_model/time_machine.rb
428
490
  [cm-cte-impl]: https://github.com/ifad/chronomodel/commit/18f4c4b
429
491
 
430
492
  [gh-pzac]: https://github.com/pzac
431
493
  [gh-issue-174]: https://github.com/ifad/chronomodel/issues/174
432
- [gh-issue-186]: https://github.com/ifad/chronomodel/issues/186
433
494
  [gh-issue-192]: https://github.com/ifad/chronomodel/issues/192
434
495
  [gh-issue-206]: https://github.com/ifad/chronomodel/issues/206
496
+ [gh-issue-313]: https://github.com/ifad/chronomodel/issues/313
497
+ [gh-issue-336]: https://github.com/ifad/chronomodel/issues/336
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'chrono_model'
3
+ require_relative '../../chrono_model'
4
4
 
5
5
  module ActiveRecord
6
6
  # TODO: Remove when dropping Rails < 7.2 compatibility
@@ -32,7 +32,7 @@ module ActiveRecord
32
32
  args = ['-c', '-f', target.to_s]
33
33
  args << chronomodel_configuration[:database]
34
34
 
35
- run_cmd 'pg_dump', args, 'dumping data'
35
+ run_cmd_with_compatibility('pg_dump', args, 'dumping data')
36
36
  end
37
37
 
38
38
  def data_load(source)
@@ -41,7 +41,7 @@ module ActiveRecord
41
41
  args = ['-f', source]
42
42
  args << chronomodel_configuration[:database]
43
43
 
44
- run_cmd 'psql', args, 'loading data'
44
+ run_cmd_with_compatibility('psql', args, 'loading data')
45
45
  end
46
46
 
47
47
  private
@@ -50,6 +50,19 @@ module ActiveRecord
50
50
  @chronomodel_configuration ||= @configuration_hash
51
51
  end
52
52
 
53
+ # TODO: replace `run_cmd_with_compatibility` with `run_cmd` and remove when dropping Rails < 8.1 support
54
+ # Compatibility method to handle Rails version differences in run_cmd signature
55
+ # Rails < 8.1: run_cmd(cmd, args, action)
56
+ # Rails >= 8.1: run_cmd(cmd, *args, **opts)
57
+ def run_cmd_with_compatibility(cmd, args, action_description)
58
+ # Check if run_cmd method accepts keyword arguments (new signature)
59
+ if method(:run_cmd).parameters.any? { |type, _name| type == :rest }
60
+ run_cmd(cmd, *args)
61
+ else
62
+ run_cmd(cmd, args, action_description)
63
+ end
64
+ end
65
+
53
66
  # If a schema search path is defined in the configuration file, it will
54
67
  # be used by the database tasks class to dump only the specified search
55
68
  # path. Here we add also ChronoModel's temporal and history schemas to
@@ -55,12 +55,12 @@ module ChronoModel
55
55
  parent = "#{TEMPORAL_SCHEMA}.#{table}"
56
56
  p_pkey = primary_key(parent)
57
57
 
58
- execute <<-SQL.squish
59
- CREATE TABLE #{table} (
60
- hid BIGSERIAL PRIMARY KEY,
61
- validity tsrange NOT NULL,
62
- recorded_at timestamp NOT NULL DEFAULT timezone('UTC', now())
63
- ) INHERITS ( #{parent} )
58
+ execute <<~SQL.squish
59
+ CREATE TABLE #{table} (
60
+ hid BIGSERIAL PRIMARY KEY,
61
+ validity tsrange NOT NULL,
62
+ recorded_at timestamp NOT NULL DEFAULT timezone('UTC', now())
63
+ ) INHERITS (#{parent})
64
64
  SQL
65
65
 
66
66
  add_history_validity_constraint(table, p_pkey)
@@ -85,11 +85,11 @@ module ChronoModel
85
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
- #{insert_sequence_sql(pk, current)} INTO #{current} ( #{pk}, #{fields} )
89
- VALUES ( NEW.#{pk}, #{values} );
88
+ #{insert_sequence_sql(pk, current)} INTO #{current} (#{pk}, #{fields})
89
+ VALUES (NEW.#{pk}, #{values});
90
90
 
91
- INSERT INTO #{history} ( #{pk}, #{fields}, validity )
92
- VALUES ( NEW.#{pk}, #{values}, tsrange(timezone('UTC', now()), NULL) );
91
+ INSERT INTO #{history} (#{pk}, #{fields}, validity)
92
+ VALUES (NEW.#{pk}, #{values}, tsrange(timezone('UTC', now()), NULL));
93
93
 
94
94
  RETURN NEW;
95
95
  END;
@@ -150,7 +150,7 @@ module ChronoModel
150
150
  _new := row(#{journal.map { |c| "NEW.#{c}" }.join(', ')});
151
151
 
152
152
  IF _old IS NOT DISTINCT FROM _new THEN
153
- UPDATE ONLY #{current} SET ( #{fields} ) = ( #{values} ) WHERE #{pk} = OLD.#{pk};
153
+ UPDATE ONLY #{current} SET (#{fields}) = (#{values}) WHERE #{pk} = OLD.#{pk};
154
154
  RETURN NEW;
155
155
  END IF;
156
156
 
@@ -160,16 +160,16 @@ module ChronoModel
160
160
  #{"SELECT hid INTO _hid FROM #{history} WHERE #{pk} = OLD.#{pk} AND lower(validity) = _now;" unless ENV['CHRONOMODEL_NO_SQUASH']}
161
161
 
162
162
  IF _hid IS NOT NULL THEN
163
- UPDATE #{history} SET ( #{fields} ) = ( #{values} ) WHERE hid = _hid;
163
+ UPDATE #{history} SET (#{fields}) = (#{values}) WHERE hid = _hid;
164
164
  ELSE
165
165
  UPDATE #{history} SET validity = tsrange(lower(validity), _now)
166
166
  WHERE #{pk} = OLD.#{pk} AND upper_inf(validity);
167
167
 
168
- INSERT INTO #{history} ( #{pk}, #{fields}, validity )
169
- VALUES ( OLD.#{pk}, #{values}, tsrange(_now, NULL) );
168
+ INSERT INTO #{history} (#{pk}, #{fields}, validity)
169
+ VALUES (OLD.#{pk}, #{values}, tsrange(_now, NULL));
170
170
  END IF;
171
171
 
172
- UPDATE ONLY #{current} SET ( #{fields} ) = ( #{values} ) WHERE #{pk} = OLD.#{pk};
172
+ UPDATE ONLY #{current} SET (#{fields}) = (#{values}) WHERE #{pk} = OLD.#{pk};
173
173
 
174
174
  RETURN NEW;
175
175
  END;
@@ -234,7 +234,6 @@ module ChronoModel
234
234
  INSERT
235
235
  SQL
236
236
  end
237
- # private
238
237
  end
239
238
  end
240
239
  end
@@ -24,15 +24,13 @@ module ChronoModel
24
24
  temporal_index_names(table, range, options)
25
25
 
26
26
  chrono_alter_index(table, options) do
27
- execute <<-SQL.squish
28
- CREATE INDEX #{range_idx} ON #{table} USING gist ( #{range} )
29
- SQL
27
+ execute "CREATE INDEX #{range_idx} ON #{table} USING gist (#{range})"
30
28
 
31
29
  # Indexes used for precise history filtering, sorting and, in history
32
30
  # tables, by UPDATE / DELETE triggers.
33
31
  #
34
- execute "CREATE INDEX #{lower_idx} ON #{table} ( lower(#{range}) )"
35
- execute "CREATE INDEX #{upper_idx} ON #{table} ( upper(#{range}) )"
32
+ execute "CREATE INDEX #{lower_idx} ON #{table} (lower(#{range}))"
33
+ execute "CREATE INDEX #{upper_idx} ON #{table} (upper(#{range}))"
36
34
  end
37
35
  end
38
36
 
@@ -53,9 +51,9 @@ module ChronoModel
53
51
  id = options[:id] || primary_key(table)
54
52
 
55
53
  chrono_alter_constraint(table, options) do
56
- execute <<-SQL.squish
54
+ execute <<~SQL.squish
57
55
  ALTER TABLE #{table} ADD CONSTRAINT #{name}
58
- EXCLUDE USING gist ( #{id} WITH =, #{range} WITH && )
56
+ EXCLUDE USING gist (#{id} WITH =, #{range} WITH &&)
59
57
  SQL
60
58
  end
61
59
  end
@@ -64,7 +62,7 @@ module ChronoModel
64
62
  name = timeline_consistency_constraint_name(table)
65
63
 
66
64
  chrono_alter_constraint(table, options) do
67
- execute <<-SQL.squish
65
+ execute <<~SQL.squish
68
66
  ALTER TABLE #{table} DROP CONSTRAINT #{name}
69
67
  SQL
70
68
  end
@@ -81,9 +79,9 @@ module ChronoModel
81
79
  def chrono_create_history_indexes_for(table, p_pkey)
82
80
  add_temporal_indexes table, :validity, on_current_schema: true
83
81
 
84
- execute "CREATE INDEX #{table}_inherit_pkey ON #{table} ( #{p_pkey} )"
85
- execute "CREATE INDEX #{table}_recorded_at ON #{table} ( recorded_at )"
86
- execute "CREATE INDEX #{table}_instance_history ON #{table} ( #{p_pkey}, recorded_at )"
82
+ execute "CREATE INDEX #{table}_inherit_pkey ON #{table} (#{p_pkey})"
83
+ execute "CREATE INDEX #{table}_recorded_at ON #{table} (recorded_at)"
84
+ execute "CREATE INDEX #{table}_instance_history ON #{table} (#{p_pkey}, recorded_at)"
87
85
  end
88
86
 
89
87
  # Rename indexes on history schema
@@ -144,10 +142,10 @@ module ChronoModel
144
142
  #
145
143
  columns = Array.wrap(index.columns).join(', ')
146
144
 
147
- execute %[
148
- CREATE INDEX #{index.name} ON #{table_name}
149
- USING #{index.using} ( #{columns} )
150
- ], 'Copy index from temporal to history'
145
+ execute <<~SQL.squish, 'Copy index from temporal to history'
146
+ CREATE INDEX #{index.name} ON #{table_name}
147
+ USING #{index.using} (#{columns})
148
+ SQL
151
149
  end
152
150
  end
153
151
  end
@@ -84,7 +84,7 @@ module ChronoModel
84
84
  end
85
85
 
86
86
  else
87
- if is_chrono?(table_name)
87
+ if is_chrono?(table_name) && options[:temporal] == false
88
88
  chrono_undo_temporal_table(table_name)
89
89
  end
90
90
 
@@ -214,14 +214,14 @@ module ChronoModel
214
214
  seq = on_history_schema { pk_and_sequence_for(table_name).last.to_s }
215
215
  from = options[:validity] || '0001-01-01 00:00:00'
216
216
 
217
- execute %[
218
- INSERT INTO #{HISTORY_SCHEMA}.#{table_name}
219
- SELECT *,
220
- nextval('#{seq}') AS hid,
221
- tsrange('#{from}', NULL) AS validity,
222
- timezone('UTC', now()) AS recorded_at
223
- FROM #{TEMPORAL_SCHEMA}.#{table_name}
224
- ]
217
+ execute <<~SQL.squish
218
+ INSERT INTO #{HISTORY_SCHEMA}.#{table_name}
219
+ SELECT *,
220
+ nextval('#{seq}') AS hid,
221
+ tsrange('#{from}', NULL) AS validity,
222
+ timezone('UTC', now()) AS recorded_at
223
+ FROM #{TEMPORAL_SCHEMA}.#{table_name}
224
+ SQL
225
225
  end
226
226
 
227
227
  # Removes temporal features from this table
@@ -252,8 +252,6 @@ module ChronoModel
252
252
  execute "ALTER SEQUENCE #{seq} RENAME TO #{new_seq}"
253
253
  execute "ALTER TABLE #{name} RENAME TO #{new_name}"
254
254
  end
255
-
256
- # private
257
255
  end
258
256
  end
259
257
  end
@@ -17,7 +17,7 @@ module ChronoModel
17
17
  # Uniqueness constraints do not make sense in the history table
18
18
  options = options.dup.tap { |o| o.delete(:unique) } if options[:unique].present?
19
19
 
20
- on_history_schema { super(table_name, column_name, **options) }
20
+ on_history_schema { super }
21
21
  end
22
22
  end
23
23
 
@@ -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 dependant database objects'
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}"
@@ -81,12 +81,12 @@ module ChronoModel
81
81
  p_pkey = primary_key(table_name)
82
82
 
83
83
  execute "ALTER TABLE #{history_table} ADD COLUMN validity tsrange;"
84
- execute <<-SQL.squish
85
- UPDATE #{history_table} SET validity = tsrange(valid_from,
86
- CASE WHEN extract(year from valid_to) = 9999 THEN NULL
87
- ELSE valid_to
88
- END
89
- );
84
+ execute <<~SQL.squish
85
+ UPDATE #{history_table} SET validity = tsrange(valid_from,
86
+ CASE WHEN extract(year from valid_to) = 9999 THEN NULL
87
+ ELSE valid_to
88
+ END
89
+ );
90
90
  SQL
91
91
 
92
92
  execute "DROP INDEX #{history_table}_temporal_on_valid_from;"
@@ -112,7 +112,6 @@ module ChronoModel
112
112
  on_history_schema { add_history_validity_constraint(table_name, p_pkey) }
113
113
  on_history_schema { chrono_create_history_indexes_for(table_name, p_pkey) }
114
114
  end
115
- # private
116
115
  end
117
116
  end
118
117
  end
@@ -2,13 +2,12 @@
2
2
 
3
3
  require 'active_record/connection_adapters/postgresql_adapter'
4
4
 
5
- require 'chrono_model/adapter/migrations'
6
- require 'chrono_model/adapter/migrations_modules/stable'
5
+ require_relative 'adapter/migrations'
6
+ require_relative 'adapter/migrations_modules/stable'
7
7
 
8
- require 'chrono_model/adapter/ddl'
9
- require 'chrono_model/adapter/indexes'
10
- require 'chrono_model/adapter/tsrange'
11
- require 'chrono_model/adapter/upgrade'
8
+ require_relative 'adapter/ddl'
9
+ require_relative 'adapter/indexes'
10
+ require_relative 'adapter/upgrade'
12
11
 
13
12
  module ChronoModel
14
13
  # This class implements all ActiveRecord::ConnectionAdapters::SchemaStatements
@@ -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
@@ -89,15 +87,10 @@ module ChronoModel
89
87
  # The default search path is included however, since the table
90
88
  # may reference types defined in other schemas, which result in their
91
89
  # names becoming schema qualified, which will cause type resolutions to fail.
92
- #
93
- # NOTE: This method is dynamically defined, see the source.
94
- #
95
- def column_definitions; end
96
-
97
- define_method(:column_definitions) do |table_name|
98
- return super(table_name) unless is_chrono?(table_name)
90
+ def column_definitions(table_name)
91
+ return super unless is_chrono?(table_name)
99
92
 
100
- on_schema("#{TEMPORAL_SCHEMA},#{schema_search_path}", recurse: :ignore) { super(table_name) }
93
+ on_schema("#{TEMPORAL_SCHEMA},#{schema_search_path}", recurse: :ignore) { super }
101
94
  end
102
95
 
103
96
  # Evaluates the given block in the temporal schema.
@@ -172,7 +165,7 @@ module ChronoModel
172
165
  def chrono_metadata_set(view_name, metadata)
173
166
  comment = MultiJson.dump(metadata)
174
167
 
175
- execute %( COMMENT ON VIEW #{view_name} IS #{quote(comment)} )
168
+ execute "COMMENT ON VIEW #{view_name} IS #{quote(comment)}"
176
169
  end
177
170
 
178
171
  def valid_table_definition_options
@@ -4,21 +4,6 @@ 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
21
-
22
7
  def time_to_utc_string(time)
23
8
  time.to_fs(:db) << '.' << format('%06d', time.usec)
24
9
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'chrono_model/patches/db_console'
3
+ require_relative 'patches/db_console'
4
4
 
5
5
  Rails::DBConsole.prepend ChronoModel::Patches::DBConsole::DbConfig
@@ -11,10 +11,6 @@ module ChronoModel
11
11
  # on the join model's (:through association) one.
12
12
  #
13
13
  module Association
14
- def skip_statement_cache?(*)
15
- super || _chrono_target?
16
- end
17
-
18
14
  # If the association class or the through association are ChronoModels,
19
15
  # then fetches the records from a virtual table using a subquery scope
20
16
  # to a specific timestamp.
@@ -36,6 +32,10 @@ module ChronoModel
36
32
 
37
33
  private
38
34
 
35
+ def skip_statement_cache?(*)
36
+ super || _chrono_target?
37
+ end
38
+
39
39
  def _chrono_record?
40
40
  owner.class.include?(ChronoModel::Patches::AsOfTimeHolder) && owner.as_of_time.present?
41
41
  end