Commit Graph

2456 Commits

Author SHA1 Message Date
Chris Salzberg
251445601e Rename AttributeMethodMatcher to AttributeMethodPattern 2022-02-09 10:41:02 +09:00
David Heinemeier Hansson
41478f7074 Make #to_fs the default replacement for #to_s(:format)
#to_formatted_s is too cumbersome.
2022-02-07 12:41:21 +01:00
Ryuta Kamizono
ed4d81caf7
Merge pull request #44299 from jonathanhefner/model_name-human-performance
Improve `ActiveModel::Name#human` performance
2022-02-04 07:58:17 +09:00
Chris Salzberg
81519dec1a Use different namespace for proxy calls 2022-02-03 10:10:07 +01:00
Jonathan Hefner
acbc39b663 Improve human_attribute_name performance
This reduces allocations and improves performance by ~35% when a
translation is defined and ~50% when a translation is not defined.

Benchmark script:

```ruby
require "benchmark/memory"
require "benchmark/ips"

class BaseModel
  extend ActiveModel::Translation
end

Person = Class.new(BaseModel)

module A
  Person = Class.new(BaseModel)

  module B
    Person = Class.new(BaseModel)
  end
end

I18n.backend.store_translations "en",
  activemodel: { attributes: { person: { has_translation: "translated" } } }

Benchmark.memory do |x|
  x.report("warmup") do
    Person.human_attribute_name("first_name")
    A::Person.human_attribute_name("first_name")
    A::B::Person.human_attribute_name("first_name")
    Person.human_attribute_name("has_translation")
  end

  x.report("no namespace") { Person.human_attribute_name("first_name") }
  x.report("1 namespace")  { A::Person.human_attribute_name("first_name") }
  x.report("2 namespaces") { A::B::Person.human_attribute_name("first_name") }

  x.report("has translation") { Person.human_attribute_name("has_translation") }
end

Benchmark.ips do |x|
  x.report("no namespace") { Person.human_attribute_name("first_name") }
  x.report("1 namespace")  { A::Person.human_attribute_name("first_name") }
  x.report("2 namespaces") { A::B::Person.human_attribute_name("first_name") }

  x.report("has translation") { Person.human_attribute_name("has_translation") }
end
```

Before:

```
Calculating -------------------------------------
              warmup   988.923k memsize (    24.587k retained)
                         2.441k objects (   339.000  retained)
                        50.000  strings (    50.000  retained)
        no namespace     6.416k memsize (     0.000  retained)
                        58.000  objects (     0.000  retained)
                        18.000  strings (     0.000  retained)
         1 namespace     6.416k memsize (     0.000  retained)
                        58.000  objects (     0.000  retained)
                        18.000  strings (     0.000  retained)
        2 namespaces     6.416k memsize (     0.000  retained)
                        58.000  objects (     0.000  retained)
                        18.000  strings (     0.000  retained)
     has translation     4.501k memsize (     0.000  retained)
                        46.000  objects (     0.000  retained)
                        18.000  strings (     0.000  retained)

Warming up --------------------------------------
        no namespace   567.000  i/100ms
         1 namespace   563.000  i/100ms
        2 namespaces   565.000  i/100ms
     has translation   839.000  i/100ms
Calculating -------------------------------------
        no namespace      5.642k (± 0.9%) i/s -     28.350k in   5.025255s
         1 namespace      5.652k (± 0.9%) i/s -     28.713k in   5.080325s
        2 namespaces      5.662k (± 1.1%) i/s -     28.815k in   5.090226s
     has translation      8.391k (± 1.6%) i/s -     42.789k in   5.100484s
```

After:

```
Calculating -------------------------------------
              warmup   982.803k memsize (    24.587k retained)
                         2.385k objects (   339.000  retained)
                        50.000  strings (    50.000  retained)
        no namespace     4.712k memsize (     0.000  retained)
                        44.000  objects (     0.000  retained)
                        13.000  strings (     0.000  retained)
         1 namespace     4.712k memsize (     0.000  retained)
                        44.000  objects (     0.000  retained)
                        12.000  strings (     0.000  retained)
        2 namespaces     4.712k memsize (     0.000  retained)
                        44.000  objects (     0.000  retained)
                        12.000  strings (     0.000  retained)
     has translation     3.493k memsize (     0.000  retained)
                        32.000  objects (     0.000  retained)
                        11.000  strings (     0.000  retained)

Warming up --------------------------------------
        no namespace   850.000  i/100ms
         1 namespace   846.000  i/100ms
        2 namespaces   842.000  i/100ms
     has translation     1.127k i/100ms
Calculating -------------------------------------
        no namespace      8.389k (± 0.9%) i/s -     42.500k in   5.066296s
         1 namespace      8.412k (± 0.6%) i/s -     42.300k in   5.028401s
        2 namespaces      8.423k (± 0.6%) i/s -     42.942k in   5.098322s
     has translation     11.303k (± 1.1%) i/s -     57.477k in   5.085568s
```
2022-02-02 11:50:29 -06:00
Jonathan Hefner
b2f49f27ad Improve ActiveModel::Name#human performance
This refactors `ActiveModel::Name#human` to reduce allocations and
improve performance by ~2x when a translation is not defined.

Benchmark script:

```ruby
require "benchmark/memory"
require "benchmark/ips"

class BaseModel
  extend ActiveModel::Translation
end

BlogPost = Class.new(BaseModel)

Benchmark.memory do |x|
  x.report("warmup") { BlogPost.model_name.human }
  x.report("human")  { BlogPost.model_name.human }
end

Benchmark.ips do |x|
  x.report("human")  { BlogPost.model_name.human }
end
```

Before:

```
Calculating -------------------------------------
              warmup   964.242k memsize (    23.575k retained)
                         2.157k objects (   310.000  retained)
                        50.000  strings (    50.000  retained)
               human     4.144k memsize (     0.000  retained)
                        32.000  objects (     0.000  retained)
                         6.000  strings (     0.000  retained)

Warming up --------------------------------------
               human   870.000  i/100ms
Calculating -------------------------------------
               human      8.434k (± 0.8%) i/s -     42.630k in   5.054943s
```

After:

```
Calculating -------------------------------------
              warmup   962.418k memsize (    23.607k retained)
                         2.143k objects (   310.000  retained)
                        50.000  strings (    50.000  retained)
               human     2.112k memsize (     0.000  retained)
                        16.000  objects (     0.000  retained)
                         1.000  strings (     0.000  retained)

Warming up --------------------------------------
               human     1.851k i/100ms
Calculating -------------------------------------
               human     18.492k (± 0.7%) i/s -     92.550k in   5.005100s
```
2022-02-02 11:11:56 -06:00
Ryuta Kamizono
65766ebcc8 Bump license years to 2022 [ci-skip] 2022-01-01 15:22:15 +09:00
Orhan Toy
318d6905e2 Add only_numeric option to numericality validator 2021-12-16 22:21:14 +01:00
Rafael Mendonça França
83d85b2207
Start Rails 7.1 development 2021-12-07 15:52:30 +00:00
Rafael Mendonça França
c2e12e0191
Use to_formatted_s(:db) instead of to_s(:db) internally
Ruby 3.1 introduced an optimization to string interpolation for some
core classes in b08dacfea3.

But since we override `to_s` in some of those core classes to add behavior
like `to_s(:db)`, all Rails applications will not be able to take advantage
of that improvement.

Since we can use the `to_formatted_s` alias for the Rails specific behavior
it is best for us to deprecate the `to_s` core extension and allow Rails
applications to get the proformace improvement.

This commit starts removing all the `to_s(:db)` calls inside the framework
so we can deprecate the core extension in the next commit.
2021-12-06 19:22:04 +00:00
Rafael Mendonça França
6f2c4027e8
Use assert_equal 2021-11-29 22:49:39 +00:00
Zoran Pesic
1de55c3596
don't import errors if attempting to merge with self
Prior to this change, attempting to merge an `ActiveModel::Error` instance
with itself would result in an endless loop where `ActiveModel::NestedError`s
would continue to be imported on the instance until interrupted. Though the
merging of identical objects is less likely to happen in practice, this method
should still be able to handle such a case gracefully. This change ensures
that instances attempting to merge on themselves return early rather than
hanging indefinitely.

Addresses https://github.com/rails/rails/issues/43737
2021-11-29 10:17:36 -08:00
Rafael Mendonça França
902e829914
Merge pull request #43284 from mibradev/password-digest-nil
Prevent error when authenticating user with a blank password digest
2021-11-25 13:59:42 -05:00
Rafael Mendonça França
ad96027f4c
Make sure errors.messages works in the same way as Rails 6.1
When there are no errors for a given attribute we were returning empty
arrays. We should continue to do that.
2021-11-25 18:15:27 +00:00
Rafael Mendonça França
50ec25b506
Remove duplicated tests 2021-11-19 23:15:59 +00:00
Rafael Mendonça França
1f3cfb272c
Remove support to Marshal load Rails 5.x ActiveModel::AttributeSet format 2021-11-17 21:51:32 +00:00
Rafael Mendonça França
2996732089
Remove support to Marshal and YAML load Rails 5.x error format 2021-11-17 21:51:31 +00:00
Rafael Mendonça França
70dc990ad2
Remove deprecated support to use []= in ActiveModel::Errors#messages 2021-11-17 21:51:30 +00:00
Rafael Mendonça França
bab78b5d54
Remove deprecated support delete errors from ActiveModel::Errors#messages 2021-11-17 21:51:29 +00:00
Rafael Mendonça França
b2db6f390c
Implement each using a delegator and make sure all enumerable methods are available 2021-11-17 21:51:28 +00:00
Rafael Mendonça França
93edfaa7b4
Remove unnecessary class 2021-11-17 21:51:27 +00:00
Rafael Mendonça França
ef40a92c1c
Remove deprecated support clear errors from ActiveModel::Errors#messages 2021-11-17 21:51:26 +00:00
Rafael Mendonça França
884c97fad0
Remove deprecated support concat errors to ActiveModel::Errors#messages 2021-11-17 21:51:25 +00:00
Rafael Mendonça França
8a5e217b47
Remove unused method 2021-11-17 21:51:24 +00:00
Rafael Mendonça França
73872c7220
Remove deprecated ActiveModel::Errors#to_xml 2021-11-17 21:51:23 +00:00
Rafael Mendonça França
edc4e7dfb5
Remove deprecated ActiveModel::Errors#keys 2021-11-17 21:51:22 +00:00
Rafael Mendonça França
6fed53b694
Remove deprecated ActiveModel::Errors#values 2021-11-17 21:51:21 +00:00
Rafael Mendonça França
362e17e899
Remove deprecated ActiveModel::Errors#slice! 2021-11-17 21:51:20 +00:00
Rafael Mendonça França
a743656ae4
Remove deprecated ActiveModel::Errors#to_h 2021-11-17 21:51:19 +00:00
Rafael Mendonça França
05b18d2694
Remove deprecated enumeration of ActiveModel::Errors instances as a Hash 2021-11-17 21:51:18 +00:00
Rafael Mendonça França
1fde031e89 Fix gemspec 2021-11-15 21:06:21 +00:00
Rafael Mendonça França
9195b7fd0a
Require MFA to release rails 2021-11-15 20:37:42 +00:00
Jean Boussier
bf33510d86 Optimize CurrentAttributes method generation
The bulk of the optimization is to generate code rather than use
`define_method` with a closure.

```
Warming up --------------------------------------
            original   207.468k i/100ms
      code-generator   340.849k i/100ms
Calculating -------------------------------------
            original      2.127M (± 1.1%) i/s -     10.788M in   5.073860s
      code-generator      3.426M (± 0.9%) i/s -     17.383M in   5.073965s

Comparison:
      code-generator:  3426241.0 i/s
            original:  2126539.2 i/s - 1.61x  (± 0.00) slower
```

```ruby

require 'benchmark/ips'
require 'active_support/all'

class Original < ActiveSupport::CurrentAttributes
  attribute :foo
end

class CodeGen < ActiveSupport::CurrentAttributes
  class << self
    def attribute(*names)
      ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |owner|
        names.each do |name|
          owner.define_cached_method(name, namespace: :current_attributes) do |batch|
            batch <<
              "def #{name}" <<
              "attributes[:#{name}]" <<
              "end"
          end
          owner.define_cached_method("#{name}=", namespace: :current_attributes) do |batch|
            batch <<
              "def #{name}=(value)" <<
              "attributes[:#{name}] = value" <<
              "end"
          end
        end
      end

      ActiveSupport::CodeGenerator.batch(singleton_class, __FILE__, __LINE__) do |owner|
        names.each do |name|
          owner.define_cached_method(name, namespace: :current_attributes_delegation) do |batch|
            batch <<
              "def #{name}" <<
              "instance.#{name}" <<
              "end"
          end
          owner.define_cached_method("#{name}=", namespace: :current_attributes_delegation) do |batch|
            batch <<
              "def #{name}=(value)" <<
              "instance.#{name} = value" <<
              "end"
          end
        end
      end
    end
  end
  attribute :foo
end

Benchmark.ips do |x|
  x.report('original') { Original.foo }
  x.report('code-generator') { CodeGen.foo }
  x.compare!
end
```
2021-11-02 15:52:25 +01:00
Rafael Mendonça França
3b5db8e8c9
Merge pull request #43378 from Stellenticket/set_empty_secure_password
clear secure password cache if password is set to `nil`
2021-10-14 17:59:25 -04:00
Markus Doits
9bd186a0e8
clear secure password cache if password is set to nil
```rb
# before:
user.password = 'something'
user.password = nil

user.password # => 'something'

# now:
user.password = 'something'
user.password = nil

user.password # => nil
```
2021-10-10 11:54:35 +02:00
Daniel Colson
26351067c1
Replace more ableist language
Along the same lines as ccb3cb573b, this commit removes unnecessary
references to mental health.

As in that commit, I think many of these are more descriptive than what
we had before.

The commit changes only tests and documentation.
2021-10-07 11:47:28 -04:00
Muhammad Muhammad Ibrahim
d1d4a54c23 Prevent error when authenticating user with a blank password digest
Co-authored-by: Petrik de Heus <petrik@deheus.net>
2021-09-24 09:28:10 +02:00
Jesse van der Pluijm
4862c2faf6 Fix typo: integer numbers (not integral) 2021-09-18 10:21:42 +02:00
Rafael Mendonça França
d177551c30
Preparing for 7.0.0.alpha2 release 2021-09-15 18:22:51 -04:00
Rafael Mendonça França
9b7be48212
Preparing for 7.0.0.alpha1 release 2021-09-15 17:55:08 -04:00
Petrik
c477d95604 Introduce ActiveModel::API
Currently `ActiveModel::Model` is defined as the minimum API to talk
with Action Pack and Action View.
Its name suggests it can be included to create Active Record type
models, but for creating models it's probably too minimal. For example
it's very common to include ActiveModel::Attributes as well.

By moving `ActiveModel::Model`'s implementation to a new
`ActiveModel::API` we keep a definition of the minimum API to talk with
Action Pack and Action View.

For `ActiveModel::Model` we only need to include `ActiveModel::API`.
This will allow adding more funcationality to `ActiveModel::Model` while
keeping backwards compatibility.

Co-authored-by: Nathaniel Watts <1141717+thewatts@users.noreply.github.com>
2021-09-15 18:24:47 +02:00
Guillermo Iguaran
75ea828409 Don't add attribute_names to ActiveModel::Serialization public API 2021-08-28 11:56:21 -07:00
Cody Cutrer
1fbb3ddcd3 avoid calling attributes.keys in ActiveModel#serializable_hash
for ActiveRecord objects, it will force the entire attributes hash to
be constructed, which may include expensive deserialization.
ActiveModel already has a simple attribute_names method defined which
returns attributes.keys, exactly like the code we're replacing.
ActiveRecord overrides that method, and returns the names of the
attributes _without_ having to deserialize any values.

this change can save a lot of CPU if you have a serialized column
on a model that you often load from the DB because it's not worth
the effort to customize the SELECT on every relation, but
also rarely expose in any JSON serializations (i.e. have a default
except option for it).
2021-08-17 18:59:37 -06:00
Rafael Mendonça França
18707ab17f
Standardize nodoc comments 2021-07-29 21:18:07 +00:00
Ryuta Kamizono
0f001f00eb Fix to_json after changes_applied for ActiveModel::Dirty object
Follow up to #41677.

Mutation tracking variables are not only `@mutations_from_database` but
also `@mutations_before_last_save`.
2021-07-24 08:22:18 +09:00
Jonathan Hefner
baa070c3b4
Merge pull request #42839 from jonathanhefner/doc-each_validator
Remove nodoc from EachValidator [ci-skip]
2021-07-23 13:27:35 -05:00
Jean Boussier
8512118f43
Merge pull request #42832 from lulalala/slim-errors-inspect
Slimmer ActiveModel::Errors#inspect message
2021-07-23 09:05:08 +02:00
lulalala
1523838567 Slimmer ActiveModel::Errors#inspect
Only show @errors array and hide @base
2021-07-23 12:29:46 +08:00
Jonathan Hefner
79bb7d0a2b Remove nodoc from EachValidator [ci-skip]
The Active Record Validations guide recommends using `EachValidator`,
and has done so for many years.
2021-07-22 10:35:48 -05:00
Marcelo Lauxen
d1df4c100f
Fix dirty check for Float::NaN and BigDecimal::NaN
Float::NaN and BigDecimal::NaN in Ruby are [special values](https://bugs.ruby-lang.org/issues/1720) and can't be compared with `==`.
2021-07-22 09:49:16 -03:00