chrono_model 1.2.2 → 3.0.1

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.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +19 -20
  3. data/README.md +73 -62
  4. data/lib/active_record/connection_adapters/chronomodel_adapter.rb +14 -14
  5. data/lib/active_record/tasks/chronomodel_database_tasks.rb +40 -39
  6. data/lib/chrono_model/adapter/ddl.rb +168 -153
  7. data/lib/chrono_model/adapter/indexes.rb +99 -94
  8. data/lib/chrono_model/adapter/migrations.rb +81 -104
  9. data/lib/chrono_model/adapter/migrations_modules/stable.rb +41 -0
  10. data/lib/chrono_model/adapter/tsrange.rb +20 -5
  11. data/lib/chrono_model/adapter/upgrade.rb +89 -91
  12. data/lib/chrono_model/adapter.rb +59 -31
  13. data/lib/chrono_model/chrono.rb +17 -0
  14. data/lib/chrono_model/conversions.rb +14 -8
  15. data/lib/chrono_model/db_console.rb +5 -0
  16. data/lib/chrono_model/patches/as_of_time_holder.rb +2 -2
  17. data/lib/chrono_model/patches/as_of_time_relation.rb +3 -13
  18. data/lib/chrono_model/patches/association.rb +15 -12
  19. data/lib/chrono_model/patches/batches.rb +13 -0
  20. data/lib/chrono_model/patches/db_console.rb +20 -4
  21. data/lib/chrono_model/patches/join_node.rb +4 -4
  22. data/lib/chrono_model/patches/preloader.rb +41 -11
  23. data/lib/chrono_model/patches/relation.rb +51 -8
  24. data/lib/chrono_model/patches.rb +3 -1
  25. data/lib/chrono_model/railtie.rb +13 -27
  26. data/lib/chrono_model/time_gate.rb +3 -3
  27. data/lib/chrono_model/time_machine/history_model.rb +65 -31
  28. data/lib/chrono_model/time_machine/time_query.rb +65 -49
  29. data/lib/chrono_model/time_machine/timeline.rb +52 -28
  30. data/lib/chrono_model/time_machine.rb +57 -25
  31. data/lib/chrono_model/utilities.rb +3 -3
  32. data/lib/chrono_model/version.rb +3 -1
  33. data/lib/chrono_model.rb +31 -36
  34. metadata +24 -263
  35. data/.gitignore +0 -21
  36. data/.rspec +0 -2
  37. data/.travis.yml +0 -41
  38. data/Gemfile +0 -4
  39. data/README.sql +0 -161
  40. data/Rakefile +0 -25
  41. data/chrono_model.gemspec +0 -33
  42. data/gemfiles/rails_5.0.gemfile +0 -6
  43. data/gemfiles/rails_5.1.gemfile +0 -6
  44. data/gemfiles/rails_5.2.gemfile +0 -6
  45. data/lib/chrono_model/json.rb +0 -28
  46. data/spec/aruba/dbconsole_spec.rb +0 -25
  47. data/spec/aruba/fixtures/database_with_default_username_and_password.yml +0 -14
  48. data/spec/aruba/fixtures/database_without_username_and_password.yml +0 -11
  49. data/spec/aruba/fixtures/empty_structure.sql +0 -27
  50. data/spec/aruba/fixtures/migrations/56/20160812190335_create_impressions.rb +0 -10
  51. data/spec/aruba/fixtures/migrations/56/20171115195229_add_temporal_extension_to_impressions.rb +0 -10
  52. data/spec/aruba/fixtures/railsapp/config/application.rb +0 -17
  53. data/spec/aruba/fixtures/railsapp/config/boot.rb +0 -5
  54. data/spec/aruba/fixtures/railsapp/config/environments/development.rb +0 -38
  55. data/spec/aruba/migrations_spec.rb +0 -48
  56. data/spec/aruba/rake_task_spec.rb +0 -71
  57. data/spec/chrono_model/adapter/base_spec.rb +0 -157
  58. data/spec/chrono_model/adapter/ddl_spec.rb +0 -243
  59. data/spec/chrono_model/adapter/indexes_spec.rb +0 -72
  60. data/spec/chrono_model/adapter/migrations_spec.rb +0 -312
  61. data/spec/chrono_model/conversions_spec.rb +0 -43
  62. data/spec/chrono_model/history_models_spec.rb +0 -32
  63. data/spec/chrono_model/json_ops_spec.rb +0 -59
  64. data/spec/chrono_model/time_machine/as_of_spec.rb +0 -188
  65. data/spec/chrono_model/time_machine/changes_spec.rb +0 -50
  66. data/spec/chrono_model/time_machine/counter_cache_race_spec.rb +0 -46
  67. data/spec/chrono_model/time_machine/default_scope_spec.rb +0 -37
  68. data/spec/chrono_model/time_machine/history_spec.rb +0 -104
  69. data/spec/chrono_model/time_machine/keep_cool_spec.rb +0 -27
  70. data/spec/chrono_model/time_machine/manipulations_spec.rb +0 -84
  71. data/spec/chrono_model/time_machine/model_identification_spec.rb +0 -46
  72. data/spec/chrono_model/time_machine/sequence_spec.rb +0 -74
  73. data/spec/chrono_model/time_machine/sti_spec.rb +0 -100
  74. data/spec/chrono_model/time_machine/time_query_spec.rb +0 -261
  75. data/spec/chrono_model/time_machine/timeline_spec.rb +0 -63
  76. data/spec/chrono_model/time_machine/timestamps_spec.rb +0 -43
  77. data/spec/chrono_model/time_machine/transactions_spec.rb +0 -69
  78. data/spec/config.travis.yml +0 -5
  79. data/spec/config.yml.example +0 -9
  80. data/spec/spec_helper.rb +0 -33
  81. data/spec/support/adapter/helpers.rb +0 -53
  82. data/spec/support/adapter/structure.rb +0 -44
  83. data/spec/support/aruba.rb +0 -44
  84. data/spec/support/connection.rb +0 -70
  85. data/spec/support/matchers/base.rb +0 -56
  86. data/spec/support/matchers/column.rb +0 -99
  87. data/spec/support/matchers/function.rb +0 -79
  88. data/spec/support/matchers/index.rb +0 -69
  89. data/spec/support/matchers/schema.rb +0 -39
  90. data/spec/support/matchers/table.rb +0 -275
  91. data/spec/support/time_machine/helpers.rb +0 -47
  92. data/spec/support/time_machine/structure.rb +0 -111
  93. data/sql/json_ops.sql +0 -56
  94. data/sql/uninstall-json_ops.sql +0 -24
@@ -1,13 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_record/connection_adapters/postgresql_adapter'
2
4
 
3
5
  require 'chrono_model/adapter/migrations'
6
+ require 'chrono_model/adapter/migrations_modules/stable'
7
+
4
8
  require 'chrono_model/adapter/ddl'
5
9
  require 'chrono_model/adapter/indexes'
6
10
  require 'chrono_model/adapter/tsrange'
7
11
  require 'chrono_model/adapter/upgrade'
8
12
 
9
13
  module ChronoModel
10
-
11
14
  # This class implements all ActiveRecord::ConnectionAdapters::SchemaStatements
12
15
  # methods adding support for temporal extensions. It inherits from the Postgres
13
16
  # adapter for a clean override of its methods using super.
@@ -25,12 +28,28 @@ module ChronoModel
25
28
  # The schema holding historical data
26
29
  HISTORY_SCHEMA = 'history'
27
30
 
31
+ if ActiveRecord::VERSION::STRING >= '7.1'
32
+ def initialize(*)
33
+ super
34
+
35
+ connect!
36
+
37
+ unless chrono_supported?
38
+ raise ChronoModel::Error, 'Your database server is not supported by ChronoModel. ' \
39
+ 'Currently, only PostgreSQL >= 9.3 is supported.'
40
+ end
41
+
42
+ chrono_setup!
43
+ end
44
+ end
45
+
28
46
  # Returns true whether the connection adapter supports our
29
47
  # implementation of temporal tables. Currently, Chronomodel
30
- # is supported starting with PostgreSQL 9.3.
48
+ # is supported starting with PostgreSQL 9.3 (90300 in PostgreSQL's
49
+ # `PG_VERSION_NUM` numeric format).
31
50
  #
32
51
  def chrono_supported?
33
- postgresql_version >= 90300
52
+ postgresql_version >= 90300 # rubocop:disable Style/NumericLiterals
34
53
  end
35
54
 
36
55
  def chrono_setup!
@@ -53,13 +72,13 @@ module ChronoModel
53
72
  #
54
73
  # NOTE: These methods are dynamically defined, see the source.
55
74
  #
56
- def primary_key(table_name)
57
- end
75
+ def primary_key(table_name); end
58
76
 
59
- [:primary_key, :indexes, :default_sequence_name].each do |method|
77
+ %i[primary_key indexes default_sequence_name].each do |method|
60
78
  define_method(method) do |*args|
61
79
  table_name = args.first
62
80
  return super(*args) unless is_chrono?(table_name)
81
+
63
82
  on_schema(TEMPORAL_SCHEMA, recurse: :ignore) { super(*args) }
64
83
  end
65
84
  end
@@ -73,12 +92,12 @@ module ChronoModel
73
92
  #
74
93
  # NOTE: This method is dynamically defined, see the source.
75
94
  #
76
- def column_definitions
77
- end
95
+ def column_definitions; end
78
96
 
79
97
  define_method(:column_definitions) do |table_name|
80
98
  return super(table_name) unless is_chrono?(table_name)
81
- on_schema(TEMPORAL_SCHEMA + ',' + self.schema_search_path, recurse: :ignore) { super(table_name) }
99
+
100
+ on_schema("#{TEMPORAL_SCHEMA},#{schema_search_path}", recurse: :ignore) { super(table_name) }
82
101
  end
83
102
 
84
103
  # Evaluates the given block in the temporal schema.
@@ -101,16 +120,15 @@ module ChronoModel
101
120
  # See specs for examples and behaviour.
102
121
  #
103
122
  def on_schema(schema, recurse: :follow)
104
- old_path = self.schema_search_path
123
+ old_path = schema_search_path
105
124
 
106
125
  count_recursions do
107
- if recurse == :follow or Thread.current['recursions'] == 1
126
+ if (recurse == :follow) || (Thread.current['recursions'] == 1)
108
127
  self.schema_search_path = schema
109
128
  end
110
129
 
111
130
  yield
112
131
  end
113
-
114
132
  ensure
115
133
  # If the transaction is aborted, any execute() call will raise
116
134
  # "transaction is aborted errors" - thus calling the Adapter's
@@ -121,7 +139,7 @@ module ChronoModel
121
139
  # transaction ends.
122
140
  #
123
141
  transaction_aborted =
124
- @connection.transaction_status == PG::Connection::PQTRANS_INERROR
142
+ chrono_connection.transaction_status == PG::Connection::PQTRANS_INERROR
125
143
 
126
144
  if transaction_aborted && Thread.current['recursions'] == 1
127
145
  @schema_search_path = nil
@@ -143,7 +161,8 @@ module ChronoModel
143
161
  def chrono_metadata_for(view_name)
144
162
  comment = select_value(
145
163
  "SELECT obj_description(#{quote(view_name)}::regclass)",
146
- "ChronoModel metadata for #{view_name}") if data_source_exists?(view_name)
164
+ "ChronoModel metadata for #{view_name}"
165
+ ) if data_source_exists?(view_name)
147
166
 
148
167
  MultiJson.load(comment || '{}').with_indifferent_access
149
168
  end
@@ -153,29 +172,38 @@ module ChronoModel
153
172
  def chrono_metadata_set(view_name, metadata)
154
173
  comment = MultiJson.dump(metadata)
155
174
 
156
- execute %[ COMMENT ON VIEW #{view_name} IS #{quote(comment)} ]
175
+ execute %( COMMENT ON VIEW #{view_name} IS #{quote(comment)} )
176
+ end
177
+
178
+ def valid_table_definition_options
179
+ super + %i[temporal journal no_journal full_journal]
157
180
  end
158
181
 
159
182
  private
160
- # Counts the number of recursions in a thread local variable
161
- #
162
- def count_recursions # yield
163
- Thread.current['recursions'] ||= 0
164
- Thread.current['recursions'] += 1
165
183
 
166
- yield
184
+ # Rails 7.1 uses `@raw_connection`, older versions use `@connection`
185
+ #
186
+ def chrono_connection
187
+ @chrono_connection ||= @raw_connection || @connection
188
+ end
167
189
 
168
- ensure
169
- Thread.current['recursions'] -= 1
170
- end
190
+ # Counts the number of recursions in a thread local variable
191
+ #
192
+ def count_recursions # yield
193
+ Thread.current['recursions'] ||= 0
194
+ Thread.current['recursions'] += 1
171
195
 
172
- # Create the temporal and history schemas, unless they already exist
173
- #
174
- def chrono_ensure_schemas
175
- [TEMPORAL_SCHEMA, HISTORY_SCHEMA].each do |schema|
176
- execute "CREATE SCHEMA #{schema}" unless schema_exists?(schema)
177
- end
196
+ yield
197
+ ensure
198
+ Thread.current['recursions'] -= 1
199
+ end
200
+
201
+ # Create the temporal and history schemas, unless they already exist
202
+ #
203
+ def chrono_ensure_schemas
204
+ [TEMPORAL_SCHEMA, HISTORY_SCHEMA].each do |schema|
205
+ execute "CREATE SCHEMA #{schema}" unless schema_exists?(schema)
178
206
  end
207
+ end
179
208
  end
180
-
181
209
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ChronoModel
4
+ # A module to add to ActiveRecord::Base to check if they are backed by
5
+ # temporal tables.
6
+ module Chrono
7
+ # Checks whether this Active Record model is backed by a temporal table
8
+ #
9
+ # @return [Boolean] false if the connection does not respond to is_chrono?
10
+ # the result of connection.is_chrono?(table_name) otherwise
11
+ def chrono?
12
+ return false unless connection.respond_to? :is_chrono?
13
+
14
+ connection.is_chrono?(table_name)
15
+ end
16
+ end
17
+ end
@@ -1,20 +1,26 @@
1
- module ChronoModel
1
+ # frozen_string_literal: true
2
2
 
3
+ module ChronoModel
3
4
  module Conversions
4
- extend self
5
+ module_function
5
6
 
6
7
  ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(?:\.(\d+))?\z/
7
8
 
9
+ # rubocop:disable Style/PerlBackrefs
8
10
  def string_to_utc_time(string)
9
- if string =~ ISO_DATETIME
10
- usec = $7.nil? ? '000000' : $7.ljust(6, '0') # .1 is .100000, not .000001
11
- Time.utc $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, usec.to_i
12
- end
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
13
19
  end
20
+ # rubocop:enable Style/PerlBackrefs
14
21
 
15
22
  def time_to_utc_string(time)
16
- [time.to_s(:db), sprintf('%06d', time.usec)].join '.'
23
+ time.to_fs(:db) << '.' << format('%06d', time.usec)
17
24
  end
18
25
  end
19
-
20
26
  end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'chrono_model/patches/db_console'
4
+
5
+ Rails::DBConsole.prepend ChronoModel::Patches::DBConsole::DbConfig
@@ -1,6 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ChronoModel
2
4
  module Patches
3
-
4
5
  # Added to classes that need to carry the As-Of date around
5
6
  #
6
7
  module AsOfTimeHolder
@@ -18,6 +19,5 @@ module ChronoModel
18
19
  @_as_of_time
19
20
  end
20
21
  end
21
-
22
22
  end
23
23
  end
@@ -1,19 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ChronoModel
2
4
  module Patches
3
-
4
5
  # This class is a dummy relation whose scope is only to pass around the
5
6
  # as_of_time parameters across ActiveRecord call chains.
6
- #
7
- # With AR 5.2 a simple relation can be used, as the only required argument
8
- # is the model. 5.0 and 5.1 require more arguments, that are passed here.
9
- #
10
- class AsOfTimeRelation < ActiveRecord::Relation
11
- if ActiveRecord::VERSION::STRING.to_f < 5.2
12
- def initialize(klass, table: klass.arel_table, predicate_builder: klass.predicate_builder, values: {})
13
- super(klass, table, predicate_builder, values)
14
- end
15
- end
16
- end
17
-
7
+ class AsOfTimeRelation < ActiveRecord::Relation; end
18
8
  end
19
9
  end
@@ -1,6 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ChronoModel
2
4
  module Patches
3
-
4
5
  # Patches ActiveRecord::Associations::Association to add support for
5
6
  # temporal associations.
6
7
  #
@@ -30,23 +31,25 @@ module ChronoModel
30
31
 
31
32
  scope.as_of_time!(owner.as_of_time)
32
33
 
33
- return scope
34
+ scope
34
35
  end
35
36
 
36
37
  private
37
- def _chrono_record?
38
- owner.respond_to?(:as_of_time) && owner.as_of_time.present?
39
- end
40
38
 
41
- def _chrono_target?
42
- @_target_klass ||= reflection.options[:polymorphic] ?
43
- owner.public_send(reflection.foreign_type).constantize :
44
- reflection.klass
39
+ def _chrono_record?
40
+ owner.class.include?(ChronoModel::Patches::AsOfTimeHolder) && owner.as_of_time.present?
41
+ end
45
42
 
46
- @_target_klass.chrono?
47
- end
43
+ def _chrono_target?
44
+ @_target_klass ||=
45
+ if reflection.options[:polymorphic]
46
+ owner.public_send(reflection.foreign_type).constantize
47
+ else
48
+ reflection.klass
49
+ end
48
50
 
51
+ @_target_klass.chrono?
52
+ end
49
53
  end
50
-
51
54
  end
52
55
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ChronoModel
4
+ module Patches
5
+ module Batches
6
+ def in_batches(**)
7
+ return super unless try(:history?)
8
+
9
+ with_hid_pkey { super }
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,11 +1,27 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ChronoModel
2
4
  module Patches
3
-
4
5
  module DBConsole
5
- def config
6
- super.dup.tap {|config| config['adapter'] = 'postgresql/chronomodel' }
6
+ module Config
7
+ def config
8
+ super.dup.tap { |config| config['adapter'] = 'postgresql/chronomodel' }
9
+ end
7
10
  end
8
- end
9
11
 
12
+ module DbConfig
13
+ def db_config
14
+ return @patched_db_config if @patched_db_config
15
+
16
+ original_db_config = super
17
+ patched_db_configuration_hash = original_db_config.configuration_hash.dup.tap do |config|
18
+ config[:adapter] = 'postgresql/chronomodel'
19
+ end
20
+ original_db_config.instance_variable_set :@configuration_hash, patched_db_configuration_hash
21
+
22
+ @patched_db_config = original_db_config
23
+ end
24
+ end
25
+ end
10
26
  end
11
27
  end
@@ -1,6 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ChronoModel
2
4
  module Patches
3
-
4
5
  # This class supports the AR 5.0 code that expects to receive an
5
6
  # Arel::Table as the left join node. We need to replace the node
6
7
  # with a virtual table that fetches from the history at a given
@@ -21,12 +22,11 @@ module ChronoModel
21
22
 
22
23
  @as_of_time = as_of_time
23
24
 
24
- virtual_table = history_model.
25
- virtual_table_at(@as_of_time, table_name: @table_alias || @table_name)
25
+ virtual_table = history_model
26
+ .virtual_table_at(@as_of_time, table_name: @table_alias || @table_name)
26
27
 
27
28
  super(virtual_table)
28
29
  end
29
30
  end
30
-
31
31
  end
32
32
  end
@@ -1,18 +1,26 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ChronoModel
2
4
  module Patches
3
-
4
5
  # Patches ActiveRecord::Associations::Preloader to add support for
5
6
  # temporal associations. This is tying itself to Rails internals
6
7
  # and it is ugly :-(.
7
8
  #
8
9
  module Preloader
9
- attr_reader :options
10
+ attr_reader :chronomodel_options
10
11
 
11
12
  # We overwrite the initializer in order to pass the +as_of_time+
12
13
  # parameter above in the build_preloader
13
14
  #
14
- def initialize(options = {})
15
- @options = options.freeze
15
+ def initialize(**options)
16
+ @chronomodel_options = options.extract!(:as_of_time, :model)
17
+ options[:scope] = chronomodel_scope(options[:scope]) if options.key?(:scope)
18
+
19
+ if options.empty?
20
+ super()
21
+ else
22
+ super(**options)
23
+ end
16
24
  end
17
25
 
18
26
  # Patches the AR Preloader (lib/active_record/associations/preloader.rb)
@@ -37,14 +45,20 @@ module ChronoModel
37
45
  # so we use it directly.
38
46
  #
39
47
  def preload(records, associations, given_preload_scope = nil)
40
- if options[:as_of_time]
41
- preload_scope = given_preload_scope ||
42
- ChronoModel::Patches::AsOfTimeRelation.new(options[:model])
48
+ super(records, associations, chronomodel_scope(given_preload_scope))
49
+ end
50
+
51
+ private
43
52
 
44
- preload_scope.as_of_time!(options[:as_of_time])
53
+ def chronomodel_scope(given_preload_scope)
54
+ preload_scope = given_preload_scope
55
+
56
+ if chronomodel_options[:as_of_time]
57
+ preload_scope ||= ChronoModel::Patches::AsOfTimeRelation.new(chronomodel_options[:model])
58
+ preload_scope.as_of_time!(chronomodel_options[:as_of_time])
45
59
  end
46
60
 
47
- super records, associations, preload_scope
61
+ preload_scope
48
62
  end
49
63
 
50
64
  module Association
@@ -59,10 +73,26 @@ module ChronoModel
59
73
  scope = scope.as_of(preload_scope.as_of_time)
60
74
  end
61
75
 
62
- return scope
76
+ scope
63
77
  end
64
78
  end
65
- end
66
79
 
80
+ module ThroughAssociation
81
+ # Builds the preloader scope taking into account a potential
82
+ # +as_of_time+ passed down the call chain starting at the
83
+ # end user invocation.
84
+ #
85
+ def through_scope
86
+ scope = super
87
+ return unless scope # Rails 5.2 may not return a scope
88
+
89
+ if preload_scope.try(:as_of_time)
90
+ scope = scope.as_of(preload_scope.as_of_time)
91
+ end
92
+
93
+ scope
94
+ end
95
+ end
96
+ end
67
97
  end
68
98
  end
@@ -1,13 +1,32 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ChronoModel
2
4
  module Patches
3
-
4
5
  module Relation
5
6
  include ChronoModel::Patches::AsOfTimeHolder
6
7
 
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
17
+ end
18
+ end
19
+
20
+ def empty_scope?
21
+ return super unless @_as_of_time
22
+
23
+ @values == klass.unscoped.as_of(as_of_time).values
24
+ end
25
+
7
26
  def load
8
27
  return super unless @_as_of_time && !loaded?
9
28
 
10
- super.each {|record| record.as_of_time!(@_as_of_time) }
29
+ super.each { |record| record.as_of_time!(@_as_of_time) }
11
30
  end
12
31
 
13
32
  def merge(*)
@@ -20,11 +39,9 @@ module ChronoModel
20
39
  return super unless @_as_of_time
21
40
 
22
41
  super.tap do |arel|
23
-
24
42
  arel.join_sources.each do |join|
25
43
  chrono_join_history(join)
26
44
  end
27
-
28
45
  end
29
46
  end
30
47
 
@@ -37,11 +54,18 @@ module ChronoModel
37
54
  #
38
55
  return if join.left.respond_to?(:as_of_time)
39
56
 
40
- model = ChronoModel.history_models[join.left.table_name]
57
+ model =
58
+ if join.left.respond_to?(:table_name)
59
+ ChronoModel.history_models[join.left.table_name]
60
+ else
61
+ ChronoModel.history_models[join.left]
62
+ end
63
+
41
64
  return unless model
42
65
 
43
66
  join.left = ChronoModel::Patches::JoinNode.new(
44
- join.left, model.history, @_as_of_time)
67
+ join.left, model.history, @_as_of_time
68
+ )
45
69
  end
46
70
 
47
71
  # Build a preloader at the +as_of_time+ of this relation.
@@ -49,10 +73,29 @@ module ChronoModel
49
73
  #
50
74
  def build_preloader
51
75
  ActiveRecord::Associations::Preloader.new(
52
- model: self.model, as_of_time: as_of_time
76
+ model: model, as_of_time: as_of_time
53
77
  )
54
78
  end
55
- end
56
79
 
80
+ def find_nth(*)
81
+ return super unless try(:history?)
82
+
83
+ with_hid_pkey { super }
84
+ end
85
+
86
+ def last(*)
87
+ return super unless try(:history?)
88
+
89
+ with_hid_pkey { super }
90
+ end
91
+
92
+ private
93
+
94
+ def ordered_relation
95
+ return super unless try(:history?)
96
+
97
+ with_hid_pkey { super }
98
+ end
99
+ end
57
100
  end
58
101
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'chrono_model/patches/as_of_time_holder'
2
4
  require 'chrono_model/patches/as_of_time_relation'
3
5
 
@@ -5,4 +7,4 @@ require 'chrono_model/patches/join_node'
5
7
  require 'chrono_model/patches/relation'
6
8
  require 'chrono_model/patches/preloader'
7
9
  require 'chrono_model/patches/association'
8
- require 'chrono_model/patches/db_console'
10
+ require 'chrono_model/patches/batches'
@@ -1,45 +1,31 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_record/tasks/chronomodel_database_tasks'
2
4
 
3
5
  module ChronoModel
4
6
  class Railtie < ::Rails::Railtie
7
+ TASKS_CLASS = ActiveRecord::Tasks::ChronomodelDatabaseTasks
5
8
 
6
- rake_tasks do
7
- if Rails.application.config.active_record.schema_format != :sql
8
- raise 'In order to use ChronoModel, config.active_record.schema_format must be :sql!'
9
- end
10
-
11
- tasks_class = ActiveRecord::Tasks::ChronomodelDatabaseTasks
12
-
13
- # Register our database tasks under our adapter name
14
- #
15
- ActiveRecord::Tasks::DatabaseTasks.register_task(/chronomodel/, tasks_class)
9
+ # Register our database tasks under our adapter name
10
+ ActiveRecord::Tasks::DatabaseTasks.register_task(/chronomodel/, TASKS_CLASS.to_s)
16
11
 
17
- # Make schema:dump and schema:load invoke structure:dump and structure:load
18
- Rake::Task['db:schema:dump'].clear.enhance(['environment']) do
19
- Rake::Task['db:structure:dump'].invoke
20
- end
21
-
22
- Rake::Task['db:schema:load'].clear.enhance(['environment']) do
23
- Rake::Task['db:structure:load'].invoke
12
+ rake_tasks do
13
+ def task_config
14
+ ActiveRecord::Base.connection_db_config
24
15
  end
25
16
 
26
- desc "Dumps database into db/data.NOW.sql or file specified via DUMP="
17
+ desc 'Dumps database into db/data.NOW.sql or file specified via DUMP='
27
18
  task 'db:data:dump' => :environment do
28
- config = ActiveRecord::Tasks::DatabaseTasks.current_config
29
19
  target = ENV['DUMP'] || Rails.root.join('db', "data.#{Time.now.to_f}.sql")
30
-
31
- tasks_class.new(config).data_dump(target)
20
+ TASKS_CLASS.new(task_config).data_dump(target)
32
21
  end
33
22
 
34
- desc "Loads database dump from file specified via DUMP="
23
+ desc 'Loads database dump from file specified via DUMP='
35
24
  task 'db:data:load' => :environment do
36
- config = ActiveRecord::Tasks::DatabaseTasks.current_config
37
25
  source = ENV['DUMP'].presence or
38
- raise ArgumentError, "Invoke as rake db:data:load DUMP=/path/to/data.sql"
39
-
40
- tasks_class.new(config).data_load(source)
26
+ raise ArgumentError, 'Invoke as rake db:data:load DUMP=/path/to/data.sql'
27
+ TASKS_CLASS.new(task_config).data_load(source)
41
28
  end
42
29
  end
43
-
44
30
  end
45
31
  end
@@ -1,5 +1,6 @@
1
- module ChronoModel
1
+ # frozen_string_literal: true
2
2
 
3
+ module ChronoModel
3
4
  # Provides the TimeMachine API to non-temporal models that associate
4
5
  # temporal ones.
5
6
  #
@@ -17,12 +18,11 @@ module ChronoModel
17
18
  end
18
19
 
19
20
  def as_of(time)
20
- self.class.as_of(time).where(id: self.id).first!
21
+ self.class.as_of(time).where(id: id).first!
21
22
  end
22
23
 
23
24
  def timeline
24
25
  self.class.timeline(self)
25
26
  end
26
27
  end
27
-
28
28
  end