Commit Graph

2289 Commits

Author SHA1 Message Date
Ryuta Kamizono
27a1ca2bfe PERF: 15% faster attribute access
Delegating to just one line method is to not be worth it.
Avoiding the delegation makes `read_attribute` about 15% faster.

```ruby
ActiveRecord::Schema.define do
  create_table :users, force: true do |t|
    t.string :name
  end
end

class User < ActiveRecord::Base
  def fast_read_attribute(attr_name, &block)
    name = attr_name.to_s
    name = self.class.attribute_aliases[name] || name

    name = @primary_key if name == "id" && @primary_key
    @attributes.fetch_value(name, &block)
  end
end

user = User.create!(name: "user name")

Benchmark.ips do |x|
  x.report("read_attribute('id')") { user.read_attribute('id') }
  x.report("read_attribute('name')") { user.read_attribute('name') }
  x.report("fast_read_attribute('id')") { user.fast_read_attribute('id') }
  x.report("fast_read_attribute('name')") { user.fast_read_attribute('name') }
end
```

```
Warming up --------------------------------------
read_attribute('id')   165.744k i/100ms
read_attribute('name')
                       162.229k i/100ms
fast_read_attribute('id')
                       192.543k i/100ms
fast_read_attribute('name')
                       191.209k i/100ms
Calculating -------------------------------------
read_attribute('id')      1.648M (± 1.7%) i/s -      8.287M in   5.030170s
read_attribute('name')
                          1.636M (± 3.9%) i/s -      8.274M in   5.065356s
fast_read_attribute('id')
                          1.918M (± 1.8%) i/s -      9.627M in   5.021271s
fast_read_attribute('name')
                          1.928M (± 0.9%) i/s -      9.752M in   5.058820s
```
2020-06-05 09:12:21 +09:00
Ryuta Kamizono
7834363bbf Avoid redundant to_s in internal attribute API
Redundant `to_s` has a few overhead. Especially private methods are not
intend to be passed user input directly so it should be passed always
string.

Removing redundant `to_s` makes attribute methods about 10% faster.

```ruby
ActiveRecord::Schema.define do
  create_table :users, force: true do |t|
  end
end

class User < ActiveRecord::Base
  def fast_read_attribute(attr_name, &block)
    @attributes.fetch_value(attr_name, &block)
  end
end

user = User.create!

Benchmark.ips do |x|
  x.report("user._read_attribute('id')") { user._read_attribute("id") }
  x.report("user.fast_read_attribute('id')") { user.fast_read_attribute("id") }
end
```

```
Warming up --------------------------------------
user._read_attribute('id')
                       272.151k i/100ms
user.fast_read_attribute('id')
                       283.518k i/100ms
Calculating -------------------------------------
user._read_attribute('id')
                          2.699M (± 1.3%) i/s -     13.608M in   5.042846s
user.fast_read_attribute('id')
                          2.988M (± 1.2%) i/s -     15.026M in   5.029056s
```
2020-06-04 09:02:53 +09:00
Ryuta Kamizono
6f1bf2a3dd Promote clear_attribute_change as attribute methods
For now, `increment` with aliased attribute does work, but `increment!`
with aliased attribute does not work, due to `clear_attribute_change` is
not aware of attribute aliases.

We sometimes partially updates specific attributes in dirties, at that
time it relies on `clear_attribute_change` to clear partially updated
attribute dirties. If `clear_attribute_change` is not attribute method
unlike others, we need to resolve attribute aliases manually only for
`clear_attribute_change`, it is a little inconvinient for me.

From another point of view, we have `restore_attributes`,
`restore_attribute!`, `clear_attribute_changes`, and
`clear_attribute_change`. Despite almost similar features
`restore_attribute!` is an attribute method but `clear_attribute_change`
is not.

Given the above, I'd like to promote `clear_attribute_change` as
attribute methods to fix issues caused by the inconsisteny.
2020-06-04 04:17:17 +09:00
Aaron Patterson
35fe9bc0aa
Merge pull request #39477 from p8/improve-inspect
Make custom inspect methods more consistent
2020-06-03 10:43:35 -07:00
Ryuta Kamizono
4dbbba4a97 Don't call ruby2_keywords for user supplied block 2020-06-04 02:19:30 +09:00
Petrik
74cb9a6f38 Make inspect look more like regular Object#inspect
Move the # outside the < > just like regular Object#inspect
2020-05-29 21:53:35 +02:00
Ryuta Kamizono
c65864cdca Prefer no allocation start/end_with? over String#[] == 2020-05-29 10:20:13 +09:00
Eugene Kenny
7e14c16cc0 Optimise serializable_hash when options are empty
This reverts 8538dfdc084555673d18cfc3479ebef09f325c9c, which broke the
activemodel-serializers-xml gem.

We can still get most of the benefit by applying the optimisation from
7b3919774252f99e55e6b6ec370aafc42adca2b2 to empty hashes as well as nil.
This has the additional benefit of retaining the optimisation when the
user passes an empty options hash.
2020-05-22 00:04:31 +01:00
Eugene Kenny
8538dfdc08 Reduce allocations in to_json's include option
Since 7b3919774252f99e55e6b6ec370aafc42adca2b2, `serializable_hash`
allocates fewer objects when options is nil rather than empty.
2020-05-19 22:00:00 +01:00
Ryuta Kamizono
fba016c7a3 Fix update with dirty locking column to not match latest object accidentally
Related #32163.

We should not identify an object by dirty value. If do so, accidentally
matches latest object even though it is a stale object.
2020-05-18 09:46:09 +09:00
fatkodima
7b39197742 Avoid allocating extra hash and arrays when as_json is called without options
Co-authored-by: Eugene Kenny <elkenny@gmail.com>
2020-05-17 22:54:55 +03:00
eileencodes
6833bf4d10
Remove implementation of unchecked_serialize
Since we're checking `serializable?` in the new `HomogeneousIn`
`serialize` will no longer raise an exception. We implemented
`unchecked_serialize` to avoid raising in these cases, but with some of
our refactoring we no longer need it.

I discovered this while trying to fix a query in our application that
was not properly serializing binary columns. I discovered that in at
least 2 of our active model types we were not calling the correct
serialization. Since `serialize` wasn't aliased to `unchecked_serialize`
in `ActiveModel::Type::Binary` and `ActiveModel::Type::Boolean` (I
didn't check others but pretty sure all the AM Types are broken) the SQL
was being treated as a `String` and not the correct type.

This caused Rails to incorrectly query by string values. This is
problematic for columns storing binary data like our emoji columns at
GitHub. The test added here is an example of how the Binary type was
broken previously. The SQL should be using the hex values, not the
string value of "🥦" or other emoji.

We still have the problem `unchecked_serialize` was supposed to fix -
that `serialize` shouldn't validate data, just convert it. We'll be
fixing that in a followup PR so for now we should use `serialize` so we
know all the values are going through the right serialization for their
SQL.
2020-05-12 13:37:22 -04:00
Akira Matsuda
1edf103b52 undef_method can take varargs 2020-05-08 23:58:01 +09:00
Ryuta Kamizono
12c2f44316 Remove redundant squish for single line message 2020-05-06 15:08:06 +09:00
eileencodes
70ddb8a704
Merge branch 'fix-array-builder-wheres' 2020-05-05 12:57:14 -04:00
Ryuta Kamizono
199e4e96d6 Attributes can be dup-ed 2020-05-06 00:40:48 +09:00
Ryuta Kamizono
776f2abc5a Type::Value#initialize doesn't take positional arguments
Follow up to 257d8f97921a234425817404eab662f590d2d2b3.
2020-05-05 20:40:37 +09:00
Ryuta Kamizono
f93b04afab Deprecate marshalling load from legacy attributes format
Since #31827, marshalling attributes hash format is changed to improve
performance because materializing lazy attribute hash is too expensive.

In that time, we had kept an ability to load from legacy attributes
format, since that performance improvement is backported to 5-1-stable
and 5-0-stable.

Now all supported versions will dump attributes as new format, the
backward compatibity should no longer be needed.
2020-05-02 15:49:09 +09:00
eileencodes
272f2f081a
Use range.member? over range.cover?
Rails has a monkey patch on `range.cover?` that is slower than Ruby's
`range.cover?`. We don't need Range support in this case because the SQL
creates a `BETWEEN` not an `IN` statement.
2020-05-01 15:12:05 -04:00
eileencodes
72fd0bae59
Perf: Improve performance of where when using an array of values
A coworker at GitHub found a few months back that if we used
`santitize_sql` over `where` when we knew the values going into `where`
it was a lot faster than `where`.

This PR adds a new Arel node type called `HomogenousIn` that will be
used when Rails knows the values are all homogenous and can therefore
pick a faster codepath. This new codepath skips some of the required
processing by `where` to make `wheres` with homogenous arrays faster
without requiring the application author to know when to use which query
type.

Using our benchmark code:

```ruby
ids = (1..1000).each.map do |n|
  Post.create!.id
end

Benchmark.ips do |x|
  x.report("where with ids") do
    Post.where(id: ids).to_a
  end

  x.report("where with sanitize") do
    Post.where(ActiveRecord::Base.sanitize_sql(["id IN (?)", ids])).to_a
  end

  x.compare!
end
```

Before this PR comparing where with a list of IDs to santitize sql:

```
Warming up --------------------------------------
      where with ids    11.000  i/100ms
 where with sanitize    17.000  i/100ms

Calculating -------------------------------------
      where with ids    115.733  (± 4.3%) i/s -    583.000  in   5.045828s
 where with sanitize    174.231  (± 4.0%) i/s -    884.000  in   5.081495s

Comparison:
 where with sanitize:      174.2 i/s
      where with ids:      115.7 i/s - 1.51x  slower
```

After this PR comparing where with a list of IDs to santitize sql:

```
Warming up --------------------------------------
      where with ids    16.000  i/100ms
 where with sanitize    19.000  i/100ms

Calculating -------------------------------------
      where with ids    158.293  (± 6.3%) i/s -    800.000  in   5.072208s
 where with sanitize    169.141  (± 3.5%) i/s -    855.000  in   5.060878s

Comparison:
 where with sanitize:      169.1 i/s
      where with ids:      158.3 i/s - same-ish: difference falls within error
```

Co-authored-by: Aaron Patterson <aaron.patterson@gmail.com>
2020-05-01 15:12:05 -04:00
Jean Boussier
d12418a776 Also batch attribute readers and writers 2020-05-01 12:21:16 +02:00
Jean Boussier
9e8bbf6fc9 Batch attribute methods definition in a single module_eval 2020-04-30 18:09:48 +02:00
Adam Hess
096f2d1f83 Reference updated errors attribute names method in deprecation warning 2020-04-22 09:08:42 -07:00
nimish
b3c308dd20 Reject signed hexadecimal numbers while validating numericality 2020-04-22 12:15:57 +00:00
Ryuta Kamizono
6f2126c760 Fixup CHANGELOGs [ci skip] 2020-04-15 21:23:24 +09:00
Rafael Mendonça França
a4deb63798
No need to deprecate Errors#first
This deprecation is useless since the result is still an Error object
and there is no way to fix the code to remove the deprecation.

Let's just accept as a breaking change.
2020-04-13 19:07:59 -04:00
Rafael França
6f6f4e40ab
Merge pull request #36125 from lulalala/doc-for-model-errors
Document update for ActiveModel#errors
2020-04-13 14:11:55 -04:00
Rafael França
46cb94f6ed
Merge pull request #38903 from HParker/add-access-to-attributes-on-errors
Add attribute_names method on errors
2020-04-08 19:18:11 -04:00
Adam Hess
89eb554025 Add attribute_names method on errors
This method replaces the `keys` method on `errors` as a way to get the error attribute names from the errors object without treating `errors` like a hash.
2020-04-08 16:02:24 -07:00
Adam Hess
19ec6233cc Update the Active Model deprecation warning
This message more clearly communicates how to access the attribute and message keys that you would expect to get when using the previous API.

before you might iterate over errors like,

errors.each do |attribute, message|
  # My error code here
end

This message helps the user find the methods on error that match the previous API.
2020-04-08 15:43:38 -07:00
Abhay Nikam
bdfffd1355 Update the Rails mailing list URLs to new discuss discourse URL [ci skip] 2020-04-02 22:00:28 +05:30
Ryuta Kamizono
de8a404751
Merge pull request #38784 from JuanitoFatas/doc/am-absence-validator
Fix a typo in AbsenceValidator

[ci skip]
2020-03-21 22:49:56 +09:00
Juanito Fatas
1c9a427ca9 Fix a typo in AbsenceValidator 2020-03-21 21:24:38 +09:00
Tim Dorr
55fdf173cc
Tiny doc fix in .validates
This should be a string array literal. Simple as that :)
2020-03-17 11:08:50 -04:00
Ryuta Kamizono
1ef8c60dfc Avoid extra string allocation in the methods generated by eval 2020-03-10 17:43:35 +09:00
Ryuta Kamizono
1d3eb7be13 Fixup CHANGELOGs [ci skip] 2020-02-25 14:14:54 +09:00
Rafael França
df186bd16f
Merge pull request #38401 from vinistock/stop_stringifying_during_attribute_assignment
Do not stringify attributes in assign_attributes
2020-02-13 16:54:35 -05:00
aminamos
7bb0706f2c update from PR #36222 2020-02-12 13:31:43 -05:00
Vinicius Stock
2e9e940e22
Change safe guard to check for each_pair instead of stringify_keys 2020-02-07 11:36:35 -05:00
Vinicius Stock
112c3ec0f5
Only dup attributes in activerecord attribute_assignment 2020-02-06 17:02:56 -05:00
Vinicius Stock
8b59960cd4
Do not stringify attributes in assign_attributes 2020-02-06 16:27:24 -05:00
Jean Boussier
1e209d71b0 Save a string allocation for each attribute method call 2020-01-30 20:40:31 +01:00
Ryuta Kamizono
dd77e787a0 Remove unnecessary include ActiveModel::Model in the doc [ci skip] 2020-01-29 11:40:41 +09:00
Ryuta Kamizono
1f08fec27e Generalize FrozenError on write attribute 2020-01-29 11:23:41 +09:00
George Claghorn
a6841c720a Allow checking whether an attribute previously changed from/to a particular value 2020-01-27 16:38:24 -05:00
Ryuta Kamizono
501cb3eb68 Fix warnings for attribute methods with kwargs 2020-01-21 00:34:06 +09:00
Ryuta Kamizono
289d3db332 Defined attribute should not expand positional hash argument 2020-01-21 00:16:02 +09:00
lulalala
bbf839d1a8 Add list of deprecated methods 2020-01-15 00:17:23 +08:00
lulalala
fcd1e41e82 Document on ActiveModel::Errors changes
Mark private constants

Display alternative for deprecation removal warning

Annotate Error's attributes

More emphasis on adding an error instead of message

Rewrite scaffold template using new errors API

Set first and last with behavior change deprecation

Update more doc and example

Add inspect for easier debugging
2020-01-14 23:55:09 +08:00
Gannon McGibbon
f72f743dc3 Add scale support to ActiveRecord::Validations::NumericalityValidator 2020-01-13 11:00:22 -05:00