| Commit message (Collapse) | Author | Age | Files | Lines |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Currently, although using both dirty tracking (ivar backed and
attributes backed) on one model is not supported (doesn't fully work at
least), both dirty tracking are being performed, that is very slow.
As long as attributes backed dirty tracking is used, ivar backed dirty
tracking should not need to be performed.
I've refactored to extract new `ForcedMutationTracker` which only tracks
`force_change` to be performed for ivar backed dirty tracking, that
makes dirty tracking on Active Record 2x ~ 30x faster.
https://gist.github.com/kamipo/971dfe0891f0fe1ec7db8ab31f016435
Before:
```
Warming up --------------------------------------
changed? 4.467k i/100ms
changed 5.134k i/100ms
changes 3.023k i/100ms
changed_attributes 4.358k i/100ms
title_change 3.185k i/100ms
title_was 3.381k i/100ms
Calculating -------------------------------------
changed? 42.197k (±28.5%) i/s - 187.614k in 5.050446s
changed 50.481k (±16.0%) i/s - 246.432k in 5.045759s
changes 30.799k (± 7.2%) i/s - 154.173k in 5.030765s
changed_attributes 51.530k (±14.2%) i/s - 252.764k in 5.041106s
title_change 44.667k (± 9.0%) i/s - 222.950k in 5.040646s
title_was 44.635k (±16.6%) i/s - 216.384k in 5.051098s
```
After:
```
Warming up --------------------------------------
changed? 24.130k i/100ms
changed 13.503k i/100ms
changes 6.511k i/100ms
changed_attributes 9.226k i/100ms
title_change 48.221k i/100ms
title_was 96.060k i/100ms
Calculating -------------------------------------
changed? 245.478k (±16.1%) i/s - 1.182M in 5.015837s
changed 157.641k (± 4.9%) i/s - 796.677k in 5.066734s
changes 70.633k (± 5.7%) i/s - 358.105k in 5.086553s
changed_attributes 95.155k (±13.6%) i/s - 470.526k in 5.082841s
title_change 566.481k (± 3.5%) i/s - 2.845M in 5.028852s
title_was 1.487M (± 3.9%) i/s - 7.493M in 5.046774s
```
|
|
|
|
| |
[ci skip]
|
|
|
|
| |
See https://english.stackexchange.com/questions/23218/anyone-has-or-anyone-have-seen-them
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
See https://github.com/rails/rails/commit/136fc65c9b8b66e1fb56f3a17f0d1fddff9b4bd0#r28897107
I _think_ that this method can now be rewritten from:
```ruby
def attribute_previous_change(attr)
previous_changes[attr] if attribute_previously_changed?(attr)
end
```
to:
```ruby
def attribute_previous_change(attr)
previous_changes[attr]
end
```
without losing performance.
---
Calling
```ruby
previous_changes[attr] if attribute_previously_changed?(attr)
```
is equivalent to calling
```ruby
previous_changes[attr] if previous_changes.include?(attr)
```
When this commit 136fc65c9b was made, Active Record had its own `previous_changes` method, added here below. However, that method has been recently removed from the codebase, so `previous_changes` is now only the method defined in Active Model as:
```ruby
def previous_changes
@previously_changed ||= ActiveSupport::HashWithIndifferentAccess.new
@previously_changed.merge(mutations_before_last_save.changes)
end
```
Since we are dealing with a memoized Hash, there is probably no need to check `if .include?(attr_name)` before trying to fetch `[attr]` for it.
Does that make sense? Did I miss anything? Thanks!
|
|
|
|
|
|
| |
This is an alternate implementation of #31698. That PR makes assumptions
that I do not want in the code base. We can fix the performance
regression with a much simpler patch.
|
|
|
|
| |
This reverts commit a19e91f0fab13cca61acdb1f33e27be2323b9786.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
#30985 caused `object.save` performance regression since calling
`changes` in `changes_applied` is very slow.
We don't need to call the expensive method in `changes_applied` as long
as `@attributes` is tracked by mutation tracker.
https://gist.github.com/kamipo/1a9f4f3891803b914fc72ede98268aa2
Before:
```
Warming up --------------------------------------
create_string_columns
73.000 i/100ms
Calculating -------------------------------------
create_string_columns
722.256 (± 5.8%) i/s - 3.650k in 5.073031s
```
After:
```
Warming up --------------------------------------
create_string_columns
96.000 i/100ms
Calculating -------------------------------------
create_string_columns
950.224 (± 7.7%) i/s - 4.800k in 5.084837s
```
|
| |
|
|
|
|
|
| |
Use these to back the attributes API. Stop automatically including
ActiveModel::Dirty in ActiveModel::Attributes, and make it optional.
|
| |
|
|
|
|
|
| |
This reverts commit 3420a14590c0e6915d8b6c242887f74adb4120f9, reversing
changes made to afb66a5a598ce4ac74ad84b125a5abf046dcf5aa.
|
| |
|
|
|
|
|
|
| |
We already have a _read_attribute method that can get the value we need
from the model. Lets define that method in AM::Dirty and use the
existing one from AR::Dirty rather than introducing a new method.
|
| |
|
| |
|
|
|
| |
Fix potentially misleading example.
|
|
|
|
|
| |
The current code base is not uniform. After some discussion,
we have chosen to go with double quotes by default.
|
| |
|
| |
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
Benchmark results:
Warming up --------------------------------------
old code 32.176k i/100ms
new code 34.837k i/100ms
Calculating -------------------------------------
old code 1.595M (± 3.5%) i/s - 7.947M
new code 1.942M (± 3.9%) i/s - 9.685M
|
|\
| |
| |
| | |
[ci skip] Update dirty.rb: documentation fix.
|
|/
|
| |
ActiveModel::Dirty module documentation fix.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The biggest source of the performance regression in these methods
occurred because dirty tracking required eagerly materializing and type
casting the assigned values. In the previous commits, I've changed dirty
tracking to perform the comparisons lazily. However, all of this is moot
when calling `save`, since `changes_applied` will be called, which just
ends up eagerly materializing everything, anyway. With the new mutation
tracker, it's easy to just compare the previous two hashes in the same
lazy fashion.
We will not have aliasing issues with this setup, which is proven by the
fact that we're able to detect nested mutation.
Before:
User.create! 2.007k (± 7.1%) i/s - 10.098k
After:
User.create! 2.557k (± 3.5%) i/s - 12.789k
Fixes #19859
|
| |
|
|
|
|
|
|
|
|
| |
`ActiveModel::Dirty#[attr_name]_previous_change` to improve access
to recorded changes after the model has been saved.
It makes the dirty-attributes query methods consistent before and after
saving.
|
|\ |
|
| |
| |
| |
| |
| |
| | |
- Grammar fixes
- Add doc for changes_include?
- implemntations => implementations
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| | |
The existing example seems somewhat forced: is it realistic
to have a model that accepts state in its initializer but
considers it has not been changed? By allowing state changes
to happen only via accessors it seems more natural that new
instances are considered to be unchanged (as they are in AR).
[ci skip]
|
| | |
|
| | |
|
| |
| |
| |
| | |
As per Rails general coding conventions. Related to #18794 [ci skip]
|
| |
| |
| |
| |
| |
| |
| | |
is wrong.
Added simple initialize and made use of Person.new instead of Person.find_by to clarify the docs.
[ci skip]
|
|/
|
|
|
|
|
|
|
|
|
| |
When an attribute is assigned, we determine if it was already marked as
changed so we can determine if we need to clear the changes, or mark it
as changed. Since this only affects the `attributes_changed_by_setter`
hash, in-place changes are irrelevant to this process. Since calculating
in-place changes can be expensive, we can just skip it here.
I also added a test for the only edge case I could think of that would
be affected by this change.
|
|
|
|
| |
These requires were added only to change deprecation message
|
|
|
|
| |
`ActiveModel::Dirty#reset_changes`.
|
|\ |
|
| |
| |
| |
| |
| |
| |
| | |
Also prevents the word "Model" from linking to the documentation
of ActiveModel::Model because that's not intended.
[ci skip]
|
|/
|
|
|
|
|
|
|
| |
Calling `changed_attributes` will ultimately check if every mutable
attribute has changed in place. Since this gets called whenever an
attribute is assigned, it's extremely slow. Instead, we can avoid this
calculation until we actually need it.
Fixes #18029
|
|
|
|
|
|
| |
since 'attr_name_will_change!' is not an actual method it should
be clearer that you have to insert the attribute name as in line 104
[ci skip]
|
|
|
|
|
|
|
|
|
| |
ActiveModel::Dirty#clear_attribute_changes method
In Rails 4.2 it is impossible to define a custom default value for a model's
attribute without making it appear as _changed?, especially when the model
is first initialized. Making this method publicly visible will allow such a behaviour,
without the need to use private APIs.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
This patch uniformizes warning messages. I used the most common style
already present in the code base:
* Capitalize the first word.
* End the message with a full stop.
* "Rails 5" instead of "Rails 5.0".
* Backticks for method names and inline code.
Also, converted a few long strings into the new heredoc convention.
|
|
|
|
|
|
|
| |
WARNING: don't use them! They might change or go away between future beta/RC/
patch releases!
Also added a CHANGELOG entry for this.
|
| |
|
|
|
|
|
|
|
| |
Also make it accept a list of attributes to be changed. This will make
possible to restore only a subset of the changed attributes.
Closes #16203
|
|
|
|
|
|
|
|
|
| |
These methods may cause confusion with the `reset_changes` that
behaves differently
of them.
Also rename undo_changes to restore_changes to match this new set of
methods.
|
|
|
|
|
|
|
|
|
| |
#clear_changes_information
This method name is causing confusion with the `reset_#{attribute}`
methods. While `reset_name` set the value of the name attribute for the
previous value the `reset_changes` only discard the changes and previous
changes.
|
|
|
|
| |
To avoid overload with database rollback
|
| |
|
|\
| |
| |
| | |
Added rollback method to ActiveModel::Dirty
|