This reduces the possibility of generating index names that are too long
for certain databases (e.g. Postgres which has a 63 character limit) by
naming the index based on the reference name rather than the type and id
column names (the default behaviour of t.index).
This still allows the name to be explicitly specified by passing options.
`setup_shared_connection_pool` replaces reading connection pools with
writing ones, so that replica reads work as they would in production.
If a shard doesn't have a reading connection we should skip it, since
adding one could lead to code that only works in the test environment.
Sometimes you need to have a different default connection but aren't
calling the connection with a block. An example is booting a console in
`reading` mode. This adds the ability for a script to set a specific
connection on boot while preserving the behavior of `connected_to` for
application code.
This is a separate commit because I want it to be easy to revert if we
change our minds. After some discussion I think it is confusing that you
could swap shard but not role granularly in legacy mode. This change
forces users to always either have global swapping until moved off
legacy mode. This will prevent a situation where `AnimalsBase` can
change the shard granularly but the role globally.
This change allows for a connection to be swapped on role or shard for a
class. Previously calling `connected_to` would swap all the connections
to a particular role or shard. Granular connection swapping is useful
for swapping one connection to reading while leaving all other
connection classes on writing.
The public methods on connection handler have been updated to behave the
same as they did previously on the different handlers. The difference
however is instead of calling
`ActiveRecord::Base.connection_handlers[:reading].clear_all_connections!`
you now call
`ActiveRecord::Base.connection_handler.clear_all_connections!` which
will clear based on current role set by a `connected_to` block. Outside
the context of a `connected_to` block, `clear_all_connections!` can take
an optional parameter to clear specific connections by role.
The major changes in this PR are:
* We introduced a `legacy_connection_handling` configuration option that
is set to true by default. It will be set to `false` for all new
applications.
* In the new connection handling there will be one only connection
handler. Previously there was a connection handler for each role. Now
the role is stored in the `PoolManager`. In order to maintain backwards
compatibility we introduced a `LegacyPoolManager` to avoid duplicate
conditionals. See diagram in PR body for changes to connection
management.
* `connected_to` will now use a stacked concurrent map to keep track of
the connection for each class. For each opened block the `class`,
`role`, and `shard` will be added to the stack, when the block is exited
the `class`, `role`, `shard` array will be removed from the stack.
* With these changes `ActiveRecord::Base.connected_to` will remain
global. If called all connections in the block will use the `role` and
`shard` that was switched to. If called with a parent class like
`AnimalsRecord.connected_to` only models under `AnimalsRecord` will be
switched and everything else will remain the same.
Examples:
Given an application we have a `User` model that inherits from
`ApplicationRecord` and a `Dog` model that inherits from
`AnimalsRecord`. `AnimalsRecord` and `ApplicationRecord` have writing
and reading connections as well as shard `default`, `one`, and `two`.
```ruby
ActiveRecord::Base.connected_to(role: :reading) do
User.first # reads from default replica
Dog.first # reads from default replica
AnimalsRecord.connected_to(role: :writing, shard: :one) do
User.first # reads from default replica
Dog.first # reads from shard one primary
end
User.first # reads from default replica
Dog.first # reads from default replica
ApplicationRecord.connected_to(role: :writing, shard: :two) do
User.first # reads from shard two primary
Dog.first # reads from default replica
end
end
```
Things this PR does not solve:
* Currently there is no API for swapping more than one but not all
connections. Apps with many primaries may want to swap 3 but not all 10
connections. We plan to build an API for that in a followup PR.
* The middleware remains the same and is using the global switching
methods. Therefore at this time to use this new feature applications
must manually switch connections. We will also address this in a
followup PR.
* The `schema_cache` is currently on the `PoolConfig`. We plan on trying
to move this up to the `PoolManager` or elsewhere later on so each
`PoolConfig` doesn't need to hold a reference to the `schema_cache`.
Co-authored-by: John Crepezzi <john.crepezzi@gmail.com>
As well as allowing queries of the form
/* some comment */
SELECT ...
we should also allow queries of the form
-- some comment
SELECT ...
Additionally, I've added tests for both comment forms into the general
adapter test case, along with some additional checks to ensure that the
regexp does not return a false-positive match if the read query
signifier keyword is actually a part of the comment.
This expands on the change already implemented in
12b964c50f108f6668d1af2e22939059a217f28a.
fixes#40018
Prior to this commit, the following test would fail. `book`'s author was
actually being returned as `author_b`. (The same would happen for
`unscope`.)
def test
author_a = Author.create!
author_b = Author.create!
Book.create(author: author_a)
book = author_b.books.or(author_a.books).first
assert_equal book.author, author_a
end
Co-authored-by: Adam Hess <adamhess1991@gmail.com>
I've been reported an issue on enum from friends, they want a way to get
mapped value, but currently there is no reliable way to get that value.
If a record is loaded from database, `attribute_before_type_cast` works
for that. but the attribute is changed by user, the attribute method
won't work for that.
```ruby
book = Book.new(status: "published")
# returns "published", but what we really want is 2.
book.status_before_type_cast
```
So I propose to add `attribute_for_database` attribute method, it
consistently returns mapped value for enum.
Related #40415, #40208, #39778, #32810, #30497, #29570.
We had been reported lots of issues about `Relation#inspect` query.
I think that annotation for `Relation#inspect` query makes it easier for
people to understand why the query is being executed.
Numericality validations for aliased attributes are not able
to get the value of the attribute before type cast because
activerecord was trying to get the value of the attribute based
on attribute alias name and not the original attribute name.
Example of validation which would pass even if a invalid value
would be provided
class MyModel < ActiveRecord::Base
validates :aliased_balance, numericality: { greater_than_or_equal_to: 0 }
end
If we instantiate MyModel like bellow it will be valid because
when numericality validation runs it will not be able to get the
value before type cast, so it uses the type casted value
which will be `0.0` and the validation will match.
subject = MyModel.new(aliased_balance: "abcd")
subject.valid?
But if we declare MyModel like this
class MyModel < ActiveRecord::Base
validates :balance, numericality: { greater_than_or_equal_to: 0 }
end
and assign "abcd" value to `balance` when the validations
run the model will be invalid because activerecord will be able
to get the value before type cast.
With this change `read_attribute_before_type_cast` will be able to
get the value before type cast even when the attr_name is an
attribute_alias.
Serialized attributes stored in BLOB columns will be loaded
with the `ASCII-8BIT` (AKA BINARY) encoding.
So unless the serialized payload is pure ASCII, they need
to have the same internal encoding to be properly compared.
Since the serializer have no way to know how the string will
be stored, it's up to the column type to properly set the
encoding.
Random failures due to active connection checking within the
assert_no_changes block.
Failure:
MigrationTest#test_with_advisory_lock_closes_connection [/rails/activerecord/test/cases/migration_test.rb:947]:
--- expected
+++ actual
@@ -1 +1 @@
-["SELECT 1", "SELECT 1"]
+["SELECT 1", "SELECT 1", "SELECT 1"]
This commit scopes the query down further to ensure that the advisory
unlock query is not left in pg_stat_activity which is an indication that
the connection pool was not disconnected.