Commit Graph

2667 Commits

Author SHA1 Message Date
Jean Boussier
403743ed88 Fix alias_attribute to ignore methods defined in parent classes
Fix: https://github.com/rails/rails/issues/52144

When defining regular attributes, inherited methods aren't overriden,
however when defining aliased attributes, inherited methods aren't
considered.

This behavior could be debatted, but that was the behavior prior
to https://github.com/rails/rails/pull/52118, so I'm restoring it.
2024-06-18 09:44:31 +02:00
Jean Boussier
514d474836 Fix a performance regression in attribute methods
Fix: #52111
Fix: 5dbc7b4

The above commit caused the size of the `CodeGenerator` method cache
to explode, because the dynamic namespace is way too granular.

But there is actually a much better fix for that, since `alias_attribute`
is now generating exactly the same code as the attribute it's aliasing,
we can generated it as the canonical method in the cache, and then just
define it in the model as the aliased name.

This prevent the cache from growing a lot, and even reduce memory
usage further as the original attribute and its alias now share
the same method cache.
2024-06-13 17:50:11 +02:00
Rafael Mendonça França
7ee34d9efb
Enable Rails minitest plugin in our rake tasks 2024-05-23 16:16:37 +00:00
Rafael Mendonça França
bf59d363fb
Clean CHANGELOG for 8.0 2024-05-13 16:55:52 +00:00
Rafael Mendonça França
37fd0e7fe4
Development of Rails 8.0 starts now
🎉
2024-05-13 16:45:20 +00:00
fatkodima
702638291c
Fix tests without assertions in the framework 2024-04-30 23:29:30 +00:00
Jean Boussier
50daadaa71 Update test suite for compatibility with Ruby 3.4-dev
https://bugs.ruby-lang.org/issues/19117 and https://bugs.ruby-lang.org/issues/16495
slightly change how backtrace are rendered which makes a few tests fail.
2024-02-16 11:55:44 +01:00
Nick Dower
22bc976576 Tiny update to callbacks docs [ci skip]
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".
2024-02-02 12:11:30 +01:00
Petrik
8565f45100 Use relative includes of README's in documentation [ci-skip]
The Rails documentation uses the `:include:` directive to inline the
README of the framework into the main documentation page. As the
README's aren't in the root directory from where SDoc is run we need to
add the framework path to the include:

    # :include: activesupport/README.md

This results in a warning when installing the gems as generating the rdoc for the gem is run from the gem/framework root:

    Couldn't find file to include 'activesupport/README.rdoc' from lib/active_support.rb

The `:include:` RDoc directive supports includes relative to the current
file as well:

    # :include: ../README.md

This makes sure it works for the Rails API docs and the separate gems.

Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>
2024-01-18 10:39:15 +01:00
Akira Matsuda
a71e0aabe3
Merge pull request #46872 from amatsuda/fast_string_to_time_bug
TZ offset minute has to be negated in the Western Hemisphere
2024-01-17 17:55:38 +09:00
Akira Matsuda
d10d5aedce
TZ offset minute has to be negated in the Western Hemisphere
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>
2024-01-17 17:25:37 +09:00
Jean Boussier
946e46ebcc Modernize method missing implementations
`...` is both simpler an more correct since the keyword argument
separation.
2024-01-16 13:17:45 +01:00
Jean Boussier
2cd568a5da Handle alternative base classes in define_attribute_methods
Ref: d429bfb3b6 (r136670440)
2024-01-15 17:20:56 +01:00
Petrik de Heus
8944d804ec
Add examples to #slice and #values_at documentation [ci-skip] (#50679) 2024-01-12 11:58:23 +01:00
Jean Boussier
c0b5052d92
Merge pull request #50609 from ricardotk002/use-array-intersect
Replace usage of `Array#?` with `Array#intersect?` for efficiency
2024-01-07 21:15:56 +01:00
Jonathan Hefner
29456080c7 Ensure type_for_attribute method docs are visible [ci-skip]
`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
4726b1ab4780a8d8033130165eb1628286935dcb.
2024-01-06 18:07:26 -06:00
Ricardo Díaz
de154095ed Replace usage of Array#? with Array#intersect? for efficiency
`Array#intersect?` was introduced in Ruby 3.1.0 and it's more efficient and
useful when the result of the intersection is not needed as the
following benchmarks show:

```
require "bundler/inline"

gemfile(true) do
  source "https://rubygems.org"

  git_source(:github) { |repo| "https://github.com/#{repo}.git" }

  gem "rails", path: "./"
  # If you want to test against edge Rails replace the previous line with this:
  # gem "rails", github: "rails/rails", branch: "main"
  gem "benchmark-ips"
end

require "active_support"

SCENARIOS = [
  [(1..100).to_a, (90..200).to_a],    # Case 1
  [("a".."m").to_a, ("j".."z").to_a], # Case 2
  [(1..100).to_a, (101..200).to_a],   # Case 3
]

SCENARIOS.each_with_index do |values, n|
  puts
  puts " Case #{n + 1} ".center(80, "=")
  puts
  Benchmark.ips do |x|
    x.report("Array#?") { !(values[0] & values[1]).empty? }
    x.report("Array#intersect?")      { values[0].intersect?(values[1]) }
    x.compare!
  end
end
```

Results:

```
==================================== Case 1 ====================================

ruby 3.2.2 (2023-03-30 revision e51014f9c0) [arm64-darwin21]
Warming up --------------------------------------
             Array#?    34.221k i/100ms
    Array#intersect?    62.035k i/100ms
Calculating -------------------------------------
             Array#?    343.119k (± 1.1%) i/s -      1.745M in   5.087078s
    Array#intersect?    615.394k (± 1.1%) i/s -      3.102M in   5.040838s

Comparison:
    Array#intersect?:   615393.7 i/s
             Array#?:   343119.4 i/s - 1.79x  slower

==================================== Case 2 ====================================

ruby 3.2.2 (2023-03-30 revision e51014f9c0) [arm64-darwin21]
Warming up --------------------------------------
             Array#?   103.256k i/100ms
    Array#intersect?   185.104k i/100ms
Calculating -------------------------------------
             Array#?      1.039M (± 1.3%) i/s -      5.266M in   5.066847s
    Array#intersect?      1.873M (± 1.6%) i/s -      9.440M in   5.041740s

Comparison:
    Array#intersect?:  1872932.7 i/s
             Array#?:  1039482.4 i/s - 1.80x  slower

==================================== Case 3 ====================================

ruby 3.2.2 (2023-03-30 revision e51014f9c0) [arm64-darwin21]
Warming up --------------------------------------
             Array#?    37.070k i/100ms
    Array#intersect?    41.438k i/100ms
Calculating -------------------------------------
             Array#?    370.902k (± 0.8%) i/s -      1.891M in   5.097584s
    Array#intersect?    409.902k (± 1.0%) i/s -      2.072M in   5.055185s

Comparison:
    Array#intersect?:   409901.8 i/s
             Array#?:   370902.3 i/s - 1.11x  slower
```
2024-01-05 15:15:30 -05:00
Jean Boussier
27140247c2 Cleanup defined? usage
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.
2024-01-05 15:05:35 +01:00
Jean Boussier
ef65e5fb32 Cleanup usage of ruby2_keywords
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.
2024-01-05 14:40:18 +01:00
Jean Boussier
6ba2fdb2fe Bump the required Ruby version to 3.1.0
Until now, Rails only droped compatibility with older
rubies on new majors, but I propose to change this policy
because it causes us to either keep compatibility with long
EOLed rubies or to bump the Rails major more often, and to
drop multiple Ruby versions at once when we bump the major.

In my opinion it's a bad alignments of incentives. And we'd
be much better to just drop support in new minors whenever they
go EOL (so 3 years).

Also Ruby being an upstream dependency, it's not even
a semver violation AFAICT.

Since Rails 7.2 isn't planned before a few months, we
can already drop Ruby 3.0 as it will be EOL in March.
2023-12-31 08:54:03 +01:00
Jonathan Hefner
b0048c787a Capitalize "Rails" [ci-skip] 2023-12-19 13:16:47 -06:00
Orhan Toy
d86b098a8a Document delegated methods in ActiveModel::Errors
Fixes #50187
2023-11-29 06:38:14 +01:00
Daniel Bernier
417b84a0d1
Fix Dirty#*_was documentation
The docs illustrated `*_change`, not `*_was`, leaving the reader to have to guess.
2023-11-22 14:24:57 -05:00
Rafael Mendonça França
139e806e5c
Remove duplication between alias_attribute_method_definition and define_proxy_call 2023-11-22 17:27:05 +00:00
Rafael Mendonça França
8ec3843cd8
Make duplication explicit between alias_attribute_method_definition and define_proxy_call 2023-11-22 17:20:46 +00:00
Rafael Mendonça França
01492e329f
Extract common logic to build mangled method names to a method 2023-11-22 17:13:28 +00:00
Jonathan Hefner
390db4be35 Use api.rubyonrails.org URLs in README.rdoc [ci-skip]
Follow-up to #49995.

Prior to this commit, `link:classes/...` URLs were used in `README.rdoc`
files so that the URLs would be versioned when rendered at
api.rubyonrails.org.  However, outside of api.rubyonrails.org, such URLs
aren't properly resolved.

Since rails/sdoc#345, `https://api.rubyonrails.org/classes/...` URLs
will also be versioned when rendered at api.rubyonrails.org, and such
URLs are properly resolved everywhere.  Therefore, this commit converts
`link:classes/...` URLs to that form.
2023-11-14 16:35:43 -06:00
Rafael Mendonça França
c6e12729fb
Revert "Port BeforeTypeCast to Active Model" 2023-11-08 14:44:05 -05:00
MaicolBen
19869e765e Don't mark Float::INFINITY as changed when reassigning it
Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>
2023-11-06 08:33:39 -06:00
Jonathan Hefner
cfb72c9d37 Port BeforeTypeCast to Active Model
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>
2023-11-03 21:58:01 -05:00
Jonathan Hefner
83f543b876 Port type_for_attribute to Active Model
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>
2023-11-03 17:24:25 -05:00
Jonathan Hefner
ed2839dd9e
Merge pull request #49836 from skipkayhil/hm-assign-attribute-respond-to
Remove respond_to? in assign_attribute happy path
2023-10-29 15:43:04 -05:00
Hartley McGuire
7a9a537e9d
Remove respond_to? in assign_attribute happy path
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 a225d4bec51778d99ccba5f0d6700dd00d2474f4.
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
```
2023-10-29 14:16:03 -04:00
Jonathan Hefner
34be48ec35 Do not rely on dup in forgetting_assignment optimization
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.
2023-10-28 16:26:48 -05:00
Jean Boussier
c28e4f2434 Use double quotes more consistenly in doc and error messages
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.
2023-10-28 11:38:49 +02:00
Ryuta Kamizono
c3446327df Exercise comparability test with LazyAttributeSet
LazyAttributeSet was added at #39612 for performance reason, it should
not break comparability with AttributeSet, which was added at 9e25e0e.
2023-10-21 07:00:58 +09:00
Dmitry Pogrebnoy
3ed02297a7 Make ==(other) method of AttributeSet safe
The `==(other)` should be able to work with any instances without exception. To do so, we check if the other instance is an AttributeSet.

Fixes#49670
2023-10-19 09:08:52 +02:00
Rafael Mendonça França
3e810adef5
Avoid __method__
Just be explicit about what we are trying to do here.
2023-10-16 13:49:20 +00:00
Rafael Mendonça França
0737765ddf
We don't use :: to denote class methods 2023-10-16 13:44:04 +00:00
Jonathan Hefner
e0a55b038f Use ActiveModel::AttributeRegistration in AR
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.
2023-10-15 16:08:35 -05:00
Jonathan Hefner
31bb0b4aee Support Active Model attribute type decoration
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`.
2023-10-15 15:53:16 -05:00
Nikita Vasilevsky
19f8ab2e7d
[Tests only] Enable Minitest/AssertPredicate rule 2023-10-13 19:26:47 +00:00
John Bampton
130c0c173a test(ruby): fix grammar 2023-10-12 16:29:58 +10:00
Jonathan Hefner
007ea58ec9 Fix typo in method names [ci-skip] 2023-10-07 12:02:22 -05:00
Jean Boussier
02e679ba75 Get rid of the jruby_skip test helper
The last test calling it actually passes on latest
JRuby.
2023-10-02 13:01:44 +02:00
Rafael Mendonça França
fb6c6007d0
Development of Rails 7.2 starts now
🎉
2023-09-27 03:59:11 +00:00
Rafael Mendonça França
e5386cb402
Preparing for 7.1.0.rc1 release 2023-09-27 03:08:31 +00:00
Rafael Mendonça França
acfa045405
Revert typography change in user facing errors
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 9f60cd8dc7d963b1843b66d9639715b4a04c9c65, reversing
changes made to 35d574dbfda68d09fe1fb532f45a3e32f14c571d.
2023-09-26 21:45:03 +00:00
hachi8833
4fb4af7312 [Docs][Tests] Add tests for validates_exclusion_of to 7.1.0beta1 Active Model 2023-09-21 10:33:58 +09:00
Shouichi Kamiya
51ac8b9f6f Enable Minitest/LiteralAsActualArgument
There are assertions that expected/actual arguments are passed in the
reversed order by mistake. Enabling the LiteralAsActualArgument rule
prevents this mistake from happening.

The existing tests were auto-corrected by rubocop with a bit of
indentation adjustment.

Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>
2023-09-13 10:09:32 +09:00