The following was added to the `ActiveJob::Callbacks`,
`ActiveModel::Callbacks` and `AbstractController:Callbacks` docs
in #29072:
> NOTE: Calling the same callback multiple times will overwrite
> previous callback definitions.
The comment refers to "calling" callbacks but seems to be about defining
callbacks, as mentioned in the PR description.
In the ActiveJob and AbstractController docs, I believe this will be
misinterpreted as referring to setting callbacks, which, as far as I can
tell, does not have such a restriction.
In the ActiveModel docs, I believe it would be slightly clearer to
replace "calling" with "defining".
When the TZ in the given string contains minus offset, both hour and minute
value has to be negated, but the current code negates hour only.
Hence, for instance in Newfoundland Time Zone (UTC−03:30), it used to return
1 hour advanced value.
Co-authored-by: Nobuyoshi Nakada <nobu@ruby-lang.org>
`ActiveModel::AttributeRegistration` is marked `:nodoc:`, so
`type_for_attribute` must be documented under `ActiveModel::Attributes`,
which includes `ActiveModel::AttributeRegistration`.
Also, RDoc does not correctly interpret `:method:` docs when they are
immediately followed by a visibility keyword. Therefore, this commit
adds delimiter comments before the keywords, similar to
4726b1ab47.
Now that we dropped support for Ruby 2.7, we no longer
need to check if variables are defined before accessing them
to avoid the undefined variable warning.
Now that we no longer support Ruby 2.7, many `ruby2_keyword` calls
can be eliminated.
The ones that are left could be eliminated but would end up substantially
slower or more compliacated so I left them for now.
This commit ports `ActiveRecord::AttributeMethods::BeforeTypeCast` to
`ActiveModel::BeforeTypeCast` and includes it in `ActiveModel::Attributes`.
Thus, classes that include `ActiveModel::Attributes` will now
automatically define methods such as `*_before_type_cast`, just as
Active Record models do.
The `ActiveRecord::AttributeMethods::BeforeTypeCast` module is kept for
backward compatibility, but it now merely includes
`ActiveModel::BeforeTypeCast`.
Co-authored-by: Petrik <petrik@deheus.net>
This moves `type_for_attribute` from `ActiveRecord::ModelSchema::ClassMethods`
to `ActiveModel::AttributeRegistration::ClassMethods`, where
`attribute_types` is also defined.
Co-authored-by: Petrik <petrik@deheus.net>
Kernel#respond_to? is generally slow, and so it should be avoided
especially in hot loops. In this case, assign_attributes ends up calling
respond_to? for each attribute being assigned. The purpose of the check
here is to raise UnknownAttributeError when the attribute setter method
doesn't exist.
This commit moves the respond_to? inside a rescue. For a single
attribute being assigned, the performance is about the same. However,
the performance is ~10% better for 10 attributes being assigned and
~30% better for 100 attributes. There is a tradeoff here since using
rescue for control flow is generally not good for performance, however
in this case the ~10% decrease in performance only applies to the
exceptional case when attempting to assign an unknown attribute.
This also partially reverts a225d4bec5.
The commit message doesn't have much info so it's unclear to me why this
was changed.
Benchmark:
```
require "active_record"
require "benchmark/ips"
require "logger"
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Schema.define do
create_table :posts, force: true do |t|
100.times do |i|
t.text :"body_#{i}"
end
end
end
class Post < ActiveRecord::Base
end
one_attribute = { "body_1" => "1" }
ten_attributes = {}
hundered_attributes = {}
invalid_attribute = { "body__1" => "1" }
100.times do |i|
ten_attributes["body_#{i}"] = i.to_s if i < 11
hundered_attributes["body_#{i}"] = i.to_s
end
Benchmark.ips do |x|
x.report("1 attribute") { Post.new(one_attribute) }
x.report("10 attribute") { Post.new(ten_attributes) }
x.report("100 attribute") { Post.new(hundered_attributes) }
x.report("invalid attribute") do
Post.new(invalid_attribute)
rescue ActiveModel::UnknownAttributeError
end
end
```
Before:
```
Warming up --------------------------------------
1 attribute 1.090k i/100ms
10 attribute 805.000 i/100ms
100 attribute 251.000 i/100ms
invalid attribute 966.000 i/100ms
Calculating -------------------------------------
1 attribute 10.882k (± 1.3%) i/s - 54.500k in 5.009085s
10 attribute 8.070k (± 1.2%) i/s - 41.055k in 5.088110s
100 attribute 2.548k (± 1.6%) i/s - 12.801k in 5.026321s
invalid attribute 9.687k (± 2.5%) i/s - 49.266k in 5.089246s
```
After:
```
Warming up --------------------------------------
1 attribute 1.098k i/100ms
10 attribute 884.000 i/100ms
100 attribute 341.000 i/100ms
invalid attribute 868.000 i/100ms
Calculating -------------------------------------
1 attribute 11.004k (± 1.1%) i/s - 55.998k in 5.089635s
10 attribute 8.817k (± 1.8%) i/s - 44.200k in 5.014699s
100 attribute 3.410k (± 0.4%) i/s - 17.391k in 5.100166s
invalid attribute 8.695k (± 0.9%) i/s - 44.268k in 5.091846s
```
Follow-up to #46282.
The purpose of the optimization from #46282 is to avoid unnecessary
`deserialize` / `cast` / `serialize` calls associated with invoking
`value_for_database` on an attribute that has not changed. `dup`ing the
attribute accomplishes that, but `dup`ing the attribute's value is not
appropriate for some value types. For example, a value of the type
`ActiveModel::Type::ImmutableString` requires `clone` instead of `dup`,
and a value of the type `ActiveRecord::Type::Json` likely requires
`deep_dup`. In some cases the only appropriate method may be
`deserialize(serialize(value))`, such as when a serializer for the type
`ActiveRecord::Type::Serialized` deserializes `ActiveRecord::Base`
instances. (In that case, `dup`ing the value would clear its `id`, and
`clone`ing the value would only produce a shallow copy.) However,
`deserialize(serialize(value))` is expensive and would defeat the
purpose of the optimization.
Instead of `dup`ing the attribute, this commit changes the optimization
to use `with_value_from_database(value_before_type_cast)`, which
parallels `with_value_from_database(value_for_database)` in the base
implementation. This drops the (cast) value entirely, causing a fresh
copy to be deserialized if the attribute is subsequently read. In cases
where the attribute is _not_ subsequently read, this will actually be
more efficient since no extra work is performed. And in cases where the
attribute _is_ subsequently read, it will still be more efficient than
`deserialize(serialize(value))`.
Fixes#49809.
For better or worse, the Rails guide settled on double quotes
and a large part of the community also use rubocop which enforce
them by default.
So we might as well try to follow that style when providing code
snippets in the documentation or error messages.
Fix: https://github.com/rails/rails/issues/49822
I certainly didn't get them all, but consistency should be significantly
improved.
This refactors the `ActiveRecord::Attributes` module to use
`ActiveModel::AttributeRegistration`. This also replaces the block form
of the `attribute` method (which was support by only Active Record) with
`decorate_attributes` (which is supported by both Active Model and
Active Record). The block form of the `attribute` method was a private
API, so no deprecation is necessary.
This adds a `decorate_attributes` method to Active Model to support
attribute type decoration. `decorate_attributes` is a private,
low-level API that is intended to be wrapped by high-level APIs like
`ActiveRecord::Base::normalizes` and `ActiveRecord::Base::enum`.
This change would force a lot of existing applications and libraries
to update their tests.
We included it in the beta to collect feedback from the community and
we had some comments about how negative this change would be.
Developers that care about the typography of their error messages
can easily change it in their applications using the translation
files, so there is no need to inflict pain in the upgrade process
by changing the default in the framework.
Revert "Merge PR #45463"
This reverts commit 9f60cd8dc7, reversing
changes made to 35d574dbfd.
`undefine_attribute_methods` now removes alias attribute methods along
with attribute methods. This commit changes `define_attribute_methods` to
redefine methods back if any alias attributes were declared which provides
applications and libraries an option to bring the alias methods back
after using `undefine_attribute_methods`.
We ran into a few cases at GitHub where we were using alias_attribute
incorrectly and the new behavior either didn't warn or raised an unclear
deprecation warning. This attempts to add clarity to the deprecation reason
when you try to alias something that isn't actually an attribute.
Previously, trying to alias a generated attribute method, such as `attribute_in_database`, would
still hit `define_proxy_call`, because we were only checking the owner of the target method.
In the future, we should probably raise if you try to use alias_attribute for a non-attribute.
Note that we don't raise the warning for abstract classes, because the attribute may be implemented
by a child class. We could potentially figure out a way to raise in these cases as well, but this
hopefully is good enough for now.
Finally, I also updated the way we're setting `local_alias_attributes` when `alias_attribute` is
first called. This was causing problems since we now use `alias_attribute` early in the
`load_schema!` call for some models: https://buildkite.com/rails/rails/builds/98910
The current implementation of _to_partial_path composes the part of two bits, `collection' and `element'.
ActiveModel::Name also contains these fields, and they are derived the same way _to_partial_path does it.
However, _to_partial_path doesn't use the information in model_name, and solely relies on its own computations instead.
This works for all standard cases, but not necessarily for models that provide a non-standard model_name.
This commit fixes that and has _to_partial_path use model_name if the class responds to it.
This commit allows customizing the delimiter used by `to_param` when
`to_key` returns multiple value. This unblocks supporting more varieties
of composite primary key types in Active Record.
This commit adds support for composite identifiers in `to_key`.
Rails 7.1 adds support for composite primary key which means that
composite primary key models' `#id` method returns an `Array` and
`to_key` needs to avoid double-wrapping the value.
Because we're using a class_attribute to track all of the attribute_aliases,
each subclass was regenerating the parent class methods, causing some unexpected
deprecation messages.
Instead, we can use a class instance variable to track the attributes aliased locally
in that particular subclass. We then walk up the chain and re-define the attribute methods
if they haven't been defined yet.
If the user defined a translation to a nested error on base we should
look it up in the same way we do for the other attributes.
If no translation is set, we fallback to the name of the association.
Fixes#48884.
`define_proxy_call` accepts `call_args` as an argument which impacts
method body generation but it doesn't use `call_args` in the `namespace`
generation which leads to the same cached method to be reused even if
`call_args` differ.
It has never been an issue since `define_proxy_call` was only used to
generate Active Record attribute methods and we were always passing
the same `call_args` per method name.
However, since https://github.com/rails/rails/pull/48533 we are using
`define_proxy_call` to generate alias attribute methods where `call_args`
differ for the same method name which leads to the same cached method
being reused in wrong places.
This commit fixes the issue by making sure `call_args` are being
considered when generating the `namespace` for the method.