Mis*_*hko 5 ruby-on-rails ruby-on-rails-3.2 rails-activerecord
用户可以有很多帖子:
class User < ActiveRecord::Base
has_many :posts
accepts_nested_attributes_for :posts
end
class Post < ActiveRecord::Base
belongs_to :user
end
Run Code Online (Sandbox Code Playgroud)
为什么以下序列不更新第一篇文章?
$ rails c
> user = User.create(name: 'Misha')
=> #<User id: 7, name: "Misha", ... >
> user.posts << Post.create(description: 'hello')
=> #<ActiveRecord::Associations::CollectionProxy [#<Post id: 9, description: "hello", user_id: 7, ... >]>
> post1 = Post.find(9)
> post1.assign_attributes(description: 'world')
> post1
=> #<Post id: 9, description: "world", user_id: 7, ... >
> post2 = Post.new(description: 'new post')
> user.posts = [post1, post2]
> user.posts.second.description
=> "new post" # As expected
> user.posts.first.description
=> "hello" # Why not "world"?
Run Code Online (Sandbox Code Playgroud)
您将保存帖子对象与保存从帖子到用户的关联混为一谈。
就像 @zeantsoi 所说,assign_attributes从不保存它 - 并且查看执行的 SQL,collection=也不会保存任何内容。
> user.posts = [post1, post2]
(0.1ms) begin transaction
SQL (0.7ms) INSERT INTO "posts" ("created_at", "description", "updated_at", "user_id") VALUES (?, ?, ?, ?) [["created_at", Mon, 17 Jun 2013 10:48:13 UTC +00:00], ["des
cription", "p2"], ["updated_at", Mon, 17 Jun 2013 10:48:13 UTC +00:00], ["user_id", 2]]
(22.8ms) commit transaction
=> [#<Post id: 3, description: "p1 modified", user_id: 2, created_at: "2013-06-17 10:46:43", updated_at: "2013-06-17 10:46:43">, #<Post id: 4, description: "p2", user_id:
2, created_at: "2013-06-17 10:48:13", updated_at: "2013-06-17 10:48:13">]
>
Run Code Online (Sandbox Code Playgroud)
post2插入只是因为它必须是为了设置关系;User如果没有办法唯一地标识某个特定的对象,那么该对象就无法知道该特定对象Post属于它Post。
查看 的源代码CollectionAssociation,观察其has_many基础,观察批发替换是如何实现的:
# Replace this collection with +other_array+. This will perform a diff
# and delete/add only records that have changed.
def replace(other_array)
other_array.each { |val| raise_on_type_mismatch!(val) }
original_target = load_target.dup
if owner.new_record?
replace_records(other_array, original_target)
else
transaction { replace_records(other_array, original_target) }
end
end
Run Code Online (Sandbox Code Playgroud)
工作的核心在于replace_records:
def replace_records(new_target, original_target)
delete(target - new_target)
unless concat(new_target - target)
@target = original_target
raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
"new records could not be saved."
end
target
end
Run Code Online (Sandbox Code Playgroud)
换句话说,它删除不在目标列表中的项目,然后添加不在新列表中的项目;结果是post1在集合分配期间根本不会触及目标列表和新列表 ( ) 中的任何项目。
根据上面的代码,target传入参数的 as 就是返回的内容,这似乎反映了更改:
=> [#<Post id: 3, description: "p1 modified", user_id: 2, created_at: "2013-06-17 10:46:43", updated_at: "2013-06-17 10:46:43">, #<Post id: 4, description: "p2", user_id:
2, created_at: "2013-06-17 10:48:13", updated_at: "2013-06-17 10:48:13">]
Run Code Online (Sandbox Code Playgroud)
但在重新访问集合时,更改并未反映出来:
> post1
=> #<Post id: 3, description: "p1 modified", user_id: 2, created_at: "2013-06-17 10:46:43", updated_at: "2013-06-17 10:46:43">
> user.posts
=> #<ActiveRecord::Associations::CollectionProxy [#<Post id: 3, description: "p1", user_id: 2, created_at: "2013-06-17 10:46:43", updated_at: "2013-06-17 10:46:43">, #<Pos
t id: 4, description: "p2", user_id: 2, created_at: "2013-06-17 10:48:13", updated_at: "2013-06-17 10:48:13">]>
>
Run Code Online (Sandbox Code Playgroud)
请注意,这里的返回略有不同;赋值的返回值是您传入的数组对象;这是一个ActiveRecord::Associations::CollectionProxy. 该reader函数在这里调用:
# Implements the reader method, e.g. foo.items for Foo.has_many :items
def reader(force_reload = false)
if force_reload
klass.uncached { reload }
elsif stale_target?
reload
end
@proxy ||= CollectionProxy.new(klass, self)
end
Run Code Online (Sandbox Code Playgroud)
然后,根据 has_many 关系创建集合代理,其值是根据我们分配选项时所知道的内容填充的。这个答案唯一未被发现的部分是为什么生成的对象被清除了脏值——我已经阅读了一些代码,并且认为使用调试器来回答是最简单的,但我没有心情为了。:) 但很明显,它要么从缓存加载,要么传入的对象的更改被丢弃。
不管怎样,如果你想让改变出现在目标对象中,你应该先保存它——仅仅分配集合是不够的,就好像它已经是一个成员一样,它不会被触及。
更新:有趣的是,这种情况发生只是因为我们用来Post.find获取post1; 如果我们改为说post1 = (user.posts << Post.create(description: 'p1')),则最后观察到的集合user.posts实际上具有脏对象。
这首先揭示了它是如何存在的。观看object_id:
>
u = User.create; p1 = (u.posts << Post.create(description: 'p1'))[0]; p1.assign_attributes(description: 'p1 mod'); p2 = Post.new(description: 'p2'); u.posts = [p1, p2]; u.posts
...
=> #<ActiveRecord::Associations::CollectionProxy [#<Post id: 21, description: "p1 mod", user_id: 10, created_at: "2013-06-17 11:43:30", updated_at: "2013-06-17 11:43:30">, #<Post id: 22, description: "p2", user_id: 10, created_at: "2013-06-17 11:43:30", updated_at: "2013-06-17 11:43:30">]>
> _[0].object_id
=> 70160940234280
> p1.object_id
=> 70160940234280
>
Run Code Online (Sandbox Code Playgroud)
请注意,集合代理中返回的对象与我们创建的对象相同。如果我们重新find定义:
> u = User.create; u.posts << Post.create(description: 'p1'); p1 = Post.find(u.posts.first.id); p1.assign_attributes(description: 'p1 mod'); p2 = Post.new(description: 'p2'); u.posts = [p1, p2]; u.posts
...=> #<ActiveRecord::Associations::CollectionProxy [#<Post id: 23, description: "p1", user_id: 11, created_at: "2013-06-17 11:43:47", updated_at: "2013-06-17 11:43:47">, #<Post id: 24, description: "p2", user_id: 11, created_at: "2013-06-17 11:43:47", updated_at: "2013-06-17 11:43:47">]>
> _[0].object_id
=> 70264436302820
> p1.object_id
=> 70264441827000
>
Run Code Online (Sandbox Code Playgroud)
最初问题中让我困惑的部分是没有脏数据的对象来自哪里;没有发生 SQL,甚至没有缓存命中,所以它必须来自某个地方。我原以为它要么是其他缓存源,要么它明确地获取给定的对象并清理它们。
上面的内容清楚地表明,缓存实际上是Post我们在插入时创建的。为了百分百确定,让我们看看返回的值是否Post与创建的值相同:
> u = User.create; p0 = (u.posts << Post.create(description: 'p1'))[0]; p1 = Post.find(u.posts.first.id); p1.assign_attributes(description: 'p1 mod'); p2 = Post.new(description: 'p2'); u.posts = [p1, p2]; u.posts
...
=> #<ActiveRecord::Associations::CollectionProxy [#<Post id: 27, description: "p1", user_id: 13, created_at: "2013-06-17 12:01:05", updated_at: "2013-06-17 12:01:05">, #<Post id: 28, description: "p2", user_id: 13, created_at: "2013-06-17 12:01:07", updated_at: "2013-06-17 12:01:07">]>
> _[0].object_id
=> 70306779571100
> p0.object_id
=> 70306779571100
> p1.object_id
=> 70306779727620
>
Run Code Online (Sandbox Code Playgroud)
因此,其中不反映更改的对象CollectionProxy实际上与我们最初追加到集合时创建的对象相同;这解释了缓存数据的来源。然后我们排列一个副本,该副本不会反映收集后分配。
| 归档时间: |
|
| 查看次数: |
4759 次 |
| 最近记录: |