If you have a `has_one through` assocation where the `through` has a
`belongs_to` the foreign key won't be `id` for the joins. This change
ensures we use the correct foreign key when querying with disable joins
turned off. Prior to this change the call to `project.lead_developer`
would return `nil` if `disable_joins` was set.
Project has one lead developer through firm. Project also belongs to
firm. So when we call `project.lead_developer` it needs to find the
lead developer through the firm. In these cases we don't want to read
the owners id, we want to read the attribute that will give us the
value of the first chain item's `join_foreign_key`.
Ref: https://github.com/rails/rails/pull/42237
Ref: https://bugs.ruby-lang.org/issues/17763
Ruby class variables are slow, and get slower the more ancestors the class has.
And it doesn't seem likely to get faster any time soon. Overall I'm under the
impression that ruby-core consider them more or less deprecated.
But in many cases where `cattr_accessor` is used, a class instance variable
could work just fine, and would be much faster.
For Active Record this means most of the `cattr_accessor` on `ActiveRecord::Base`
could actually be `attr_accessor` on `ActiveRecord`.
I started with `legacy_connection_handling` as a proof of concept.
This allows to enable or disable thew new behavior on a per
mode basis, which is helpful for large applications where
fixing all the problems at once is too much.
In https://github.com/rails/rails/pull/41084#discussion_r626739953, @pixeltrix helped me work through adding a new feature to the migration compatibility system. We both founded it a bit head scratching understanding the inheritance chain. In this PR I'm adding comments for future developers working on this to hopefully save some head scratching.
These comments aren't intended to be public - I think the `# :nodoc: all` on line 5 should prevent that.
This allows to have a different behavior on update and create.
For instance it might be desirable to disable partial inserts
to protect against a concurrent migration removing the default
value of a column, which would cause processes with an outdated
schema cache to fail on insert.
BatchEnumerator is a simple object storing a few parameters coming from
ActiveRecord::Batches#in_batches when it's called without a block.
Sometimes, it's useful to access those values, in our case, a library
receiving a BatchEnumerator from application code.
Co-authored-by: Adrianna Chang <adrianna.chang@shopify.com>
Ruby master ships with Psych 4.0.0 which makes `YAML.load`
defaults to safe mode (https://github.com/ruby/psych/pull/487).
However since these YAML files are trustworthy sources
we can parse them with `unsafe_load`.
`cattr_accessor` rely on class variables which has terrible
performance on long ancestor chains. See https://bugs.ruby-lang.org/issues/17763
for a detailed description of the problem.
In comparison `class_attribute` on `ActiveRecord::Base` is almost 7x
faster:
```
Calculating -------------------------------------
logger 1.700M (± 0.9%) i/s - 8.667M in 5.097595s
clogger 11.556M (± 0.9%) i/s - 58.806M in 5.089282s
Comparison:
clogger: 11555754.2 i/s
logger: 1700280.4 i/s - 6.80x (± 0.00) slower
```
This is because `ActiveRecord::Base.ancestors.size == 62`.
Most databases order tables with the `NULL` value first, having it before
all other data values. Postgres has `NULLS` last.
Fortunately, ANSI SQL has an option to allow the database to specify where NULLS
come out in this sort order
ORDER BY column ASC NULLS FIRST
MS SQL, SQLite, Oracle, and Postgres all follow this syntax. Unfortunately, MySql
does not.
Before:
PostgreSQL: both `.nulls_first()` and `.nulls_last()` work as designed.
Others: both raise a runtime error.
After:
MySQL: `.nulls_first()` works as designed.
MySQL: `.nulls_last()` raises a runtime error
Others: both work as designed
Calling `load_async` on a null relation was executing the fallback
`WHERE 1=0` query, since it doesn't go through `exec_queries`.
Since 2a90104989b9f1653337d08871610d394b2d626d, `exec_queries` is
implemented in terms of `exec_main_query`, so we only need to override
that one method.
We can also stop assigning `@records`, as that now happens in `load`
since 847643a55acf9c82d2b70448cfa527fee63e9601.
If you have an application that has `ApplicationRecord` and another
class like `PrimaryApplicationRecord` and the `PrimaryApplicationRecord`
is set to `primary_abstract_class`, then in a lazy loaded env like
development, it's possible for `application_record_class` to be set to
`ApplicationRecord`. This would then raise an error that you can't reset
the class if you then loaded `PrimaryApplicationRecord`.
To fix this we've removed the setter for `application_record_class` in
the fall through so that if this does happen, `ApplicationRecord` will
only be the primary class until the correct primary class is loaded.
If an application sets 2 classes to `primary_abstract_class`, the error
will still be raised.
Co-authored-by: John Crepezzi <john.crepezzi@gmail.com
Until now `config.session_store :disabled` simply silently
discard the session hash at the end of the request.
By explictly failing on writes, it can help discovering bugs
earlier.
Reads are still permitted.
Fix: #42229
It is unclear why it was defined to return an empty string
in #5809.
But returning the query that will actually be generated if
the NullRelation is executed makes more sense.
#one? and #many? generate a query like `SELECT COUNT(*) FROM posts` under the hood, and then compare if the result is equal (or greater) to 1. That count operation can be really slow for large tables or complex conditions, but there's no need to count all the records in these cases. It's much faster just by adding a limit, like `SELECT COUNT(*) FROM posts LIMIT 2`
I originally added the `type` to the `column_exists?` check in
`add_column` because I thought it would be useful to still raise if you
tried to add a column with a different type. It seemed bad to silently
ignore.
The `type` check works fine for standard, non-type casted types like
`string`, but migrations that use types like `blob` in mysql/sqlite3 or
`char` in postgres cast these to other types. When `c.type ==
type.to_sym` is called in those cases they won't match. For example
`c.type` for a `blob` column will return `binary` so the comparison
won't match. I attempted to cast the `type` passed in but that's error
prone because for dbs like postgres we need _a lot_ more information
than just a type to determine how to cast it. I also tried going the
other way but the column doesn't store the original type, just the type
casted type (as type) and the sql type which is a string representation
of type that doesn't always match the original.
This functionality was originally extracted from GitHub and when I
looked back at our old implementation I found that we originally weren't
passing type. This came up in a migration for enterprise that wasn't
properly handling the `if_not_exists` because our `type` check was
comparing `binary` to `mediumblob`. I think it makes the most sense to
leave the public `column_exists?` method as is and remove the `type`
check from `column_exists?` in the `add_column` method.
Currently Active Record derives the foreign key name for `has_many`
associations based on the Active Record Base `name. Sometimes when using
additional conventions for class names, such as with class name suffixes
and prefixes, it makes sense to be able to further customize the
foreign key logic to account for a different pattern.
Similar to what was done in the
[case of table names](https://github.com/rails/rails/pull/42213),
as Active Record Base extends Active Model Naming, we have already a
`model_name` at the class-level that behaves similarly to `name` while
at the same time giving more flexibility by allowing specifying a custom
name and namespacing.
This commit changes the foreign key computation in Reflections to infer
its value based on the Active Record's `model_name` object instead of
its class `name`. This allows customization of the foreing key pattern
since a distinct instance of `ActiveModel::Name` can be used instead.
For example, to use `post_id` as foreign key for a `PostRecord` class
in a `has_many` association:
```ruby
class PostRecord < ActiveRecord::Base
has_many :comments
class << self
def model_name
ActiveModel::Name.new(self, nil, "Post")
end
end
end
PostRecord.reflect_on_association(:comments).foreign_key
# => "post_id"
```
Instead of using the Active Record Base's class name, the table name is
now inferred based on its model name object. This allows customization
of the table name pattern since a distinct instance of
`ActiveModel::Name` can be used instead.
For example, to map a `PostRecord` class to a `posts` table:
```ruby
class PostRecord < ActiveRecord::Base
class << self
def model_name
ActiveModel::Name.new(self, nil, 'Post')
end
end
end
PostRecord.table_name
# => "posts"
```
In a multidatabse setup, migrations live in seperate directories. The check for duplicate migration names, does not take this into
account.
So you __can__ end up having multiple migration with the same name.
When running `rails db:migrate` from the console, this other migration
might be loaded __before__ running the migrations for "this" database.
Because they have the same name, we have to ensure that the
contents of this migration is not carried over from "the other"
migrations.
Logs a warning message when running SQlite in production.
The warning can be disabled by setting `config.active_record.sqlite3_production_warning=false`.
Closes#34715
The docs for this middleware indicate that you can set a delay that
can be a duration (eg. `2.seconds`).
https://api.rubyonrails.org/v6.1/classes/ActiveRecord/Middleware/DatabaseSelector.html
In normal apps, this works fine without an explicit require because
the extension is required through other libs. However, when creating a
minimal app, this results in an undefined method error.
This is something that is already being included in
test/development/production config files since c8c5497.
This is similar to the `disable_joins` option on `has_many :through`
associations applied to `has_one :through` associations. When
`disable_joins` is set Rails will create 2 or more queries to get
associations instead of generating a join.
```ruby
class Person
belongs_to :dog
has_one :veterinarian, through: :dog, disable_joins: true
end
```
Then instead of generating join SQL, two queries are used for `@person.veterinarian`:
```
SELECT "dogs"."id" FROM "dogs" WHERE "dogs"."person_id" = ? [["person_id", 1]]
SELECT "veterinarians".* FROM "veterinarians" WHERE "veterinarians"."dog_id" = ? [["dog_id", 1]]
```
Co-authored-by: Eileen M. Uchitelle <eileencodes@gmail.com>
In https://github.com/rails/rails/issues/21126 it was suggested to make "timestamp with time zone" the default type for datetime columns in PostgreSQL. This is in line with PostgreSQL [best practices](https://wiki.postgresql.org/wiki/Don't_Do_This#Don.27t_use_timestamp_.28without_time_zone.29). This PR lays some groundwork for that.
This PR adds a configuration option, `ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type`. The default is `:timestamp` which preserves current Rails behavior of using "timestamp without time zone" when you do `t.datetime` in a migration. If you change it to `:timestamptz`, you'll get "timestamp with time zone" columns instead.
If you change this setting in an existing app, you should immediately call `bin/rails db:migrate` to ensure your `schema.rb` file remains correct. If you do so, then existing columns will not be impacted, so for example if you have an app with a mixture of both types of columns, and you change the config, schema dumps will continue to output the correct types.
This PR also adds two new types that can be used in migrations: `t.timestamp` and `t.timestamptz`.
```ruby
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type = :timestamp # default value is :timestamp
create_table("foo1") do |t|
t.datetime :default_format # "timestamp without time zone"
t.timestamp :without_time_zone # "timestamp without time zone"
t.timestamptz :with_time_zone # "timestamp with time zone"
end
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type = :timestamptz
create_table("foo2") do |t|
t.datetime :default_format # "timestamp with time zone" <-- note how this has changed!
t.timestamp :without_time_zone # "timestamp without time zone"
t.timestamptz :with_time_zone # "timestamp with time zone"
end
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:my_custom_type] = { name: "custom_datetime_format_i_invented" }
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type = :my_custom_type
create_table("foo3") do |t|
t.datetime :default_format # "custom_datetime_format_i_invented"
t.timestamp :without_time_zone # "timestamp without time zone"
t.timestamptz :with_time_zone # "timestamp with time zone"
end
```
**Notes**
- This PR doesn't change the default `datetime` format. The default is still "timestamp without time zone". A future PR could do that, but there was enough code here just getting the config option right.
- See also https://github.com/rails/rails/pull/41395 which set some groundwork (and added some tests) for this.
- This reverts some of https://github.com/rails/rails/pull/15184. https://github.com/rails/rails/pull/15184 alluded to issues in XML serialization, but I couldn't find any related tests that this broke.
`codespell` works with a small custom dictionary and seems to find perhaps more spelling mistakes than `misspell` which really only fixes commonly misspelled English words.
Not all spell checkers can check all file types and most spell checkers can't find all the errors.
https://github.com/codespell-project/codespellhttps://pypi.org/project/codespell/
Ruby (2.4+) includes a native implementation of `sum` with significant
performance gains. Rails 7.1 will be removing `Enumerable#sum` and
`Array#sum` in favor of Ruby's native implementation.
This commit adds a deprecation warning to calls with non-numeric
arguments without a suitable initial argument as those will be required
once Rails removes this functionality.
Some examples that will now trigger a deprecation warning:
>> %w[foo bar].sum
>> [[1, 2], [3, 4, 5]].sum
To avoid the deprecation warning they should now invoked as follows:
>> %w[foo bar].sum('')
>> [[1, 2], [3, 4, 5]].sum([])
In order to prepare for the deprecation on Rails 7.1, it also
deprecates `[nil].sum == 0`, which in Ruby's native implementation
throws a `TypeError`.
- Case
- CurrentRow
- Distinct
- InfixOperation
- NotRegexp
- Regexp
- UnaryOperation
- With
Also add test coverage for When and Else for which support was just
added in a previous commit in this changeset.
Also use assert_edge test helper for Arel::Visitors::Dot.
Even though encryption performance test have been ignored in 1fff866,
they're still included when using `bin/test` as the executable, and the
output could be confusing.
Following the approach used in ed8667d, this assertion is now quiet by
default.
Fixes https://github.com/rails/rails/issues/42122
Currently if you call a reader on a singular association, no error is raised if the target class doesn't exist (it just returns `nil`). If you call a reader on a collection association an error is raised, but the error appears to not be intended for this case and doesn't have a very good message.
`ActiveRecord::Reflection::AssociationReflection#compute_class` appears to be set up to handle this, but it is not being called correctly. So this PR just makes a call to that (via `reflection.klass`) from `SingularAssociation#reader` and `CollectionAssociation#reader`.
Instead of having many aliases for all the Arel::Nodes classes, rely
on generic methods for Unary, Binary, and Function (which is possible
because Arel::Visitors::Visitor#visit falls back to ancestors' visit
methods).
Note that a few new Node types are now implicitly supported that were
not previously explicitly supported, including:
- Bin
- DistinctOn
- Else
- Except
- Intersect
- Lock
- Quoted
- Union
- UnionAll
- When
This does not need to be a separate method since a2040ee which removed
the Oracle12 visitor, and essentially reverses the Oracle prefactor
from 8d04c28.
Migration version references has been updated 2 times in less than 3
weeks (#41894, #42113).
I'd not want to receive the same tweaks in the near future.
Both MySQL and PostgreSQL support dropping multiple columns in a single
SQL statement.
For tables that are very large, dropping a column can be time consuming.
When multiple columns are involved, dropping them all at once can reduce
the total overhead when compared to dropping each column individually.
SQLite3 does not support this feature so its adapter overrides the
remove_columns method to workaround SQLite3's ALTER TABLE limitations.
The already written method remove_columns_for_alter creates the ALTER
TABLE SQL fragments to execute.
The remove_timestamps method also happens to drop multiple columns, so
it now uses the updated remove_columns to take advantage of the
optimization.
This module is used in many classes but only required in one of them.
Depending in the load order, it could be undefined before its first use.
Instead of adding requires to every single files that use it I
registered an autoload entry for it.
If you have an application that has strict_loading set and then call
`reload` that would cause the preload values to get lost and
applications would start throwing a stict loading violation error.
In order to fix this we capture the association cache and re-apply that
to the reloaded records to avoid the strict loading error. Of course if
you never preloaded your records, this will still raise a strict loading
violation.
This change also removes the `reload` definition from associations.rb
because we can get the same behavior when we reassign the association
cache.
Co-authored-by: Aaron Patterson <tenderlove@ruby-lang.org>
These tests have been failing intermittently since
054e19d08638e64fa9eacc9be32fb7fde4e7415c was merged.
They replace the connection handler during setup, but before that can
happen the existing handler's pool configuration has already been saved
by `setup_shared_connection_pool`. Later when the test calls
`teardown_shared_connection_pool`, it tries to restore connection pools
that do not exist in the new handler and blows up.
We can avoid this problem by calling `teardown_shared_connection_pool`
to clear the saved pools before replacing the connection handler.
called
enlist_fixture_connections is currently called within the code when the
context is `run_in_transasction?`. However if
`enlist_fixture_connections` is called outside of that context the code
fails since these variables are not defined. This ensures the code can
still be executed without side effects
It's make more sense to revert the naming approach:
- `#previous_types`, the exposed public method, always include the clean
text type when suport for unencrypted data is enabled
- `#previous_types_without_clean_text` is a private method used
internally by the type
These models have their own connections since
8b83793549fed994bf0231aff444aa74648b3c35.
Removing them was breaking 054e19d08638e64fa9eacc9be32fb7fde4e7415c in a
way that couldn't happen in a real app: pool managers are never removed,
except by this test helper.
We were using the same method for 2 things: fetching previous types, and
fetching previous types with the cleantext type. This was making logic
harder to follow.
This fixes a problem in Active Record Encryption where, when `config
.support_unencrypted_data` was on, it was failing to try additional
previous encryption schemes beyond the first one.
The reason is that, witwh the previous implementation, when this flag
was toggled on, it was never raising encryption errors when checking
previous types.
Since b71c1cddddea736ae5af70474fe9bd765596e727, the SchemaCache
is loaded during the application initialization before the connection
adapter is loaded, which means that the application could fail to boot.
To fix this issue an autoload is setup for the schema cache and the
direct require is removed.
This autoload doesn't need to be moved to `eager_autoload` block
because the constant is already referenced in an initializer.
In a multiple database application, associations can't join across
databases. When set, this option tells Rails to make 2 or more queries
rather than using joins for associations.
Set the option on a has many through association:
```ruby
class Dog
has_many :treats, through: :humans, disable_joins: true
has_many :humans
end
```
Then instead of generating join SQL, two queries are used for `@dog.treats`:
```
SELECT "humans"."id" FROM "humans" WHERE "humans"."dog_id" = ? [["dog_id", 1]]
SELECT "treats".* FROM "treats" WHERE "treats"."human_id" IN (?, ?, ?) [["human_id", 1], ["human_id", 2], ["human_id", 3]]
```
This code is extracted from a gem we use internally at GitHub which
means the implementation here is used in production daily and isn't
experimental.
I often get the question "why can't Rails do this automatically" so I
figured I'd include the answer in the commit. Rails can't do this
automatically because associations are lazily loaded. `dog.treats` needs
to load `Dog`, then `Human` and then `Treats`. When `dog.treats` is
called Rails pre-generates the SQL that will be run and puts that
information into a reflection object. Because the SQL parts are pre-generated,
as soon as `dog.treats` is loaded it's too late to skip a join. The join
is already available on the object and that join is what's run to load
`treats` from `dog` through `humans`. I think the only way to avoid setting
an option on the association is to rewrite how and when the SQL is
generated for associations which is a large undertaking. Basically the
way that Active Record associations are designed, it is currently
impossible to have Rails figure out to not join (loading the association
will cause the join to occur, and that join will raise an error if the
models don't live in the same db).
The original implementation was written by me and Aaron. Lee helped port
over tests, and I refactored the extraction to better match Rails style.
Co-authored-by: Lee Quarella <leequarella@gmail.com>
Co-authored-by: Aaron Patterson <aaron@rubyonrails.org>
Ultimately it's exactly the same semantic that was implemented
using `superclass`, except it avoid recursion and benefit from
method cache, so much faster the longer the inheritance chain.
The use of an alias_method chain is unfortunate, but I don't
see any alternative to cast the provided value to string.
When saving a record, autosave adds callbacks to save its' associations.
Since the associations can have similar callbacks for the inverse,
endless loops could occur.
To prevent these endless loops, the callbacks for `has_many` and
`belongs_to` are defined as methods that only execute once.
This is implemented in the `define_non_cyclic_method` method.
However, this wasn't used for the `has_one` callbacks.
While `has_one` association callbacks didn't result in endless loops,
they could execute multiple times.
For example for a bidirectional `has_one` with autosave enabled,
the `save_has_one_association` gets called twice:
class Pirate < ActiveRecord::Base
has_one :ship, autosave: true
def save_has_one_association(reflection)
@count ||= 0
@count += 1 if reflection.name == :ship
super
end
end
class Ship < ActiveRecord::Base
belongs_to :pirate, autosave: true
end
pirate = Pirate.new(catchphrase: "Aye")
pirate.build_ship(name: "Nights Dirty Lightning")
pirate.save!
# this returns 2 instead of 1.
assert_equal 1, pirate.instance_variable_get(:@count)
This commit changes `has_one` autosave callbacks to be non-cyclic as
well. By doing this the autosave callback are made more consistent for
all 3 cases: `has_many`, `has_one` and `belongs_to`.
The return type was changed in the PR #41704 after addition of mode
option. The current documentation is misleading since
documentation puropose strict_loading! would return boolean whereas
it returns the current mode set.
I can across this issue while debugging issue: #41827 and thought
this should be brought to the attention.
PR fixes the issue and would always return boolean based on
strict_loading is enabled or disabled.
```
user.strict_loading! # => true
user.strict_loading!(false) # => false
user.strict_loading!(mode: :n_plus_one_only) # => true
```
Calling save on a record with cyclic autosave callbacks, can call other
callbacks and hooks multiple times. This can lead to unexpected
behaviour.
For example `save` gets called twice on Post in the following example.
This results in `changes_applied` getting called twice.
class Post < ApplicationRecord
belongs_to :postable, polymorphic: true, inverse_of: :post
end
class Message < ApplicationRecord
has_one :post, as: :postable
end
post = Post.create!(postable: Message.new(subject: "Hello, world!"))
# the following would return false when true is expected
post.id_previously_changed?
`save` gets called twice because Post autosaves Message, which
autosaves Post again.
Instead of calling `changes_applied` everytime `save` is called,
we can skip it if it has already been called once in the current saving
cycle. This requires us to track the `@_saving` state of a record.
if `@_saving` is true we know we the record is being saved.
To track if a method has already been called we reuse the
@_already_called hash that is already used for this purpose.
Commit a1a5d37749964b1e1a23914ef13da327403e34cb tried to fix doubles
saves in autosaved associations. However, it didn't work properly for
non-nullable foreign_keys and had to be reverted.
To prevent this in the future, add a regression test.
The previous: config option was being skipped because it was checking
the existence of a reader method instead of the accessor itself.
This also adds a test for the .configure option that was missing.
Before when calling `preventing_writes?` in the connection the code
would raise a `NoMethodError` when calling `current_preventing_writes`
in a String.
This problem only happen when `establish_connection` is called on the
`ConnectionHandler` and not in the model itself.
This also removes the conditional from the `PoolConfig`.
This saves some array allocations from avoiding `*args`, as well
as makes the Method object `arity` and `parameters` correct.
e.g. before this patch, ArgumentError would be confusing:
```ruby
>> model.name_was(1)
ArgumentError: wrong number of arguments (given 2, expected 1)
```
The payload name for `delete_all` was named "Destroy" in #30619 since
`delete_all` was used in `record.destroy` at that time.
Since ea45d56, `record.destroy` no longer relies on `delete_all`, so now
we can improve the payload name for `delete_all` to more appropriate.
If a model which has a user-defined `self.default_scope` is joined with
table alias, a user-defined `self.default_scope` ignores table alias.
This problem has potentially existed for a long time, but it has not
become apparent because it is difficult to meet this condition.
Since #40106, table alias is easily used if association names are used
in `where`.
So user-defined `self.default_scope` should be evaluated in the current
aliased relation.
Fixes#41857.
This commit adds more empty methods to a connection pool.
In normal usage, a connection is always be associated with a pool, but
for testing (we do this a few places in the AR test suite) it can be
convenient to build a connection and use it without associating a pool.
In most cases, this worked fine, but there were a few corner cases which
would hit NoMethodError on the NullPool. In particular in some cases
raising in a transaction would error in connection.throw_away!.
To make these "standalone" or "pool-less" connections work consistently,
this commit adds the methods which a connection could call on it's pool
(but not any other of ConnectionPool's public interface).
ActiveRecord::Type::Registry doesn't need to inherit from
ActiveModel::Type::Registry, and it makes both classes more simple.
Co-authored-by: Adrianna Chang <adrianna.chang@shopify.com>
This deprecates `legacy_connection_handling` via the
`connection_handlers` setter. This is called from the ActiveRecord
Railtie on boot and since most applications don't set this themselves
this will prevent the deprecation from being raised multiple times for a
test run or in development.
I've also updated the guides to include a migration path for
applications using the deprecated methods. The majority of applications
won't need to make any changes.
Reusing the "books" one could cause interferences when fixtures are
loaded in a very specific order such as:
https://buildkite.com/rails/rails/builds/76217#ee4ce591-e6c1-4a0d-a7db-1f83647d141e
Reproduction script:
```
activerecord $ bin/test -v --seed 23607 -n "/^(?:EagerAssociationTest#(?:test_preloading_a_regular_association_with_a_typo_through_a_polymorphic_association_still_raises)|ActiveRecord::Encryption::EncryptableFixtureTest#(?:test_fixtures_get_encrypted_automatically)|ViewWithoutPrimaryKeyTest#(?:test_attributes|test_reading))$/"
```
I noticed in profiles of simple queries (like `Post.where(id: 1).first`)
we're spending lots of time looking up the current thread. I couldn't
find any way to speed this up in Ruby, so I thought maybe we could call
`Thread.current` fewer times per query.
This patch should eliminate 4 calls to `Thread.current` per query.
For this benchmark:
```ruby
StackProf.run(mode: :wall, out: 'out.dump') do
8000.times { Post.where(id: id).first }
end
```
`Thread.current` goes from 7% to 4.7% of time:
```
==================================
Mode: wall(1000)
Samples: 1633 (0.00% miss rate)
GC: 51 (3.12%)
==================================
TOTAL (pct) SAMPLES (pct) FRAME
140 (8.6%) 140 (8.6%) String#sub!
114 (7.0%) 114 (7.0%) Thread.current
```
```
==================================
Mode: wall(1000)
Samples: 1719 (0.00% miss rate)
GC: 51 (2.97%)
==================================
TOTAL (pct) SAMPLES (pct) FRAME
134 (7.8%) 134 (7.8%) String#sub!
99 (5.8%) 99 (5.8%) Module#===
81 (4.7%) 81 (4.7%) Thread.current
```
This isn't huge, but I think we need to find more sources of
Thread.current. It's surprising to me that we spend so much time
looking up the current thread when doing a query that is so "easy"
We're spending time validating symbol parameters of the ScopeRegistry.
It's an internal class, and we can stop validating symbols by converting
to methods (you'll automatically get an error if you try to call a
method that doesn't exist). Second, since we only have 3 things to keep
track of, rather than keep those things in a hash, just break it out in
to 3 instance variables. (This is absolutely not a memory bottleneck,
but technically this patch will save some memory as the 3 ivars will be
embedded in the object rather than require a full st_table for the
original wrapper hash)
This examines all the association branches we are being asked to preload
and will delay loading an association if it's likely that we find a
similar association later and can batch them together.
For example, when loading
Author.preload(:posts, favorite_authors: :posts).first
The preloader now knows to delay loading the top level posts so that it
can load both the top level :posts and the :posts from the favourite
authors associations together.
Co-authored-by: Dinah Shi <dinahshi@github.com>
This implements several changes to encourage deterministic encryption to
remain unchanged. The main motivation is letting you define unique
indexes on deterministically-encrypted columns:
- By default, deterministic encryption will always use the oldest
encryption scheme to encrypt new data, when there are many.
- You can skip this default behavior and make it always use the current
encryption scheme with:
```ruby
deterministic: { fixed: false } # using this should be a rare need
```
- Deterministic encryption still supports previous encryption schemes
normally. So they will be used to add additional values to queries, for
example.
- You can't rotate deterministic encryption keys anymore. We can add
support for that in the future.
This makes for reasonable defaults:
- People using "deterministic: true" will get unique indexes working out
of the box.
- The system will encourage keeping deterministic encryption stable:
- By always using oldest encryption schemes
- By forbidding configuring multiple keys
But you can still opt-out of the default if you need to.
This adds a new class `Scheme` that encapsulates the encryption
properties for a given attribute. This adds a proper code representation
for a domain concept we keep referring to: encryption schemes.
It's in charge of processing the user-passed config properties when
declaring the attribute, as well as of validating those.
This removes the concern of processing properties from
`EncryptableRecord` and `EncryptableAttributeType`. It's also nice to
have a place to group attribute encryption options, versus passing
lists of hashes around.
This is in preparation to upcoming changes that will add new config
options and support for previous encryption schemes.
The .encrypts declaration in `ActionText::Encryption` was making this test
fail. It was failing to load a MySQL error that was being raised when trying
to determine the column limit.