chrono_model 0.8.2 → 0.9.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/.travis.yml +11 -10
- data/Gemfile +0 -19
- data/README.md +55 -26
- data/chrono_model.gemspec +11 -3
- data/lib/chrono_model.rb +10 -0
- data/lib/chrono_model/adapter.rb +46 -26
- data/lib/chrono_model/patches.rb +69 -6
- data/lib/chrono_model/schema_format.rake +23 -7
- data/lib/chrono_model/schema_format.rb +2 -1
- data/lib/chrono_model/time_gate.rb +3 -11
- data/lib/chrono_model/time_machine.rb +53 -102
- data/lib/chrono_model/version.rb +1 -1
- data/spec/adapter_spec.rb +115 -111
- data/spec/json_ops_spec.rb +5 -5
- data/spec/spec_helper.rb +3 -2
- data/spec/support/matchers/base.rb +5 -8
- data/spec/support/matchers/column.rb +22 -9
- data/spec/support/matchers/index.rb +8 -4
- data/spec/support/matchers/schema.rb +5 -1
- data/spec/support/matchers/table.rb +55 -24
- data/spec/time_machine_spec.rb +195 -176
- data/spec/time_query_spec.rb +39 -39
- metadata +116 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 46c5c3d3c7a91b99e025dbf9dbd961962cc36914
|
4
|
+
data.tar.gz: 0d120a3f68b711f77031e3e3df5d203efd812a23
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 17f0b8f514b94579d9cd5f4b32fad9c5d8bed4d95282c361acf10f930d7f1fd64d503139b6eb6f5bde6e9c2c51134f3bcfafdb5034b0dde95af1814bdae563c4
|
7
|
+
data.tar.gz: 4f7d2ee5109d774d3217cec430bf74964b269e9fff6c8b790a6a328fd7bcb043c3e18c87defc3601a4c85e2c2a597bf314c4e0a0251054ca923d4250cc89b6c2
|
data/.travis.yml
CHANGED
@@ -1,20 +1,21 @@
|
|
1
1
|
rvm:
|
2
2
|
- 1.9.3
|
3
3
|
- 2.0.0
|
4
|
-
- 2.1.
|
4
|
+
- 2.1.7
|
5
|
+
- 2.2.3
|
5
6
|
|
6
|
-
|
7
|
-
- postgresql
|
7
|
+
sudo: false
|
8
8
|
|
9
|
-
|
9
|
+
language: ruby
|
10
|
+
cache: bundler
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
- sudo service postgresql start 9.3
|
12
|
+
addons:
|
13
|
+
postgresql: "9.4"
|
14
|
+
apt:
|
15
|
+
packages: postgresql-plpython-9.4
|
16
16
|
|
17
17
|
before_script:
|
18
18
|
- psql -c "CREATE DATABASE chronomodel;" -U postgres
|
19
19
|
|
20
|
-
script:
|
20
|
+
script:
|
21
|
+
- bundle exec rake TEST_CONFIG=./spec/config.travis.yml CODECLIMATE_REPO_TOKEN=dedfb7472ee410eec459bff3681d9a8fd8dd237e9bd7e8675a7c8eb7e253bba9
|
data/Gemfile
CHANGED
@@ -2,22 +2,3 @@ source 'https://rubygems.org'
|
|
2
2
|
|
3
3
|
# Specify your gem's dependencies in chrono_model.gemspec
|
4
4
|
gemspec
|
5
|
-
|
6
|
-
group :development do
|
7
|
-
gem 'pry'
|
8
|
-
gem 'hirb'
|
9
|
-
|
10
|
-
gem(
|
11
|
-
case RUBY_VERSION.to_f
|
12
|
-
when 1.8 then 'ruby-debug'
|
13
|
-
when 1.9 then 'debugger'
|
14
|
-
else 'byebug'
|
15
|
-
end
|
16
|
-
)
|
17
|
-
end
|
18
|
-
|
19
|
-
group :test do
|
20
|
-
gem 'rspec'
|
21
|
-
gem 'rake'
|
22
|
-
gem 'fuubar'
|
23
|
-
end
|
data/README.md
CHANGED
@@ -1,6 +1,17 @@
|
|
1
|
-
# Temporal database system on PostgreSQL using [updatable views][], [table inheritance][] and [INSTEAD OF triggers][].
|
1
|
+
# Temporal database system on PostgreSQL using [updatable views][], [table inheritance][] and [INSTEAD OF triggers][].
|
2
2
|
|
3
|
-
|
3
|
+
[![Build Status][build-status-badge]][build-status]
|
4
|
+
[![Dependency Status][deps-status-badge]][deps-status]
|
5
|
+
[![Code Climate][code-analysis-badge]][code-analysis]
|
6
|
+
[![Test Coverage][test-coverage-badge]][test-coverage]
|
7
|
+
[![Inlinedocs][docs-analysis-badge]][docs-analysis]
|
8
|
+
|
9
|
+
![{chronos - greek god of time}][chronos-image]
|
10
|
+
|
11
|
+
> Chronos, the greek god of time.
|
12
|
+
> Courtesy of [REBELLE SOCIETY][rebelle-society]
|
13
|
+
|
14
|
+
ChronoModel implements what Oracle sells as "Flashback Queries", with standard
|
4
15
|
SQL on free PostgreSQL. Academically speaking, ChronoModel implements a
|
5
16
|
[Type-2 Slowly-Changing Dimension][wp-scd-2] with [history tables][wp-scd-4].
|
6
17
|
|
@@ -52,10 +63,17 @@ All timestamps are _forcibly_ stored in as UTC, bypassing the
|
|
52
63
|
## Requirements
|
53
64
|
|
54
65
|
* Ruby >= 2.0 (1.9 is still supported, but support will be dropped soon).
|
55
|
-
* Active Record = 4.
|
66
|
+
* Active Record = 4.2
|
56
67
|
* PostgreSQL >= 9.3
|
57
|
-
* The `btree_gist` PostgreSQL
|
68
|
+
* The `btree_gist` and `plpython` PostgreSQL extensions:
|
69
|
+
|
70
|
+
With Homebrew:
|
58
71
|
|
72
|
+
brew install --with-python postgres
|
73
|
+
|
74
|
+
With Apt:
|
75
|
+
|
76
|
+
apt-get install postgresql-plpython
|
59
77
|
|
60
78
|
## Installation
|
61
79
|
|
@@ -236,7 +254,7 @@ History objects can be changed and `.save`d just like any other record.
|
|
236
254
|
|
237
255
|
## Running tests
|
238
256
|
|
239
|
-
You need a running PostgreSQL 9.3 instance. Create `spec/config.yml` with the
|
257
|
+
You need a running PostgreSQL >= 9.3 instance. Create `spec/config.yml` with the
|
240
258
|
connection authentication details (use `spec/config.yml.example` as template).
|
241
259
|
|
242
260
|
You need to connect as a database superuser, because specs need to create the
|
@@ -257,6 +275,10 @@ comparison of JSON objects [implemented in pl/python][json-opclass].
|
|
257
275
|
To load the opclass you can use the `ChronoModel::Json.create`
|
258
276
|
convenience method. If you don't use JSON don't bother doing this.
|
259
277
|
|
278
|
+
If you are on Postgres 9.4, you are strongly encouraged to use JSONB,
|
279
|
+
that has an equality operator built-in, it's faster and stricter, and
|
280
|
+
offers many more indexing abilities and better performance than JSON.
|
281
|
+
|
260
282
|
## Caveats
|
261
283
|
|
262
284
|
* Rails 4 support requires disabling tsrange parsing support, as it
|
@@ -264,7 +286,7 @@ convenience method. If you don't use JSON don't bother doing this.
|
|
264
286
|
as of now, mainly due to a [design clash with ruby][pg-tsrange-and-ruby].
|
265
287
|
|
266
288
|
* There is (yet) no upgrade path from [v0.5][chronomodel-0.5],
|
267
|
-
(PG 9.0-compatible, `box()` and hacks) to v0.6 and up (9.3-only, `tsrange`
|
289
|
+
(PG 9.0-compatible, `box()` and hacks) to v0.6 and up (>=9.3-only, `tsrange`
|
268
290
|
and _less_ hacks).
|
269
291
|
|
270
292
|
* The triggers and temporal indexes cannot be saved in schema.rb. The AR
|
@@ -296,40 +318,47 @@ convenience method. If you don't use JSON don't bother doing this.
|
|
296
318
|
|
297
319
|
|
298
320
|
[build-status]: https://travis-ci.org/ifad/chronomodel
|
299
|
-
[build-status-badge]: https://travis-ci.org/ifad/chronomodel.
|
321
|
+
[build-status-badge]: https://travis-ci.org/ifad/chronomodel.svg
|
300
322
|
[deps-status]: https://gemnasium.com/ifad/chronomodel
|
301
|
-
[deps-status-badge]: https://gemnasium.com/ifad/chronomodel.
|
323
|
+
[deps-status-badge]: https://gemnasium.com/ifad/chronomodel.svg
|
302
324
|
[code-analysis]: https://codeclimate.com/github/ifad/chronomodel
|
303
|
-
[code-analysis-badge]: https://codeclimate.com/github/ifad/chronomodel.
|
304
|
-
|
305
|
-
[
|
306
|
-
[
|
307
|
-
[
|
325
|
+
[code-analysis-badge]: https://codeclimate.com/github/ifad/chronomodel.svg
|
326
|
+
[docs-analysis]: http://inch-ci.org/github/ifad/chronomodel
|
327
|
+
[docs-analysis-badge]: http://inch-ci.org/github/ifad/chronomodel.svg?branch=master
|
328
|
+
[test-coverage]: https://codeclimate.com/github/ifad/chronomodel
|
329
|
+
[test-coverage-badge]: https://codeclimate.com/github/ifad/chronomodel/badges/coverage.svg
|
330
|
+
|
331
|
+
[chronos-image]: http://i.imgur.com/8NObYiZl.jpg
|
332
|
+
[rebelle-society]: http://www.rebellesociety.com/2012/10/11/the-writers-way-week-two-facing-procrastination/chronos_oeuvre_grand1/
|
333
|
+
|
334
|
+
[updatable views]: http://www.postgresql.org/docs/9.4/static/sql-createview.html#SQL-CREATEVIEW-UPDATABLE-VIEWS
|
335
|
+
[table inheritance]: http://www.postgresql.org/docs/9.4/static/ddl-inherit.html
|
336
|
+
[INSTEAD OF triggers]: http://www.postgresql.org/docs/9.4/static/sql-createtrigger.html
|
308
337
|
[wp-scd-2]: http://en.wikipedia.org/wiki/Slowly_changing_dimension#Type_2
|
309
338
|
[wp-scd-4]: http://en.wikipedia.org/wiki/Slowly_changing_dimension#Type_4
|
310
|
-
[triggers]: http://www.postgresql.org/docs/9.
|
311
|
-
[schema]: http://www.postgresql.org/docs/9.
|
312
|
-
[inherits]: http://www.postgresql.org/docs/9.
|
313
|
-
[`current_timestamp`]: http://www.postgresql.org/docs/9.
|
339
|
+
[triggers]: http://www.postgresql.org/docs/9.4/static/trigger-definition.html
|
340
|
+
[schema]: http://www.postgresql.org/docs/9.4/static/ddl-schemas.html
|
341
|
+
[inherits]: http://www.postgresql.org/docs/9.4/static/ddl-inherit.html
|
342
|
+
[`current_timestamp`]: http://www.postgresql.org/docs/9.4/interactive/functions-datetime.html#FUNCTIONS-DATETIME-TABLE
|
314
343
|
|
315
|
-
[Partitioning]: http://www.postgresql.org/docs/9.
|
316
|
-
[partitioning-excl-constraints]: http://www.postgresql.org/docs/9.
|
344
|
+
[Partitioning]: http://www.postgresql.org/docs/9.4/static/ddl-partitioning.html)
|
345
|
+
[partitioning-excl-constraints]: http://www.postgresql.org/docs/9.4/static/ddl-partitioning.html#DDL-PARTITIONING-CONSTRAINT-EXCLUSION
|
317
346
|
[README.sql]: https://github.com/ifad/chronomodel/blob/master/README.sql
|
318
|
-
[GiST indexes]: http://www.postgresql.org/docs/9.
|
319
|
-
[exclusion constraints]: http://www.postgresql.org/docs/9.
|
320
|
-
[btree_gist]: http://www.postgresql.org/docs/9.
|
321
|
-
[COMMENT]: http://www.postgresql.org/docs/9.
|
347
|
+
[GiST indexes]: http://www.postgresql.org/docs/9.4/static/gist.html
|
348
|
+
[exclusion constraints]: http://www.postgresql.org/docs/9.4/static/sql-createtable.html#SQL-CREATETABLE-EXCLUDE
|
349
|
+
[btree_gist]: http://www.postgresql.org/docs/9.4/static/btree-gist.html
|
350
|
+
[COMMENT]: http://www.postgresql.org/docs/9.4/static/sql-comment.html
|
322
351
|
[TimeMachine]: https://github.com/ifad/chronomodel/blob/master/lib/chrono_model/time_machine.rb
|
323
352
|
|
324
353
|
[r4-tsrange-broken]: https://github.com/rails/rails/pull/13793#issuecomment-34608093
|
325
354
|
[r4-tsrange-incomplete]: https://github.com/rails/rails/issues/14010)
|
326
355
|
[pg-tsrange-and-ruby]: https://bugs.ruby-lang.org/issues/6864
|
327
356
|
[chronomodel-0.5]: https://github.com/ifad/chronomodel/tree/c2daa0f
|
328
|
-
[Common Table Expressions]: http://www.postgresql.org/docs/9.
|
357
|
+
[Common Table Expressions]: http://www.postgresql.org/docs/9.4/static/queries-with.html
|
329
358
|
[cte-optimization-fence]: http://archives.postgresql.org/pgsql-hackers/2012-09/msg00700.php
|
330
359
|
[cte-opt-out-fence]: http://archives.postgresql.org/pgsql-hackers/2012-10/msg00024.php
|
331
360
|
[chronomodel-cte-impl]: https://github.com/ifad/chronomodel/commit/18f4c4b
|
332
361
|
|
333
|
-
[json-type]: http://www.postgresql.org/docs/9.
|
334
|
-
[json-func]: http://www.postgresql.org/docs/9.
|
362
|
+
[json-type]: http://www.postgresql.org/docs/9.4/static/datatype-json.html
|
363
|
+
[json-func]: http://www.postgresql.org/docs/9.4/static/functions-json.html
|
335
364
|
[json-opclass]: https://github.com/ifad/chronomodel/blob/master/sql/json_ops.sql
|
data/chrono_model.gemspec
CHANGED
@@ -4,9 +4,9 @@ require File.expand_path('../lib/chrono_model/version', __FILE__)
|
|
4
4
|
Gem::Specification.new do |gem|
|
5
5
|
gem.authors = ['Marcello Barnaba', 'Peter Joseph Brindisi']
|
6
6
|
gem.email = ['vjt@openssl.it', 'p.brindisi@ifad.org']
|
7
|
-
gem.description = %q{Give your models as-of date temporal extensions. Built entirely for PostgreSQL >= 9.
|
7
|
+
gem.description = %q{Give your models as-of date temporal extensions. Built entirely for PostgreSQL >= 9.3}
|
8
8
|
gem.summary = %q{Temporal extensions (SCD Type II) for Active Record}
|
9
|
-
gem.homepage = '
|
9
|
+
gem.homepage = 'https://github.com/ifad/chronomodel'
|
10
10
|
|
11
11
|
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
12
12
|
gem.files = `git ls-files`.split("\n")
|
@@ -15,7 +15,15 @@ Gem::Specification.new do |gem|
|
|
15
15
|
gem.require_paths = ["lib"]
|
16
16
|
gem.version = ChronoModel::VERSION
|
17
17
|
|
18
|
-
gem.add_dependency "activerecord", "~> 4.
|
18
|
+
gem.add_dependency "activerecord", "~> 4.2.0"
|
19
19
|
gem.add_dependency "pg"
|
20
20
|
gem.add_dependency "multi_json"
|
21
|
+
|
22
|
+
gem.add_development_dependency 'pry'
|
23
|
+
gem.add_development_dependency 'hirb'
|
24
|
+
gem.add_development_dependency RUBY_VERSION.to_f == 1.9 ? 'debugger' : 'byebug'
|
25
|
+
gem.add_development_dependency 'rspec'
|
26
|
+
gem.add_development_dependency 'rake'
|
27
|
+
gem.add_development_dependency 'fuubar'
|
28
|
+
gem.add_development_dependency 'codeclimate-test-reporter'
|
21
29
|
end
|
data/lib/chrono_model.rb
CHANGED
@@ -23,6 +23,10 @@ if RUBY_VERSION.to_i >= 2
|
|
23
23
|
ActiveRecord::Associations::Association.instance_eval do
|
24
24
|
prepend ChronoModel::Patches::Association
|
25
25
|
end
|
26
|
+
|
27
|
+
ActiveRecord::Relation.instance_eval do
|
28
|
+
prepend ChronoModel::Patches::Relation
|
29
|
+
end
|
26
30
|
else
|
27
31
|
ActiveSupport::Deprecation.warn 'Ruby 1.9 is deprecated. Please update your Ruby <3'
|
28
32
|
|
@@ -32,5 +36,11 @@ else
|
|
32
36
|
end
|
33
37
|
|
34
38
|
ActiveRecord::Associations::Association = ChronoModel::Patches::AssociationPatch
|
39
|
+
|
40
|
+
class ChronoModel::Patches::RelationPatch < ActiveRecord::Relation
|
41
|
+
include ChronoModel::Patches::Relation
|
42
|
+
end
|
43
|
+
|
44
|
+
ActiveRecord::Relation = ChronoModel::Patches::RelationPatch
|
35
45
|
end
|
36
46
|
end
|
data/lib/chrono_model/adapter.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'active_record'
|
2
2
|
require 'active_record/connection_adapters/postgresql_adapter'
|
3
3
|
|
4
|
+
require 'multi_json'
|
5
|
+
|
4
6
|
module ChronoModel
|
5
7
|
|
6
8
|
# This class implements all ActiveRecord::ConnectionAdapters::SchemaStatements
|
@@ -285,7 +287,19 @@ module ChronoModel
|
|
285
287
|
chrono_alter(table_name) { super }
|
286
288
|
end
|
287
289
|
|
288
|
-
# Runs column_definitions
|
290
|
+
# Runs column_definitions in the temporal schema, as the table there
|
291
|
+
# defined is the source for this information.
|
292
|
+
#
|
293
|
+
# The default search path is included however, since the table
|
294
|
+
# may reference types defined in other schemas, which result in their
|
295
|
+
# names becoming schema qualified, which will cause type resolutions to fail.
|
296
|
+
#
|
297
|
+
define_method(:column_definitions) do |table_name|
|
298
|
+
return super(table_name) unless is_chrono?(table_name)
|
299
|
+
on_schema(TEMPORAL_SCHEMA + ',' + self.schema_search_path, false) { super(table_name) }
|
300
|
+
end
|
301
|
+
|
302
|
+
# Runs primary_key, indexes and default_sequence_name in the temporal schema,
|
289
303
|
# as the table there defined is the source for this information.
|
290
304
|
#
|
291
305
|
# Moreover, the PostgreSQLAdapter +indexes+ method uses current_schema(),
|
@@ -294,10 +308,11 @@ module ChronoModel
|
|
294
308
|
# Schema nesting is disabled on these calls, make sure to fetch metadata
|
295
309
|
# from the first caller's selected schema and not from the current one.
|
296
310
|
#
|
297
|
-
[:
|
298
|
-
define_method(method) do |
|
299
|
-
|
300
|
-
|
311
|
+
[:primary_key, :indexes, :default_sequence_name].each do |method|
|
312
|
+
define_method(method) do |*args|
|
313
|
+
table_name = args.first
|
314
|
+
return super(*args) unless is_chrono?(table_name)
|
315
|
+
_on_temporal_schema(false) { super(*args) }
|
301
316
|
end
|
302
317
|
end
|
303
318
|
|
@@ -454,6 +469,12 @@ module ChronoModel
|
|
454
469
|
end
|
455
470
|
end
|
456
471
|
|
472
|
+
def chrono_setup!
|
473
|
+
chrono_create_schemas
|
474
|
+
|
475
|
+
chrono_upgrade_structure!
|
476
|
+
end
|
477
|
+
|
457
478
|
# HACK: Redefine tsrange parsing support, as it is broken currently.
|
458
479
|
#
|
459
480
|
# This self-made API is here because currently AR4 does not support
|
@@ -468,18 +489,13 @@ module ChronoModel
|
|
468
489
|
#
|
469
490
|
# so, for now, we are implementing our own.
|
470
491
|
#
|
471
|
-
class TSRange < OID::
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
#exclude_start: (value[0] == '('),
|
478
|
-
#exclude_end: (value[-1] == ')')
|
479
|
-
}
|
480
|
-
end
|
492
|
+
class TSRange < ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Range
|
493
|
+
OID = 3908
|
494
|
+
|
495
|
+
def cast_value(value)
|
496
|
+
return if value == 'empty'
|
497
|
+
return value if value.is_a?(::Array)
|
481
498
|
|
482
|
-
def type_cast(value)
|
483
499
|
extracted = extract_bounds(value)
|
484
500
|
|
485
501
|
from = Conversions.string_to_utc_time extracted[:from]
|
@@ -487,13 +503,23 @@ module ChronoModel
|
|
487
503
|
|
488
504
|
[from, to]
|
489
505
|
end
|
506
|
+
|
507
|
+
def extract_bounds(value)
|
508
|
+
from, to = value[1..-2].split(',')
|
509
|
+
{
|
510
|
+
from: (value[1] == ',' || from == '-infinity') ? nil : from[1..-2],
|
511
|
+
to: (value[-2] == ',' || to == 'infinity') ? nil : to[1..-2],
|
512
|
+
}
|
513
|
+
end
|
490
514
|
end
|
491
515
|
|
492
|
-
def
|
493
|
-
|
494
|
-
|
516
|
+
def initialize_type_map(type_map)
|
517
|
+
super.tap do
|
518
|
+
ar_type = type_map.fetch(TSRange::OID)
|
519
|
+
cm_type = TSRange.new(ar_type.subtype, ar_type.type)
|
495
520
|
|
496
|
-
|
521
|
+
type_map.register_type TSRange::OID, cm_type
|
522
|
+
end
|
497
523
|
end
|
498
524
|
|
499
525
|
# Copy the indexes from the temporal table to the history table if the indexes
|
@@ -529,12 +555,6 @@ module ChronoModel
|
|
529
555
|
end
|
530
556
|
end
|
531
557
|
|
532
|
-
# Adds the above TSRange class to the PG Adapter OID::TYPE_MAP
|
533
|
-
#
|
534
|
-
def chrono_setup_type_map
|
535
|
-
OID::TYPE_MAP[3908] = TSRange.new
|
536
|
-
end
|
537
|
-
|
538
558
|
# Upgrades existing structure for each table, if required.
|
539
559
|
# TODO: allow upgrades from pre-0.6 structure with box() and stuff.
|
540
560
|
#
|
data/lib/chrono_model/patches.rb
CHANGED
@@ -3,6 +3,60 @@ require 'active_record'
|
|
3
3
|
module ChronoModel
|
4
4
|
module Patches
|
5
5
|
|
6
|
+
module AsOfTimeHolder
|
7
|
+
# Sets the virtual 'as_of_time' attribute to the given time, converting to UTC.
|
8
|
+
#
|
9
|
+
# See ChronoModel::Patches::AsOfTimeHolder
|
10
|
+
#
|
11
|
+
def as_of_time!(time)
|
12
|
+
@_as_of_time = time.utc
|
13
|
+
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
# Reads the virtual 'as_of_time' attribute
|
18
|
+
#
|
19
|
+
# See ChronoModel::Patches::AsOfTimeHolder
|
20
|
+
#
|
21
|
+
def as_of_time
|
22
|
+
@_as_of_time
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module Relation
|
27
|
+
include AsOfTimeHolder
|
28
|
+
|
29
|
+
def load
|
30
|
+
return super unless @_as_of_time && !loaded?
|
31
|
+
|
32
|
+
super.each {|record| record.as_of_time!(@_as_of_time) }
|
33
|
+
end
|
34
|
+
|
35
|
+
def merge(*)
|
36
|
+
return super unless @_as_of_time
|
37
|
+
|
38
|
+
super.as_of_time!(@_as_of_time)
|
39
|
+
end
|
40
|
+
|
41
|
+
def build_arel
|
42
|
+
return super unless @_as_of_time
|
43
|
+
|
44
|
+
super.tap do |arel|
|
45
|
+
|
46
|
+
arel.join_sources.each do |join|
|
47
|
+
model = TimeMachine.chrono_models[join.left.table_name]
|
48
|
+
next unless model
|
49
|
+
|
50
|
+
join.left = Arel::Nodes::SqlLiteral.new(
|
51
|
+
model.history.virtual_table_at(@_as_of_time,
|
52
|
+
join.left.table_alias || join.left.table_name)
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
6
60
|
# Patches ActiveRecord::Associations::Association to add support for
|
7
61
|
# temporal associations.
|
8
62
|
#
|
@@ -12,6 +66,9 @@ module ChronoModel
|
|
12
66
|
# on the join model's (:through association) one.
|
13
67
|
#
|
14
68
|
module Association
|
69
|
+
def skip_statement_cache?
|
70
|
+
super || _chrono_target?
|
71
|
+
end
|
15
72
|
|
16
73
|
# If the association class or the through association are ChronoModels,
|
17
74
|
# then fetches the records from a virtual table using a subquery scope
|
@@ -20,11 +77,7 @@ module ChronoModel
|
|
20
77
|
scope = super
|
21
78
|
return scope unless _chrono_record?
|
22
79
|
|
23
|
-
|
24
|
-
owner.public_send(reflection.foreign_type).constantize :
|
25
|
-
reflection.klass
|
26
|
-
|
27
|
-
if klass.chrono?
|
80
|
+
if _chrono_target?
|
28
81
|
# For standard associations, replace the table name with the virtual
|
29
82
|
# as-of table name at the owner's as-of-time
|
30
83
|
#
|
@@ -54,9 +107,10 @@ module ChronoModel
|
|
54
107
|
join.left.table_alias = table_alias
|
55
108
|
end
|
56
109
|
end
|
57
|
-
|
58
110
|
end
|
59
111
|
|
112
|
+
scope.as_of_time!(owner.as_of_time)
|
113
|
+
|
60
114
|
return scope
|
61
115
|
end
|
62
116
|
|
@@ -64,6 +118,15 @@ module ChronoModel
|
|
64
118
|
def _chrono_record?
|
65
119
|
owner.respond_to?(:as_of_time) && owner.as_of_time.present?
|
66
120
|
end
|
121
|
+
|
122
|
+
def _chrono_target?
|
123
|
+
@_target_klass ||= reflection.options[:polymorphic] ?
|
124
|
+
owner.public_send(reflection.foreign_type).constantize :
|
125
|
+
reflection.klass
|
126
|
+
|
127
|
+
@_target_klass.chrono?
|
128
|
+
end
|
129
|
+
|
67
130
|
end
|
68
131
|
|
69
132
|
end
|