* Revert "Switch back to DelegateClass(ActiveModel::Type::Value)"
This reverts commit 91745df899.
* Revert "Update docs and changelog"
This reverts commit b234a94e56.
* Revert "Nest encrypted attribute types within serialized types"
This reverts commit 6a27e7b2f9.
* Revert "Encryption casting with `encrypts` before `serialize`"
This reverts commit 4dd2b22efe.
Co-authored-by: Rafael Mendonça França <rafael@rubyonrails.org>
The CLI tool used as a database interface is now specified and
customisable via ActiveRecord.database_cli.
This specifies the current defaults but allows them to be overridden by
users. It continues to accept array values to allow fallback options.
* Encryption casting with `encrypts` before `serialize`
A demonstration of how we can simplify encryption serialization and
deserialization if we call `encrypts` before `serialize`.
Calling cast_type.deserialize before decrypting fixes the issue where
binary data cannot be decrypted on PostgreSQL.
We would need to update the docs to recommend that `encrypts` is called
before `serialize` to take advantage of this and possibly raise an error
if they are called the other way round.
* Nest encrypted attribute types within serialized types
Make the order in which `serializes :foo` and `encrypts :foo` are called
irrelevant by always nesting the encrypted attribute type inside the
serialized type.
This required switching from `DelegateClass` to `SimpleDelegator` so
that object we are delegating to can be replaced.
To ensure that the serialized type survives YAML serialization (there's
a test for this), we need to implement init_with to call __setobj__.
* Update docs and changelog
* Switch back to DelegateClass(ActiveModel::Type::Value)
Thanks to @rafaelfranca for the suggestion.
Prior to this commit, delegated types were unable to infer their inverse
associations, so saving records built with the generated
`#build_ASSOCIATION` methods weren't writing to all the necessary
places. For example:
```ruby
entry = Entry.create! entryable: Message.new(subject: "Hello world!")
entry.message.subject # => "Hello world!"
entry.build_entryable(subject: "Goodbye world!").save!
entry.reload.message.subject # => "Hello world!"
```
The fact that the `Entry` test model declared a `delegated_type
:entryable` definition with `types: %w[ Message Comment ]` was never
reciprocated in the appropriate models.
In order to pass the tests, this commit needed to define the
corresponding `has_one :entry` associations. To do so, introduce the
`Entryable` concern in the same style as the one mentioned in the
documentation. The same extraction is made for a `UuidEntryable` concern
mixed into `UuidMessage` and `UuidComment`.
Unfortunately, defining `delegated_type :thing, types: %w[ Post ]` was
more tricky to fix. The `Post` test model is widely used, so defining a
`has_one` had farther-reaching effects than intended. To resolve that
issue, this commit redefines `:thing` to use `types: %w[ Essay ]`, which
has much fewer unintended side effects.
This allows ActiveRecord::Point to be cast or serialized from a hash
with :x and :y keys of numeric values, mirroring the functionality
of existing casts for string and array values. Both string and symbol
keys are supported.
Note that the PostgreSQL DROP EXTENSION statement does not accept a
schema name (see https://www.postgresql.org/docs/current/sql-dropextension.html).
Since we allow `enable_extension` to be called with a schema-qualified
name, this commit allows `disable_extension` to be similarly called with
a schema-qualified name, but the schema name is chopped off in the
generated SQL statement.
Transactions run against the SQLite3 adapter default to IMMEDIATE mode to
improve concurrency support and avoid busy exceptions.
Fixture transactions use DEFERRED mode transactions as all `joinable`
transactions become DEFERRED transactions.
Previously, it was very easy to accidentally leak a database password in
production logs if an error ends up calling inspect on a ConnectionPool
or an individual connection (Adapter). This is due to the default
`#inspect` output for Pools and Adapters being unnecessarily large, and
both currently including passwords (through the DatabaseConfig of a
Pool, and the internal configuration of an Adapter).
This commit addresses these issues by defining a custom `#inspect` for
ConnectionPool, AbstractAdapter, and DatabaseConfig. The condensed
`#inspect` only includes a few valuable fields instead of all of the
internals, which prevents both the large output and passwords from being
included.
Currently, there is no (simple) way to ask a model if it connects to a
single database or to multiple shards. Furthermore, without looping
through a model's connections, I don't believe there's an easy way to
return a list of shards a model can connect to.
This commit adds a `@shard_keys` ivar that's set whenever `.connects_to`
is called. It sets the ivar to the result of `shards.keys`. `shards` in
`.connects_to` defaults to an empty hash and therefore when calling
`connects_to database: {...}` `@shard_keys` will be set to an empty array.
`@shard_keys` is set _before_ the following lines:
```
if shards.empty?
shards[:default] = database
end
```
This conditional sets the one and only shard (`:default`) to the value of `database`
that we pass to `.connects_to`. This allows for calling
`connected_to(shard: :default)` on models configured to only connect to
a database e.g.:
```ruby
class UnshardedBase < ActiveRecord::Base
self.abstract_class = true
connects_to database: { writing: :primary }
end
class UnshardedModel < UnshardedBase
end
UnshardedBase.connected_to(shard: :default) {
UnshardedBase.connection_pool.db_config.name } => primary
```
This is ultimately still an _unsharded_ model which is why `@shard_keys`
gets set before the conditional.
With the new `@shard_keys` ivar we need a way for descendants of the
abstract AR model to return that same value. For that we leverage the
existing `.connection_class_for_self` method. That method returns the
ancestor of the model where `.connects_to` was called, or returns self if
it's the connection class:
```ruby
class UnshardedBase < ActiveRecord::Base
self.abstract_class = true
connects_to database: { writing: :primary }
end
class UnshardedModel < UnshardedBase
end
ActiveRecord::Base.connection_class_for_self => ActiveRecord::Base
UnshardedBase.connection_class_for_self => UnshardedBase(abstract)
UnshardedModel.connection_class_for_self => UnshardedBase(abstract)
```
The new `.shard_keys` method is a getter which returns the value of
`@shard_keys` from the connection class or it returns an empty array.
The empty array is necessary in cases where `connects_to` was never
called.
Finally, I've added an `.connected_to_all_shards` method which takes all of the
arguments for `.connected_to` except for `shard`. Instead, it loops through
every shard key and then delegates everything else to `.connected_to`. I've
used `.map` instead of `.each` so that we can collect the results of each block.
If a developer has neglected to use a structured column type (hstore
or json) or to declare a serializer with `ActiveRecord.store`:
```ruby
class User < ActiveRecord::Base
store_accessor :settings, :notifications
end
```
then a `ConfigurationError` will now be raised with a descriptive
error message when the accessor is read or written:
```ruby
puts user.notifications
# ActiveRecord::ConfigurationError: the column 'settings' has not
# been configured as a store. Please make sure the column is
# declared serializable via 'ActiveRecord.store' or, if your
# database supports it, use a structured column type like hstore or
# json.
```
Previously, in this situation, a `NoMethodError` was raised when the
accessor was read or written:
```ruby
puts user.notifications
# NoMethodError: undefined method `accessor' for an instance of ActiveRecord::Type::Text
```
Raising a descriptive exception should help developers understand more
quickly what's wrong and how to fix it.
Closes#51699
Previously we only provided a method to set the ignored schema cache
tables, but there was no way to ask if a table was ignored by the schema
cache. Applications may want to implement their own schema cache, or at
least run this check. Rather than forcing them to implement an internal
method, this adds a way to ask whether a table is ignored by the schema
cache code.
Usage:
```ruby
ActiveRecord.schema_cache_ignored_tables = ["developers"]
ActiveRecord.schema_cache_ignored_tables?("developers")
```
which respects reject_if and is in nested_attributes order.
When in default index_errors:true mode,
fix#24390 and return index based on full association order.
This commit adds a deprecation warning for the `query_constraints:`
association option. This option will change behavior in the future versions
of Rails and applications are encouraged to switch to `foreign_key:` to preserve the
current behavior.
```ruby
Post.with_recursive(
post_and_replies: [
Post.where(id: 42),
Post.joins('JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id'),
]
)
```
Generates the following SQL:
```sql
WITH RECURSIVE "post_and_replies" AS (
(SELECT "posts".* FROM "posts" WHERE "posts"."id" = 42)
UNION ALL
(SELECT "posts".* FROM "posts" JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id)
)
SELECT "posts".* FROM "posts"
```
at the connection level
Fix#51448
Type cast columns of type `date` to ruby `Date` when running a raw
query through `ActiveRecord::Base.connection.select_all`.
Before:
```ruby
person = Person.find(1)
person.strict_loading!(mode: :n_plus_one_only)
person.posts.first
# SELECT * FROM posts WHERE person_id = 1; -- non-deterministic order
```
After:
```ruby
person = Person.find(1)
person.strict_loading!(mode: :n_plus_one_only)
person.posts.first # this is 1+1, not N+1
# SELECT * FROM posts WHERE person_id = 1 ORDER BY id LIMIT 1;
```
Strict loading in `:n_plus_one_only` mode is designed to prevent performance issues when
deeply traversing associations. It allows `Person.find(1).posts`, but _not_
`Person.find(1).posts.map(&:category)`. With this change, child associations are no
longer eagerly loaded, to match intended behavior and to prevent non-deterministic
order issues caused by calling methods like `first` or `last`.
Fixes#49473.