activerecord 6.0.0.beta1 → 6.0.0.beta2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +99 -2
- data/lib/active_record.rb +7 -0
- data/lib/active_record/associations/association.rb +17 -0
- data/lib/active_record/associations/collection_association.rb +5 -6
- data/lib/active_record/associations/collection_proxy.rb +12 -41
- data/lib/active_record/associations/has_many_association.rb +1 -9
- data/lib/active_record/associations/join_dependency/join_association.rb +11 -6
- data/lib/active_record/associations/preloader/association.rb +3 -4
- data/lib/active_record/associations/preloader/through_association.rb +9 -20
- data/lib/active_record/callbacks.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +25 -12
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +17 -9
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +6 -1
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +5 -2
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +47 -33
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +16 -8
- data/lib/active_record/connection_adapters/abstract/transaction.rb +5 -2
- data/lib/active_record/connection_adapters/abstract_adapter.rb +6 -4
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +28 -65
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +59 -1
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +6 -3
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +98 -89
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -27
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +30 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +27 -1
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +8 -5
- data/lib/active_record/connection_handling.rb +9 -4
- data/lib/active_record/core.rb +13 -1
- data/lib/active_record/database_configurations.rb +30 -10
- data/lib/active_record/database_configurations/hash_config.rb +1 -1
- data/lib/active_record/database_configurations/url_config.rb +9 -4
- data/lib/active_record/errors.rb +17 -12
- data/lib/active_record/gem_version.rb +1 -1
- data/lib/active_record/inheritance.rb +1 -1
- data/lib/active_record/middleware/database_selector.rb +75 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +90 -0
- data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
- data/lib/active_record/migration.rb +1 -1
- data/lib/active_record/migration/compatibility.rb +62 -63
- data/lib/active_record/persistence.rb +6 -6
- data/lib/active_record/querying.rb +2 -3
- data/lib/active_record/railtie.rb +9 -0
- data/lib/active_record/railties/collection_cache_association_loading.rb +3 -3
- data/lib/active_record/reflection.rb +15 -29
- data/lib/active_record/relation.rb +86 -15
- data/lib/active_record/relation/calculations.rb +2 -4
- data/lib/active_record/relation/delegation.rb +1 -1
- data/lib/active_record/relation/finder_methods.rb +8 -4
- data/lib/active_record/relation/query_attribute.rb +5 -3
- data/lib/active_record/relation/query_methods.rb +28 -8
- data/lib/active_record/relation/spawn_methods.rb +1 -1
- data/lib/active_record/relation/where_clause.rb +1 -5
- data/lib/active_record/scoping.rb +6 -7
- data/lib/active_record/scoping/default.rb +1 -8
- data/lib/active_record/scoping/named.rb +9 -1
- data/lib/active_record/test_fixtures.rb +2 -2
- data/lib/active_record/timestamp.rb +9 -3
- data/lib/active_record/validations/uniqueness.rb +3 -1
- data/lib/arel.rb +7 -0
- data/lib/arel/nodes/and.rb +1 -1
- data/lib/arel/nodes/case.rb +1 -1
- metadata +11 -8
@@ -158,10 +158,6 @@ module ActiveRecord
|
|
158
158
|
end
|
159
159
|
|
160
160
|
def with_handler(handler_key, &blk) # :nodoc:
|
161
|
-
unless ActiveRecord::Base.connection_handlers.keys.include?(handler_key)
|
162
|
-
raise ArgumentError, "The #{handler_key} role does not exist. Add it by establishing a connection with `connects_to` or use an existing role (#{ActiveRecord::Base.connection_handlers.keys.join(", ")})."
|
163
|
-
end
|
164
|
-
|
165
161
|
handler = lookup_connection_handler(handler_key)
|
166
162
|
swap_connection_handler(handler, &blk)
|
167
163
|
end
|
@@ -180,6 +176,15 @@ module ActiveRecord
|
|
180
176
|
config_hash
|
181
177
|
end
|
182
178
|
|
179
|
+
# Clears the query cache for all connections associated with the current thread.
|
180
|
+
def clear_query_caches_for_current_thread
|
181
|
+
ActiveRecord::Base.connection_handlers.each_value do |handler|
|
182
|
+
handler.connection_pool_list.each do |pool|
|
183
|
+
pool.connection.clear_query_cache if pool.active_connection?
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
183
188
|
# Returns the connection currently associated with the class. This can
|
184
189
|
# also be used to "borrow" the connection to do database work unrelated
|
185
190
|
# to any of the specific Active Records.
|
data/lib/active_record/core.rb
CHANGED
@@ -101,6 +101,7 @@ module ActiveRecord
|
|
101
101
|
# environment where dumping schema is rarely needed.
|
102
102
|
mattr_accessor :dump_schema_after_migration, instance_writer: false, default: true
|
103
103
|
|
104
|
+
mattr_accessor :database_selector, instance_writer: false
|
104
105
|
##
|
105
106
|
# :singleton-method:
|
106
107
|
# Specifies which database schemas to dump when calling db:structure:dump.
|
@@ -124,6 +125,10 @@ module ActiveRecord
|
|
124
125
|
|
125
126
|
mattr_accessor :connection_handlers, instance_accessor: false, default: {}
|
126
127
|
|
128
|
+
mattr_accessor :writing_role, instance_accessor: false, default: :writing
|
129
|
+
|
130
|
+
mattr_accessor :reading_role, instance_accessor: false, default: :reading
|
131
|
+
|
127
132
|
class_attribute :default_connection_handler, instance_writer: false
|
128
133
|
|
129
134
|
self.filter_attributes = []
|
@@ -137,7 +142,6 @@ module ActiveRecord
|
|
137
142
|
end
|
138
143
|
|
139
144
|
self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new
|
140
|
-
self.connection_handlers = { writing: ActiveRecord::Base.default_connection_handler }
|
141
145
|
end
|
142
146
|
|
143
147
|
module ClassMethods
|
@@ -473,6 +477,14 @@ module ActiveRecord
|
|
473
477
|
end
|
474
478
|
end
|
475
479
|
|
480
|
+
def present? # :nodoc:
|
481
|
+
true
|
482
|
+
end
|
483
|
+
|
484
|
+
def blank? # :nodoc:
|
485
|
+
false
|
486
|
+
end
|
487
|
+
|
476
488
|
# Returns +true+ if the record is read only. Records loaded through joins with piggy-back
|
477
489
|
# attributes will be marked as read only since they cannot be saved.
|
478
490
|
def readonly?
|
@@ -25,9 +25,9 @@ module ActiveRecord
|
|
25
25
|
#
|
26
26
|
# Options:
|
27
27
|
#
|
28
|
-
# <tt>env_name:</tt> The environment name. Defaults to nil which will collect
|
28
|
+
# <tt>env_name:</tt> The environment name. Defaults to +nil+ which will collect
|
29
29
|
# configs for all environments.
|
30
|
-
# <tt>spec_name:</tt> The specification name (
|
30
|
+
# <tt>spec_name:</tt> The specification name (i.e. primary, animals, etc.). Defaults
|
31
31
|
# to +nil+.
|
32
32
|
# <tt>include_replicas:</tt> Determines whether to include replicas in
|
33
33
|
# the returned list. Most of the time we're only iterating over the write
|
@@ -102,6 +102,7 @@ module ActiveRecord
|
|
102
102
|
|
103
103
|
def build_configs(configs)
|
104
104
|
return configs.configurations if configs.is_a?(DatabaseConfigurations)
|
105
|
+
return configs if configs.is_a?(Array)
|
105
106
|
|
106
107
|
build_db_config = configs.each_pair.flat_map do |env_name, config|
|
107
108
|
walk_configs(env_name.to_s, "primary", config)
|
@@ -134,9 +135,11 @@ module ActiveRecord
|
|
134
135
|
end
|
135
136
|
|
136
137
|
def build_db_config_from_hash(env_name, spec_name, config)
|
137
|
-
if
|
138
|
+
if config.has_key?("url")
|
139
|
+
url = config["url"]
|
138
140
|
config_without_url = config.dup
|
139
141
|
config_without_url.delete "url"
|
142
|
+
|
140
143
|
ActiveRecord::DatabaseConfigurations::UrlConfig.new(env_name, spec_name, url, config_without_url)
|
141
144
|
elsif config["database"] || (config.size == 1 && config.values.all? { |v| v.is_a? String })
|
142
145
|
ActiveRecord::DatabaseConfigurations::HashConfig.new(env_name, spec_name, config)
|
@@ -155,7 +158,7 @@ module ActiveRecord
|
|
155
158
|
configs
|
156
159
|
else
|
157
160
|
configs.map do |config|
|
158
|
-
ActiveRecord::DatabaseConfigurations::UrlConfig.new(
|
161
|
+
ActiveRecord::DatabaseConfigurations::UrlConfig.new(config.env_name, config.spec_name, url, config.config)
|
159
162
|
end
|
160
163
|
end
|
161
164
|
else
|
@@ -164,21 +167,38 @@ module ActiveRecord
|
|
164
167
|
end
|
165
168
|
|
166
169
|
def method_missing(method, *args, &blk)
|
167
|
-
if Hash.method_defined?(method)
|
168
|
-
ActiveSupport::Deprecation.warn \
|
169
|
-
"Returning a hash from ActiveRecord::Base.configurations is deprecated. Therefore calling `#{method}` on the hash is also deprecated. Please switch to using the `configs_for` method instead to collect and iterate over database configurations."
|
170
|
-
end
|
171
|
-
|
172
170
|
case method
|
173
171
|
when :each, :first
|
172
|
+
throw_getter_deprecation(method)
|
174
173
|
configurations.send(method, *args, &blk)
|
175
174
|
when :fetch
|
175
|
+
throw_getter_deprecation(method)
|
176
176
|
configs_for(env_name: args.first)
|
177
177
|
when :values
|
178
|
+
throw_getter_deprecation(method)
|
178
179
|
configurations.map(&:config)
|
180
|
+
when :[]=
|
181
|
+
throw_setter_deprecation(method)
|
182
|
+
|
183
|
+
env_name = args[0]
|
184
|
+
config = args[1]
|
185
|
+
|
186
|
+
remaining_configs = configurations.reject { |db_config| db_config.env_name == env_name }
|
187
|
+
new_config = build_configs(env_name => config)
|
188
|
+
new_configs = remaining_configs + new_config
|
189
|
+
|
190
|
+
ActiveRecord::Base.configurations = new_configs
|
179
191
|
else
|
180
|
-
|
192
|
+
raise NotImplementedError, "`ActiveRecord::Base.configurations` in Rails 6 now returns an object instead of a hash. The `#{method}` method is not supported. Please use `configs_for` or consult the documentation for supported methods."
|
181
193
|
end
|
182
194
|
end
|
195
|
+
|
196
|
+
def throw_setter_deprecation(method)
|
197
|
+
ActiveSupport::Deprecation.warn("Setting `ActiveRecord::Base.configurations` with `#{method}` is deprecated. Use `ActiveRecord::Base.configurations=` directly to set the configurations instead.")
|
198
|
+
end
|
199
|
+
|
200
|
+
def throw_getter_deprecation(method)
|
201
|
+
ActiveSupport::Deprecation.warn("`ActiveRecord::Base.configurations` no longer returns a hash. Methods that act on the hash like `#{method}` are deprecated and will be removed in Rails 6.1. Use the `configs_for` method to collect and iterate over the database configurations.")
|
202
|
+
end
|
183
203
|
end
|
184
204
|
end
|
@@ -16,7 +16,7 @@ module ActiveRecord
|
|
16
16
|
#
|
17
17
|
# Options are:
|
18
18
|
#
|
19
|
-
# <tt>:env_name</tt> - The Rails environment,
|
19
|
+
# <tt>:env_name</tt> - The Rails environment, i.e. "development"
|
20
20
|
# <tt>:spec_name</tt> - The specification name. In a standard two-tier
|
21
21
|
# database configuration this will default to "primary". In a multiple
|
22
22
|
# database three-tier database configuration this corresponds to the name
|
@@ -56,12 +56,17 @@ module ActiveRecord
|
|
56
56
|
end
|
57
57
|
|
58
58
|
private
|
59
|
-
|
60
|
-
|
61
|
-
|
59
|
+
|
60
|
+
def build_url_hash(url)
|
61
|
+
if url.nil? || /^jdbc:/.match?(url)
|
62
|
+
{ "url" => url }
|
62
63
|
else
|
63
|
-
|
64
|
+
ActiveRecord::ConnectionAdapters::ConnectionSpecification::ConnectionUrlResolver.new(url).to_hash
|
64
65
|
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def build_config(original_config, url)
|
69
|
+
hash = build_url_hash(url)
|
65
70
|
|
66
71
|
if original_config[env_name]
|
67
72
|
original_config[env_name].merge(hash)
|
data/lib/active_record/errors.rb
CHANGED
@@ -126,16 +126,26 @@ module ActiveRecord
|
|
126
126
|
|
127
127
|
# Raised when a foreign key constraint cannot be added because the column type does not match the referenced column type.
|
128
128
|
class MismatchedForeignKey < StatementInvalid
|
129
|
-
def initialize(
|
130
|
-
|
129
|
+
def initialize(
|
130
|
+
message: nil,
|
131
|
+
sql: nil,
|
132
|
+
binds: nil,
|
133
|
+
table: nil,
|
134
|
+
foreign_key: nil,
|
135
|
+
target_table: nil,
|
136
|
+
primary_key: nil,
|
137
|
+
primary_key_column: nil
|
138
|
+
)
|
131
139
|
if table
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
140
|
+
type = primary_key_column.bigint? ? :bigint : primary_key_column.type
|
141
|
+
msg = <<~EOM.squish
|
142
|
+
Column `#{foreign_key}` on table `#{table}` does not match column `#{primary_key}` on `#{target_table}`,
|
143
|
+
which has type `#{primary_key_column.sql_type}`.
|
144
|
+
To resolve this issue, change the type of the `#{foreign_key}` column on `#{table}` to be :#{type}.
|
145
|
+
(For example `t.#{type} :#{foreign_key}`).
|
136
146
|
EOM
|
137
147
|
else
|
138
|
-
msg =
|
148
|
+
msg = <<~EOM.squish
|
139
149
|
There is a mismatch between the foreign key and primary key column types.
|
140
150
|
Verify that the foreign key column type and the primary key of the associated table match types.
|
141
151
|
EOM
|
@@ -145,11 +155,6 @@ module ActiveRecord
|
|
145
155
|
end
|
146
156
|
super(msg, sql: sql, binds: binds)
|
147
157
|
end
|
148
|
-
|
149
|
-
private
|
150
|
-
def column_type(table, column)
|
151
|
-
@adapter.columns(table).detect { |c| c.name == column }.sql_type
|
152
|
-
end
|
153
158
|
end
|
154
159
|
|
155
160
|
# Raised when a record cannot be inserted or updated because it would violate a not null constraint.
|
@@ -249,7 +249,7 @@ module ActiveRecord
|
|
249
249
|
sti_column = arel_attribute(inheritance_column, table)
|
250
250
|
sti_names = ([self] + descendants).map(&:sti_name)
|
251
251
|
|
252
|
-
|
252
|
+
predicate_builder.build(sti_column, sti_names)
|
253
253
|
end
|
254
254
|
|
255
255
|
# Detect the subclass from the inheritance column of attrs. If the inheritance column value
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_record/middleware/database_selector/resolver"
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module Middleware
|
7
|
+
# The DatabaseSelector Middleware provides a framework for automatically
|
8
|
+
# swapping from the primary to the replica database connection. Rails
|
9
|
+
# provides a basic framework to determine when to swap and allows for
|
10
|
+
# applications to write custom strategy classes to override the default
|
11
|
+
# behavior.
|
12
|
+
#
|
13
|
+
# The resolver class defines when the application should switch (i.e. read
|
14
|
+
# from the primary if a write occurred less than 2 seconds ago) and a
|
15
|
+
# resolver context class that sets a value that helps the resolver class
|
16
|
+
# decide when to switch.
|
17
|
+
#
|
18
|
+
# Rails default middleware uses the request's session to set a timestamp
|
19
|
+
# that informs the application when to read from a primary or read from a
|
20
|
+
# replica.
|
21
|
+
#
|
22
|
+
# To use the DatabaseSelector in your application with default settings add
|
23
|
+
# the following options to your environment config:
|
24
|
+
#
|
25
|
+
# config.active_record.database_selector = { delay: 2.seconds }
|
26
|
+
# config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
|
27
|
+
# config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
|
28
|
+
#
|
29
|
+
# New applications will include these lines commented out in the production.rb.
|
30
|
+
#
|
31
|
+
# The default behavior can be changed by setting the config options to a
|
32
|
+
# custom class:
|
33
|
+
#
|
34
|
+
# config.active_record.database_selector = { delay: 2.seconds }
|
35
|
+
# config.active_record.database_resolver = MyResolver
|
36
|
+
# config.active_record.database_resolver_context = MyResolver::MySession
|
37
|
+
class DatabaseSelector
|
38
|
+
def initialize(app, resolver_klass = Resolver, context_klass = Resolver::Session, options = {})
|
39
|
+
@app = app
|
40
|
+
@resolver_klass = resolver_klass
|
41
|
+
@context_klass = context_klass
|
42
|
+
@options = options
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_reader :resolver_klass, :context_klass, :options
|
46
|
+
|
47
|
+
# Middleware that determines which database connection to use in a multiple
|
48
|
+
# database application.
|
49
|
+
def call(env)
|
50
|
+
request = ActionDispatch::Request.new(env)
|
51
|
+
|
52
|
+
select_database(request) do
|
53
|
+
@app.call(env)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def select_database(request, &blk)
|
60
|
+
context = context_klass.call(request)
|
61
|
+
resolver = resolver_klass.call(context, options)
|
62
|
+
|
63
|
+
if reading_request?(request)
|
64
|
+
resolver.read(&blk)
|
65
|
+
else
|
66
|
+
resolver.write(&blk)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def reading_request?(request)
|
71
|
+
request.get? || request.head?
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_record/middleware/database_selector/resolver/session"
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module Middleware
|
7
|
+
class DatabaseSelector
|
8
|
+
# The Resolver class is used by the DatabaseSelector middleware to
|
9
|
+
# determine which database the request should use.
|
10
|
+
#
|
11
|
+
# To change the behavior of the Resolver class in your application,
|
12
|
+
# create a custom resolver class that inherits from
|
13
|
+
# DatabaseSelector::Resolver and implements the methods that need to
|
14
|
+
# be changed.
|
15
|
+
#
|
16
|
+
# By default the Resolver class will send read traffic to the replica
|
17
|
+
# if it's been 2 seconds since the last write.
|
18
|
+
class Resolver # :nodoc:
|
19
|
+
SEND_TO_REPLICA_DELAY = 2.seconds
|
20
|
+
|
21
|
+
def self.call(context, options = {})
|
22
|
+
new(context, options)
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(context, options = {})
|
26
|
+
@context = context
|
27
|
+
@options = options
|
28
|
+
@delay = @options && @options[:delay] ? @options[:delay] : SEND_TO_REPLICA_DELAY
|
29
|
+
@instrumenter = ActiveSupport::Notifications.instrumenter
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_reader :context, :delay, :instrumenter
|
33
|
+
|
34
|
+
def read(&blk)
|
35
|
+
if read_from_primary?
|
36
|
+
read_from_primary(&blk)
|
37
|
+
else
|
38
|
+
read_from_replica(&blk)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def write(&blk)
|
43
|
+
write_to_primary(&blk)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def read_from_primary(&blk)
|
49
|
+
ActiveRecord::Base.connection.while_preventing_writes do
|
50
|
+
ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role) do
|
51
|
+
instrumenter.instrument("database_selector.active_record.read_from_primary") do
|
52
|
+
yield
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def read_from_replica(&blk)
|
59
|
+
ActiveRecord::Base.connected_to(role: ActiveRecord::Base.reading_role) do
|
60
|
+
instrumenter.instrument("database_selector.active_record.read_from_replica") do
|
61
|
+
yield
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def write_to_primary(&blk)
|
67
|
+
ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role) do
|
68
|
+
instrumenter.instrument("database_selector.active_record.wrote_to_primary") do
|
69
|
+
yield
|
70
|
+
ensure
|
71
|
+
context.update_last_write_timestamp
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def read_from_primary?
|
77
|
+
!time_since_last_write_ok?
|
78
|
+
end
|
79
|
+
|
80
|
+
def send_to_replica_delay
|
81
|
+
delay
|
82
|
+
end
|
83
|
+
|
84
|
+
def time_since_last_write_ok?
|
85
|
+
Time.now - context.last_write_timestamp >= send_to_replica_delay
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Middleware
|
5
|
+
class DatabaseSelector
|
6
|
+
class Resolver
|
7
|
+
# The session class is used by the DatabaseSelector::Resolver to save
|
8
|
+
# timestamps of the last write in the session.
|
9
|
+
#
|
10
|
+
# The last_write is used to determine whether it's safe to read
|
11
|
+
# from the replica or the request needs to be sent to the primary.
|
12
|
+
class Session # :nodoc:
|
13
|
+
def self.call(request)
|
14
|
+
new(request.session)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Converts time to a timestamp that represents milliseconds since
|
18
|
+
# epoch.
|
19
|
+
def self.convert_time_to_timestamp(time)
|
20
|
+
time.to_i * 1000 + time.usec / 1000
|
21
|
+
end
|
22
|
+
|
23
|
+
# Converts milliseconds since epoch timestamp into a time object.
|
24
|
+
def self.convert_timestamp_to_time(timestamp)
|
25
|
+
timestamp ? Time.at(timestamp / 1000, (timestamp % 1000) * 1000) : Time.at(0)
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(session)
|
29
|
+
@session = session
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_reader :session
|
33
|
+
|
34
|
+
def last_write_timestamp
|
35
|
+
self.class.convert_timestamp_to_time(session[:last_write])
|
36
|
+
end
|
37
|
+
|
38
|
+
def update_last_write_timestamp
|
39
|
+
session[:last_write] = self.class.convert_time_to_timestamp(Time.now)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|