aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/attribute_methods/dirty.rb
Commit message (Collapse)AuthorAgeFilesLines
* Don't track implicit `touch` mutationRyuta Kamizono2019-05-131-1/+6
| | | | | | | | | | | | | | | This partly reverts the effect of d1107f4d. d1107f4d makes `touch` tracks the mutation whether the `touch` is occurred by explicit or not. Existing apps expects that the previous changes tracks only the changes which is explicit action by users. I'd revert the implicit `touch` mutation tracking since I'd not like to break existing apps. Fixes #36219.
* Avoid redundant `attribute_alias?` before `attribute_alias`Ryuta Kamizono2019-04-241-6/+2
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | If we want to get alias resolved attribute finally, we can use `attribute_alias` directly. For that purpose, avoiding redundant `attribute_alias?` makes alias attribute access 40% faster. https://gist.github.com/kamipo/e427f080a27b46f50bc508fae3612a0e Before (2c0729d8): ``` Warming up -------------------------------------- user['id'] 102.668k i/100ms user['new_id'] 80.660k i/100ms user['name'] 99.368k i/100ms user['new_name'] 81.626k i/100ms Calculating ------------------------------------- user['id'] 1.431M (± 4.0%) i/s - 7.187M in 5.031985s user['new_id'] 1.042M (± 4.2%) i/s - 5.243M in 5.039858s user['name'] 1.406M (± 5.6%) i/s - 7.055M in 5.036743s user['new_name'] 1.074M (± 3.6%) i/s - 5.387M in 5.024152s ``` After (this change): ``` Warming up -------------------------------------- user['id'] 109.775k i/100ms user['new_id'] 103.303k i/100ms user['name'] 105.988k i/100ms user['new_name'] 99.618k i/100ms Calculating ------------------------------------- user['id'] 1.520M (± 6.7%) i/s - 7.574M in 5.011496s user['new_id'] 1.485M (± 6.2%) i/s - 7.438M in 5.036252s user['name'] 1.538M (± 5.4%) i/s - 7.737M in 5.049765s user['new_name'] 1.516M (± 4.6%) i/s - 7.571M in 5.007293s ```
* Avoid method call if `@transaction_state` is not finalizedRyuta Kamizono2019-04-211-2/+2
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Method call in Ruby is a bit slow. This makes attribute access 10% faster by avoiding method call (`sync_with_transaction_state`). Before (96cf7e0e): ``` Warming up -------------------------------------- user.id 131.291k i/100ms user['id'] 91.786k i/100ms user.name 151.605k i/100ms user['name'] 92.664k i/100ms user.changed? 17.772k i/100ms user.saved_changes? 23.909k i/100ms Calculating ------------------------------------- user.id 1.988M (± 7.0%) i/s - 9.978M in 5.051474s user['id'] 1.155M (± 5.8%) i/s - 5.783M in 5.022672s user.name 2.450M (± 4.3%) i/s - 12.280M in 5.021234s user['name'] 1.263M (± 2.1%) i/s - 6.394M in 5.066638s user.changed? 175.070k (±13.3%) i/s - 853.056k in 5.011555s user.saved_changes? 259.114k (±11.8%) i/s - 1.267M in 5.001260s ``` After (this change): ``` Warming up -------------------------------------- user.id 137.625k i/100ms user['id'] 96.054k i/100ms user.name 156.379k i/100ms user['name'] 94.795k i/100ms user.changed? 18.172k i/100ms user.saved_changes? 24.337k i/100ms Calculating ------------------------------------- user.id 2.201M (± 0.5%) i/s - 11.010M in 5.002955s user['id'] 1.320M (± 1.0%) i/s - 6.628M in 5.021293s user.name 2.677M (± 1.6%) i/s - 13.449M in 5.024399s user['name'] 1.314M (± 1.8%) i/s - 6.636M in 5.051444s user.changed? 190.588k (±11.1%) i/s - 944.944k in 5.065848s user.saved_changes? 262.782k (±12.1%) i/s - 1.290M in 5.028080s ```
* Fix dirty tracking after rollback.Ryuta Kamizono2019-04-161-0/+10
| | | | | | | | | | | | | | | | | Currently the rollback only restores primary key value, `new_record?`, `destroyed?`, and `frozen?`. Since the `save` clears current dirty attribute states, retrying save after rollback will causes no change saved if partial writes is enabled (by default). This makes `remember_transaction_record_state` remembers original values then restores dirty attribute states after rollback. Fixes #15018. Fixes #30167. Fixes #33868. Fixes #33443. Closes #33444. Closes #34504.
* Fix dirty tracking for `touch`Ryuta Kamizono2019-04-151-0/+24
| | | | | | | | | | Before this fix, `touch` only clears dirty tracking for touched attributes, doesn't track saved (touched) changes. This fixes that tracks saved changes and carry over remaining changes. Fixes #33429. Closes #34306.
* PERF: 2x ~ 30x faster dirty trackingRyuta Kamizono2019-04-111-8/+6
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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 ```
* Allow aliased attributes in updateGannon McGibbon2018-11-291-3/+7
| | | | Allow aliased attributes to be used in `#update_columns` and `#update`.
* Remove extra `& self.class.column_names` in `keys_for_partial_write`Ryuta Kamizono2018-08-301-6/+6
| | | | | | | It should be done only once in `Persistence` module. https://github.com/rails/rails/blob/c83e30da27eafde79164ecb376e8a28ccc8d841f/activerecord/lib/active_record/persistence.rb#L721 https://github.com/rails/rails/blob/c83e30da27eafde79164ecb376e8a28ccc8d841f/activerecord/lib/active_record/persistence.rb#L740
* Mutation tracker should be cleared before continuing around callbacksYuya Tanaka2018-08-281-5/+6
| | | | | | | | | | | | | | | | | | | | | | | `changes_applied` should be called before continuing around callback chain. Otherwise the mutation tracker returns old value for methods like `changed`? or `id_in_database` in around callbacks. Also methods depend on `id_in_database`, like `update_column`, are not working in `around_create` callbacks. ``` class Foo < ActiveRecord::Base around_create :around_create_callback def around_create_callback ... yield p id_in_database # => nil update_column(:generated_column, generate_value) # silently fails end ... end ```
* `becomes` should clear the mutation tracker which is created in ↵Ryuta Kamizono2018-05-111-1/+1
| | | | | | | | | | | | | | | | `after_initialize` `becomes` creates new object and copies attributes from the receiver. If new object has mutation tracker which is created in `after_initialize`, it should be cleared since it is for discarded attributes. But if the receiver doesn't have mutation tracker yet, it will not be cleared properly. It should be cleared regardless of whether the receiver has mutation tracker or not. Fixes #32867.
* Fix ActiveRecord::AttributeMethods::Dirty documentation [ci skip]Kieran Trezona-le Comte2018-05-071-20/+58
| | | | | | | | | | | | | | | Previously the documentation for the newly introduced (in 5.1) AR::Dirty methods was misleading, as it stated the the new methods were aliases for the old methods. This was false, and caused confusion when the differences in their implementation became apparent. This change attempts to describe the behaviour of these methods more accurately, also noting when they are likely to be useful (i.e. before or after saving a record). This change also makes minor updates to consistently format the documentation of this API, in accordance with the API Documentation Guidelines.
* Avoid generating full changes hash on every saveEugene Kenny2018-04-081-1/+1
| | | | | | | | | | `changed_attribute_names_to_save` is called in `keys_for_partial_write`, which is called on every save when partial writes are enabled. We can avoid generating the full changes hash by asking the mutation tracker for just the names of the changed attributes. At minimum this saves one array allocation per attribute, but will also avoid calling `Attribute#original_value` which is expensive for serialized attributes.
* Revert "PERF: Recover `changes_applied` performance (#31698)"Sean Griffin2018-03-061-2/+4
| | | | This reverts commit a19e91f0fab13cca61acdb1f33e27be2323b9786.
* PERF: Recover `changes_applied` performance (#31698)Ryuta Kamizono2018-01-221-4/+2
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | #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 ```
* Move Attribute and AttributeSet to ActiveModelLisa Ugray2017-11-091-94/+2
| | | | | Use these to back the attributes API. Stop automatically including ActiveModel::Dirty in ActiveModel::Attributes, and make it optional.
* [Active Record] require => require_relativeAkira Matsuda2017-10-211-1/+1
| | | | This basically reverts 9d4f79d3d394edb74fa2192e5d9ad7b09ce50c6d
* activerecord: Remove a redundant mutation trackerDylan Thacker-Smith2017-08-301-23/+9
| | | | | | | The extra mutation tracker was needed in Rails 5.1 to preserve the old behaviour of `changes`, but now there is no difference between `changes` and `changes_to_save`, so `@mutation_tracker` can be removed.
* Use tt in doc for ActiveRecord [ci skip]Yoshiyuki Hirano2017-08-271-11/+11
|
* Don't expose `write_attribute_without_type_cast`Ryuta Kamizono2017-08-161-6/+5
| | | | | | `write_attribute_without_type_cast` is defined as a private method in `AttributeMethods::Write`, but `AttributeMethods::Dirty` overrode it as a public method. It should be kept the original visibility.
* Document `ActiveRecord::AttributeMethods::Dirty`Sean Griffin2017-08-081-9/+9
| | | | | This module has behavior that is not present in `ActiveModel::Dirty`, which is intended to be public API.
* Remove deprecated code concerning non-attributes and `*_will_change!`Sean Griffin2017-07-181-11/+1
|
* Remove deprecated code concerning dirty methods in after callbacksSean Griffin2017-07-181-79/+8
|
* [Action Record] `rubocop -a --only Layout/EmptyLineAfterMagicComment`Koichi ITO2017-07-111-0/+1
|
* Merge pull request #29495 from eugeneius/_write_attributeMatthew Draper2017-07-091-1/+1
|\ | | | | Improve the performance of writing attributes
| * Rename raw_write_attribute to write_attribute_without_type_castEugene Kenny2017-07-071-1/+1
| | | | | | | | | | | | | | | | This name more accurately describes what the method does, and also disambiguates it from `_write_attribute`, which ignores aliases. We can also make the method private, since it's not public API and only called from one place - `update_columns` - without an explicit receiver.
* | [Active Record] require => require_relativeAkira Matsuda2017-07-011-1/+1
|/
* Add option for class_attribute default (#29270)David Heinemeier Hansson2017-05-291-2/+1
| | | | | | | | | | | | * Allow a default value to be declared for class_attribute * Convert to using class_attribute default rather than explicit setter * Removed instance_accessor option by mistake * False is a valid default value * Documentation
* Move around AR::Dirty and fix _attribute methodAaron Patterson2017-04-141-4/+0
| | | | | | 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 inconsistency with changed attributes when overriding AR attribute readerbogdanvlviv2017-04-121-0/+4
|
* Add missing backtick to deprecation messageyuuji.yaginuma2017-03-311-1/+1
|
* Soft-deprecate the top-level HashWithIndifferentAccess classRobin Dupret2017-02-251-3/+3
| | | | | | | Since using a `ActiveSupport::Deprecation::DeprecatedConstantProxy` would prevent people from inheriting this class and extending it from the `ActiveSupport::HashWithIndifferentAccess` one would break the ancestors chain, that's the best option we have here.
* Revert "Merge pull request #27925 from robin850/hwia-removal"Kasper Timm Hansen2017-02-201-3/+3
| | | | | | | | | Pointed out by @matthewd that the HWIA subclass changes the AS scoped class and top-level HWIA hierarchies out from under existing classes. This reverts commit 71da39097b67114329be6d8db7fe6911124531af, reversing changes made to 41c33bd4b2ec3f4a482e6030b6fda15091d81e4a.
* Deprecate the top-level `HashWithIndifferentAccess` contantRobin Dupret2017-02-191-3/+3
| | | | | | | | | This constant was kept for the sake of backward compatibility; it is still available under `ActiveSupport::HashWithIndifferentAccess`. Furthermore, since Ruby 2.5 (https://bugs.ruby-lang.org/issues/11547) won't support top level constant lookup, people would have to update their code anyway.
* Deprecate calling `attr_will_change!` with non-attributesSean Griffin2017-02-111-1/+11
| | | | | | | | | | | | | This was never really intended to work (at least not without calling `define_attribute_methods`, which is less common with Active Record). As we move forward the intention is to require the use of `attribute` over `attr_accessor` for more complex model behavior both on Active Record and Active Model, so this behavior is deprecated. Fixes #27956. Close #27963. [Alex Serban & Sean Griffin]
* Add missing `emit_warning_if_needed` for `changed?`Ryuta Kamizono2016-12-291-0/+5
|
* Correct deprecation warnings in `ActiveRecord::Dirty`Sean Griffin2016-11-301-6/+6
| | | | I had pointed the messages at the new behavior, not the old.
* Deprecate the behavior of AR::Dirty inside of after_(create|update|save) ↵Sean Griffin2016-11-011-6/+173
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | callbacks We pretty frequently get bug reports that "dirty is broken inside of after callbacks". Intuitively they are correct. You'd expect `Model.after_save { puts changed? }; model.save` to do the same thing as `model.save; puts model.changed?`, but it does not. However, changing this goes much farther than just making the behavior more intuitive. There are a _ton_ of places inside of AR that can be drastically simplified with this change. Specifically, autosave associations, timestamps, touch, counter cache, and just about anything else in AR that works with callbacks have code to try to avoid "double save" bugs which we will be able to flat out remove with this change. We introduce two new sets of methods, both with names that are meant to be more explicit than dirty. The first set maintains the old behavior, and their names are meant to center that they are about changes that occurred during the save that just happened. They are equivalent to `previous_changes` when called outside of after callbacks, or once the deprecation cycle moves. The second set is the new behavior. Their names imply that they are talking about changes from the database representation. The fact that this is what we really care about became clear when looking at `BelongsTo.touch_record` when tests were failing. I'm unsure that this set of methods should be in the public API. Outside of after callbacks, they are equivalent to the existing methods on dirty. Dirty itself is not deprecated, nor are the methods inside of it. They will only emit the warning when called inside of after callbacks. The scope of this breakage is pretty large, but the migration path is simple. Given how much this can improve our codebase, and considering that it makes our API more intuitive, I think it's worth doing.
* normalizes indentation and whitespace across the projectXavier Noria2016-08-061-36/+36
|
* applies new string literal convention in activerecord/libXavier Noria2016-08-061-2/+2
| | | | | The current code base is not uniform. After some discussion, we have chosen to go with double quotes by default.
* Add Singleton in NullMutationTracker classacapilleri2015-10-031-1/+1
| | | | to reduce allocation of same object
* Build the `AttributeMutationTracker` lazilySean Griffin2015-10-021-13/+15
| | | | | For reads, we never need to construct this object. The double `defined?` check is to avoid errors in tests
* Fix test failures on MySQLSean Griffin2015-10-021-1/+3
| | | | | There were a few places where I missed a `create` vs `new` before_type_cast check, and the semantics of `reload` became wrong.
* Further encapsulate dirty checking on `Attribute`Sean Griffin2015-10-021-4/+7
| | | | | | | | | | | | | | | | | | | We can skip the allocation of a full `AttributeSet` by changing the semantics of how we structure things. Instead of comparing two separate `AttributeSet` objects, and `Attribute` is now a singly linked list of every change that has happened to it. Since the attribute objects are immutable, to apply the changes we simply need to copy the head of the list. It's worth noting that this causes one subtle change in the behavior of AR. When a record is saved successfully, the `before_type_cast` version of everything will be what was sent to the database. I honestly think these semantics make more sense, as we could have just as easily had the DB do `RETURNING *` and updated the record with those if we had things like timestamps implemented at the DB layer. This brings our performance closer to 4.2, but we're still not quite there.
* Separate `dup` from `deep_dup` in the attributes hashSean Griffin2015-09-281-1/+1
| | | | | | | | | | | I'm looking to move towards a tree-like structure for dirty checking that involves an attribute holding onto the attribute that it was created from. This means that `changed?` can be fully encapsulated on that object. Since the objects are immutable, in `changes_applied`, we can simply perform a shallow dup, instead of a deep one. I'm not sure if that will actually end up in a performance boost, but I'd like to semantically separate these concepts regardless
* We still need to reset `@changed_attributes` in `changes_applied`Sean Griffin2015-09-241-0/+2
| | | | | | When I removed the call to `super` to avoid the setting of `@previous_changes`, I forgot to duplicate the other part of that behavior, which led to failing tests
* Improve the performance of `save` and friendsSean Griffin2015-09-241-2/+10
| | | | | | | | | | | | | | | | | | | | | | 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
* Encapsulate a lot of the logic from `Dirty` in an objectSean Griffin2015-09-241-24/+9
| | | | | | | | | In order to improve the performance of dirty checking, we're going to need to duplicate all of the `previous_` methods in Active Model. However, these methods are basically the same as their non-previous counterparts, but comparing `@original_attributes` to `@previous_original_attributes` instead of `@attributes` and `@original_attributes`. This will help reduce that duplication.
* Clean up the implementation of AR::DirtySean Griffin2015-09-241-78/+39
| | | | | | | | | | | | | This moves a bit more of the logic required for dirty checking into the attribute objects. I had hoped to remove the `with_value_from_database` stuff, but unfortunately just calling `dup` on the attribute objects isn't enough, since the values might contain deeply nested data structures. I think this can be cleaned up further. This makes most dirty checking become lazy, and reduces the number of object allocations and amount of CPU time when assigning a value. This opens the door (but doesn't quite finish) to improving the performance of writes to a place comparable to 4.1
* Don't crash when mutating attributes in a getterSean Griffin2015-06-121-1/+6
| | | | | | | | | | | If a getter has side effects on the DB, `changes_applied` will be called twice. The second time will try and remove the changed attributes cache, and will crash because it's already been unset. This also demonstrates that we shouldn't assume that calling getters won't change the value of `changed_attributes`, and we need to clear the cache if an attribute is modified. Fixes #20531.
* Attribute assignment and type casting has nothing to do with columnsSean Griffin2015-01-311-3/+1
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | It's finally finished!!!!!!! The reason the Attributes API was kept private in 4.2 was due to some publicly visible implementation details. It was previously implemented by overloading `columns` and `columns_hash`, to make them return column objects which were modified with the attribute information. This meant that those methods LIED! We didn't change the database schema. We changed the attribute information on the class. That is wrong! It should be the other way around, where schema loading just calls the attributes API for you. And now it does! Yes, this means that there is nothing that happens in automatic schema loading that you couldn't manually do yourself. (There's still some funky cases where we hit the connection adapter that I need to handle, before we can turn off automatic schema detection entirely.) There were a few weird test failures caused by this that had to be fixed. The main source came from the fact that the attribute methods are now defined in terms of `attribute_names`, which has a clause like `return [] unless table_exists?`. I don't *think* this is an issue, since the only place this caused failures were in a fake adapter which didn't override `table_exists?`. Additionally, there were a few cases where tests were failing because a migration was run, but the model was not reloaded. I'm not sure why these started failing from this change, I might need to clear an additional cache in `reload_schema_from_cache`. Again, since this is not normal usage, and it's expected that `reset_column_information` will be called after the table is modified, I don't think it's a problem. Still, test failures that were unrelated to the change are worrying, and I need to dig into them further. Finally, I spent a lot of time debugging issues with the mutex used in `define_attribute_methods`. I think we can just remove that method entirely, and define the attribute methods *manually* in the call to `define_attribute`, which would simplify the code *tremendously*. Ok. now to make this damn thing public, and work on moving it up to Active Model.