Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
张重言
rails
提交
c820d8d7
R
rails
项目概览
张重言
/
rails
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
R
rails
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
未验证
提交
c820d8d7
编写于
4月 12, 2019
作者:
R
Ryuta Kamizono
提交者:
GitHub
4月 12, 2019
浏览文件
操作
浏览文件
下载
差异文件
Merge pull request #35933 from kamipo/refactor_dirty_tracking
PERF: 2x ~ 30x faster dirty tracking
上级
b86f32bc
6b0a9de9
变更
3
隐藏空白更改
内联
并排
Showing
3 changed file
with
122 addition
and
128 deletion
+122
-128
activemodel/lib/active_model/attribute_mutation_tracker.rb
activemodel/lib/active_model/attribute_mutation_tracker.rb
+88
-29
activemodel/lib/active_model/dirty.rb
activemodel/lib/active_model/dirty.rb
+28
-91
activerecord/lib/active_record/attribute_methods/dirty.rb
activerecord/lib/active_record/attribute_methods/dirty.rb
+6
-8
未找到文件。
activemodel/lib/active_model/attribute_mutation_tracker.rb
浏览文件 @
c820d8d7
# frozen_string_literal: true
require
"active_support/core_ext/hash/indifferent_access"
require
"active_support/core_ext/object/duplicable"
module
ActiveModel
class
AttributeMutationTracker
# :nodoc:
OPTION_NOT_GIVEN
=
Object
.
new
def
initialize
(
attributes
)
def
initialize
(
attributes
,
forced_changes
=
Set
.
new
)
@attributes
=
attributes
@forced_changes
=
Set
.
new
@forced_changes
=
forced_changes
end
def
changed_attribute_names
...
...
@@ -18,24 +19,22 @@ def changed_attribute_names
def
changed_values
attr_names
.
each_with_object
({}.
with_indifferent_access
)
do
|
attr_name
,
result
|
if
changed?
(
attr_name
)
result
[
attr_name
]
=
attributes
[
attr_name
].
original_value
result
[
attr_name
]
=
original_value
(
attr_name
)
end
end
end
def
changes
attr_names
.
each_with_object
({}.
with_indifferent_access
)
do
|
attr_name
,
result
|
change
=
change_to_attribute
(
attr_name
)
if
change
if
change
=
change_to_attribute
(
attr_name
)
result
.
merge!
(
attr_name
=>
change
)
end
end
end
def
change_to_attribute
(
attr_name
)
attr_name
=
attr_name
.
to_s
if
changed?
(
attr_name
)
[
attributes
[
attr_name
].
original_value
,
attributes
.
fetch_value
(
attr_name
)]
[
original_value
(
attr_name
),
fetch_value
(
attr_name
)]
end
end
...
...
@@ -44,29 +43,26 @@ def any_changes?
end
def
changed?
(
attr_name
,
from:
OPTION_NOT_GIVEN
,
to:
OPTION_NOT_GIVEN
)
attr_name
=
attr_name
.
to_s
forced_changes
.
include?
(
attr_name
)
||
attributes
[
attr_name
].
changed?
&&
(
OPTION_NOT_GIVEN
==
from
||
attributes
[
attr_name
].
original_value
==
from
)
&&
(
OPTION_NOT_GIVEN
==
to
||
attributes
[
attr_name
].
value
==
to
)
attribute_changed?
(
attr_name
)
&&
(
OPTION_NOT_GIVEN
==
from
||
original_value
(
attr_name
)
==
from
)
&&
(
OPTION_NOT_GIVEN
==
to
||
fetch_value
(
attr_name
)
==
to
)
end
def
changed_in_place?
(
attr_name
)
attributes
[
attr_name
.
to_s
].
changed_in_place?
attributes
[
attr_name
].
changed_in_place?
end
def
forget_change
(
attr_name
)
attr_name
=
attr_name
.
to_s
attributes
[
attr_name
]
=
attributes
[
attr_name
].
forgetting_assignment
forced_changes
.
delete
(
attr_name
)
end
def
original_value
(
attr_name
)
attributes
[
attr_name
.
to_s
].
original_value
attributes
[
attr_name
].
original_value
end
def
force_change
(
attr_name
)
forced_changes
<<
attr_name
.
to_s
forced_changes
<<
attr_name
end
private
...
...
@@ -75,45 +71,108 @@ def force_change(attr_name)
def
attr_names
attributes
.
keys
end
def
attribute_changed?
(
attr_name
)
forced_changes
.
include?
(
attr_name
)
||
!!
attributes
[
attr_name
].
changed?
end
def
fetch_value
(
attr_name
)
attributes
.
fetch_value
(
attr_name
)
end
end
class
ForcedMutationTracker
<
AttributeMutationTracker
# :nodoc:
def
initialize
(
attributes
,
forced_changes
=
{})
super
@finalized_changes
=
nil
end
def
changed_in_place?
(
attr_name
)
false
end
def
change_to_attribute
(
attr_name
)
if
finalized_changes
&
.
include?
(
attr_name
)
finalized_changes
[
attr_name
].
dup
else
super
end
end
def
forget_change
(
attr_name
)
forced_changes
.
delete
(
attr_name
)
end
def
original_value
(
attr_name
)
if
changed?
(
attr_name
)
forced_changes
[
attr_name
]
else
fetch_value
(
attr_name
)
end
end
def
force_change
(
attr_name
)
forced_changes
[
attr_name
]
=
clone_value
(
attr_name
)
unless
attribute_changed?
(
attr_name
)
end
def
finalize_changes
@finalized_changes
=
changes
end
private
attr_reader
:finalized_changes
def
attr_names
forced_changes
.
keys
end
def
attribute_changed?
(
attr_name
)
forced_changes
.
include?
(
attr_name
)
end
def
fetch_value
(
attr_name
)
attributes
.
send
(
:_read_attribute
,
attr_name
)
end
def
clone_value
(
attr_name
)
value
=
fetch_value
(
attr_name
)
value
.
duplicable?
?
value
.
clone
:
value
rescue
TypeError
,
NoMethodError
value
end
end
class
NullMutationTracker
# :nodoc:
include
Singleton
def
changed_attribute_names
(
*
)
def
changed_attribute_names
[]
end
def
changed_values
(
*
)
def
changed_values
{}
end
def
changes
(
*
)
def
changes
{}
end
def
change_to_attribute
(
attr_name
)
end
def
any_changes?
(
*
)
def
any_changes?
false
end
def
changed?
(
*
)
def
changed?
(
attr_name
,
*
*
)
false
end
def
changed_in_place?
(
*
)
def
changed_in_place?
(
attr_name
)
false
end
def
forget_change
(
*
)
end
def
original_value
(
*
)
end
def
force_change
(
*
)
def
original_value
(
attr_name
)
end
end
end
activemodel/lib/active_model/dirty.rb
浏览文件 @
c820d8d7
# frozen_string_literal: true
require
"active_support/hash_with_indifferent_access"
require
"active_support/core_ext/object/duplicable"
require
"active_model/attribute_mutation_tracker"
module
ActiveModel
...
...
@@ -122,9 +120,6 @@ module Dirty
extend
ActiveSupport
::
Concern
include
ActiveModel
::
AttributeMethods
OPTION_NOT_GIVEN
=
Object
.
new
# :nodoc:
private_constant
:OPTION_NOT_GIVEN
included
do
attribute_method_suffix
"_changed?"
,
"_change"
,
"_will_change!"
,
"_was"
attribute_method_suffix
"_previously_changed?"
,
"_previous_change"
...
...
@@ -145,10 +140,9 @@ def initialize_dup(other) # :nodoc:
# +mutations_from_database+ to +mutations_before_last_save+ respectively.
def
changes_applied
unless
defined?
(
@attributes
)
@previously_changed
=
changes
mutations_from_database
.
finalize_
changes
end
@mutations_before_last_save
=
mutations_from_database
@attributes_changed_by_setter
=
ActiveSupport
::
HashWithIndifferentAccess
.
new
forget_attribute_assignments
@mutations_from_database
=
nil
end
...
...
@@ -159,7 +153,7 @@ def changes_applied
# person.name = 'bob'
# person.changed? # => true
def
changed?
changed_attributes
.
present
?
mutations_from_database
.
any_changes
?
end
# Returns an array with the name of the attributes with unsaved changes.
...
...
@@ -168,42 +162,37 @@ def changed?
# person.name = 'bob'
# person.changed # => ["name"]
def
changed
changed_attributes
.
key
s
mutations_from_database
.
changed_attribute_name
s
end
# Handles <tt>*_changed?</tt> for +method_missing+.
def
attribute_changed?
(
attr
,
from:
OPTION_NOT_GIVEN
,
to:
OPTION_NOT_GIVEN
)
# :nodoc:
!!
changes_include?
(
attr
)
&&
(
to
==
OPTION_NOT_GIVEN
||
to
==
_read_attribute
(
attr
))
&&
(
from
==
OPTION_NOT_GIVEN
||
from
==
changed_attributes
[
attr
])
def
attribute_changed?
(
attr_name
,
**
options
)
# :nodoc:
mutations_from_database
.
changed?
(
attr_name
.
to_s
,
options
)
end
# Handles <tt>*_was</tt> for +method_missing+.
def
attribute_was
(
attr
)
# :nodoc:
attribute_changed?
(
attr
)
?
changed_attributes
[
attr
]
:
_read_attribute
(
attr
)
def
attribute_was
(
attr
_name
)
# :nodoc:
mutations_from_database
.
original_value
(
attr_name
.
to_s
)
end
# Handles <tt>*_previously_changed?</tt> for +method_missing+.
def
attribute_previously_changed?
(
attr
)
#
:nodoc:
previous_changes_include?
(
attr
)
def
attribute_previously_changed?
(
attr
_name
)
#
:nodoc:
mutations_before_last_save
.
changed?
(
attr_name
.
to_s
)
end
# Restore all previous data of the provided attributes.
def
restore_attributes
(
attr
ibut
es
=
changed
)
attr
ibutes
.
each
{
|
attr
|
restore_attribute!
attr
}
def
restore_attributes
(
attr
_nam
es
=
changed
)
attr
_names
.
each
{
|
attr_name
|
restore_attribute!
(
attr_name
)
}
end
# Clears all dirty data: current changes and previous changes.
def
clear_changes_information
@previously_changed
=
ActiveSupport
::
HashWithIndifferentAccess
.
new
@mutations_before_last_save
=
nil
@attributes_changed_by_setter
=
ActiveSupport
::
HashWithIndifferentAccess
.
new
forget_attribute_assignments
@mutations_from_database
=
nil
end
def
clear_attribute_changes
(
attr_names
)
attributes_changed_by_setter
.
except!
(
*
attr_names
)
attr_names
.
each
do
|
attr_name
|
clear_attribute_change
(
attr_name
)
end
...
...
@@ -216,13 +205,7 @@ def clear_attribute_changes(attr_names)
# person.name = 'robert'
# person.changed_attributes # => {"name" => "bob"}
def
changed_attributes
# This should only be set by methods which will call changed_attributes
# multiple times when it is known that the computed value cannot change.
if
defined?
(
@cached_changed_attributes
)
@cached_changed_attributes
else
attributes_changed_by_setter
.
reverse_merge
(
mutations_from_database
.
changed_values
).
freeze
end
mutations_from_database
.
changed_values
end
# Returns a hash of changed attributes indicating their original
...
...
@@ -232,9 +215,7 @@ def changed_attributes
# person.name = 'bob'
# person.changes # => { "name" => ["bill", "bob"] }
def
changes
cache_changed_attributes
do
ActiveSupport
::
HashWithIndifferentAccess
[
changed
.
map
{
|
attr
|
[
attr
,
attribute_change
(
attr
)]
}]
end
mutations_from_database
.
changes
end
# Returns a hash of attributes that were changed before the model was saved.
...
...
@@ -244,27 +225,23 @@ def changes
# person.save
# person.previous_changes # => {"name" => ["bob", "robert"]}
def
previous_changes
@previously_changed
||=
ActiveSupport
::
HashWithIndifferentAccess
.
new
@previously_changed
.
merge
(
mutations_before_last_save
.
changes
)
mutations_before_last_save
.
changes
end
def
attribute_changed_in_place?
(
attr_name
)
# :nodoc:
mutations_from_database
.
changed_in_place?
(
attr_name
)
mutations_from_database
.
changed_in_place?
(
attr_name
.
to_s
)
end
private
def
clear_attribute_change
(
attr_name
)
mutations_from_database
.
forget_change
(
attr_name
)
mutations_from_database
.
forget_change
(
attr_name
.
to_s
)
end
def
mutations_from_database
unless
defined?
(
@mutations_from_database
)
@mutations_from_database
=
nil
end
@mutations_from_database
||=
if
defined?
(
@attributes
)
ActiveModel
::
AttributeMutationTracker
.
new
(
@attributes
)
else
NullMutationTracker
.
instance
ActiveModel
::
ForcedMutationTracker
.
new
(
self
)
end
end
...
...
@@ -276,68 +253,28 @@ def mutations_before_last_save
@mutations_before_last_save
||=
ActiveModel
::
NullMutationTracker
.
instance
end
def
cache_changed_attributes
@cached_changed_attributes
=
changed_attributes
yield
ensure
clear_changed_attributes_cache
end
def
clear_changed_attributes_cache
remove_instance_variable
(
:@cached_changed_attributes
)
if
defined?
(
@cached_changed_attributes
)
end
# Returns +true+ if attr_name is changed, +false+ otherwise.
def
changes_include?
(
attr_name
)
attributes_changed_by_setter
.
include?
(
attr_name
)
||
mutations_from_database
.
changed?
(
attr_name
)
end
alias
attribute_changed_by_setter?
changes_include?
# Returns +true+ if attr_name were changed before the model was saved,
# +false+ otherwise.
def
previous_changes_include?
(
attr_name
)
previous_changes
.
include?
(
attr_name
)
end
# Handles <tt>*_change</tt> for +method_missing+.
def
attribute_change
(
attr
)
[
changed_attributes
[
attr
],
_read_attribute
(
attr
)]
if
attribute_changed?
(
attr
)
def
attribute_change
(
attr
_name
)
mutations_from_database
.
change_to_attribute
(
attr_name
.
to_s
)
end
# Handles <tt>*_previous_change</tt> for +method_missing+.
def
attribute_previous_change
(
attr
)
previous_changes
[
attr
]
def
attribute_previous_change
(
attr
_name
)
mutations_before_last_save
.
change_to_attribute
(
attr_name
.
to_s
)
end
# Handles <tt>*_will_change!</tt> for +method_missing+.
def
attribute_will_change!
(
attr
)
unless
attribute_changed?
(
attr
)
begin
value
=
_read_attribute
(
attr
)
value
=
value
.
duplicable?
?
value
.
clone
:
value
rescue
TypeError
,
NoMethodError
end
set_attribute_was
(
attr
,
value
)
end
mutations_from_database
.
force_change
(
attr
)
def
attribute_will_change!
(
attr_name
)
mutations_from_database
.
force_change
(
attr_name
.
to_s
)
end
# Handles <tt>restore_*!</tt> for +method_missing+.
def
restore_attribute!
(
attr
)
if
attribute_changed?
(
attr
)
__send__
(
"
#{
attr
}
="
,
changed_attributes
[
attr
])
clear_attribute_changes
([
attr
])
def
restore_attribute!
(
attr_name
)
attr_name
=
attr_name
.
to_s
if
attribute_changed?
(
attr_name
)
__send__
(
"
#{
attr_name
}
="
,
attribute_was
(
attr_name
))
clear_attribute_change
(
attr_name
)
end
end
def
attributes_changed_by_setter
@attributes_changed_by_setter
||=
ActiveSupport
::
HashWithIndifferentAccess
.
new
end
# Force an attribute to have a particular "before" value
def
set_attribute_was
(
attr
,
old_value
)
attributes_changed_by_setter
[
attr
]
=
old_value
end
end
end
activerecord/lib/active_record/attribute_methods/dirty.rb
浏览文件 @
c820d8d7
...
...
@@ -29,9 +29,7 @@ module Dirty
# <tt>reload</tt> the record and clears changed attributes.
def
reload
(
*
)
super
.
tap
do
@previously_changed
=
ActiveSupport
::
HashWithIndifferentAccess
.
new
@mutations_before_last_save
=
nil
@attributes_changed_by_setter
=
ActiveSupport
::
HashWithIndifferentAccess
.
new
@mutations_from_database
=
nil
end
end
...
...
@@ -51,7 +49,7 @@ def reload(*)
# +to+ When passed, this method will return false unless the value was
# changed to the given value
def
saved_change_to_attribute?
(
attr_name
,
**
options
)
mutations_before_last_save
.
changed?
(
attr_name
,
**
options
)
mutations_before_last_save
.
changed?
(
attr_name
.
to_s
,
options
)
end
# Returns the change to an attribute during the last save. If the
...
...
@@ -63,7 +61,7 @@ def saved_change_to_attribute?(attr_name, **options)
# invoked as +saved_change_to_name+ instead of
# <tt>saved_change_to_attribute("name")</tt>.
def
saved_change_to_attribute
(
attr_name
)
mutations_before_last_save
.
change_to_attribute
(
attr_name
)
mutations_before_last_save
.
change_to_attribute
(
attr_name
.
to_s
)
end
# Returns the original value of an attribute before the last save.
...
...
@@ -73,7 +71,7 @@ def saved_change_to_attribute(attr_name)
# invoked as +name_before_last_save+ instead of
# <tt>attribute_before_last_save("name")</tt>.
def
attribute_before_last_save
(
attr_name
)
mutations_before_last_save
.
original_value
(
attr_name
)
mutations_before_last_save
.
original_value
(
attr_name
.
to_s
)
end
# Did the last call to +save+ have any changes to change?
...
...
@@ -101,7 +99,7 @@ def saved_changes
# +to+ When passed, this method will return false unless the value will be
# changed to the given value
def
will_save_change_to_attribute?
(
attr_name
,
**
options
)
mutations_from_database
.
changed?
(
attr_name
,
**
options
)
mutations_from_database
.
changed?
(
attr_name
.
to_s
,
options
)
end
# Returns the change to an attribute that will be persisted during the
...
...
@@ -115,7 +113,7 @@ def will_save_change_to_attribute?(attr_name, **options)
# If the attribute will change, the result will be an array containing the
# original value and the new value about to be saved.
def
attribute_change_to_be_saved
(
attr_name
)
mutations_from_database
.
change_to_attribute
(
attr_name
)
mutations_from_database
.
change_to_attribute
(
attr_name
.
to_s
)
end
# Returns the value of an attribute in the database, as opposed to the
...
...
@@ -127,7 +125,7 @@ def attribute_change_to_be_saved(attr_name)
# saved. It can be invoked as +name_in_database+ instead of
# <tt>attribute_in_database("name")</tt>.
def
attribute_in_database
(
attr_name
)
mutations_from_database
.
original_value
(
attr_name
)
mutations_from_database
.
original_value
(
attr_name
.
to_s
)
end
# Will the next call to +save+ have any changes to persist?
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录