精华帖 (0) :: 良好帖 (1) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2010-02-06
最后修改:2010-02-06
废话不多说,直接上代码。 在out里 has_many :out_items def pick(os) update_attributes(os) out_items.each(&:change_placement) end def out_item_attributes=(out_item_attributes) out_item_attributes.each do |key,value| OutItem.find_by_id(key).update_attributes(value) end end 在out_item里 has_many :out_placements def new_out_placement_attributes=(out_placement_attributes) out_placement_attributes.each do |out_placement_attribute| #out_placements.create(out_placement_attribute) op = out_placements.build(out_placement_attribute) op.save end end def change_placement self.out_placements.each(&:change_placement) end 在执行完out中update_attributes后,out_items.each(&:change_placement),而这时debug代码到change_placement方法中,发现self.out_placements这里是empty array。但是此时,打开console,通过命令,你发现,out_placements不是空的,而且通过数据库也可以看到已经成功的将out_placement更新并且和out_item关联。out_placements明明有,为什么在这里却是empty array呢? 是哪里出问题? 经过一番折腾,猜想是不是在has_manyut_placements中,默认的out_placements使用了缓存?这里的out_placements用的缓存,而没有从数据库里重新查询。如果是这样,应该将缓存更新即可。通过分析代码。发现可能是这里出问题里。 op = out_placements.build(out_placement_attribute) op.save 但是如果我将out_item里的代码改成(注释部分即改变部分)out_placements.create(out_placement_attribute),发现debug到self.out_placements后,out_placements就不是空数组,运行正常了。 成功了。原来使用out_placements.create(out_placement_attribute)这样的操作,不但更新了数据库,而且更新了out_placements缓存,而直接的用op = out_placements.build(out_placement_attribute);op.save,虽然将out_placement保存到了数据库,但是此时再调用out_placements得到的还是之前缓存。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2010-02-22
最后修改:2010-02-22
我觉得,这确实是association的缓存原因,但你的写法也有点问题。
你把out文件的out_item_attributes=方法如下改一下试试: # RAILS_ROOT/app/models/out.rb def out_item_attributes=(out_item_attributes) out_item_attributes.each do |key,value| # OutItem.find_by_id(key).update_attributes(value) # 在association查出来的array中去找,而不是直接find_by_id # 看你这种写法,可能是从前端用params批量传值过来,进行批量提交的,所以key可能是字符串,采用id.to_s比较 oi = out_items.detect {|o| o.id.to_s == key } oi.update_attributes value end end 下面的代码还是用build,应该没问题的。 暂时想到这些,还没测试过,LZ自己试试吧 PS: 你这样直接上代码,而且只是一部分代码,很容易让人迷惑的。我是猜测你用的params批量传值,也不知道对不对……有空写写文字吧。个人觉得代码从来不是程序员的问题。 |
|
返回顶楼 | |
发表时间:2010-02-22
引用 # 看你这种写法,可能是从前端用params批量传值过来,进行批量提交的,所以key可能是字符串,采用id.to_s比较 我很是不同意这点。就算key是字符串怎样?本来他也就是字符串。 你可以在console下试下,A.find('1')。 引用 oi = out_items.detect {|o| o.id.to_s == key } oi.update_attributes value 至于这段代码,我觉得也没有必要这样写。 引用 我是猜测你用的params批量传值,也不知道对不对……有空写写文字吧。个人觉得代码从来不是程序员的问题。 代码也就是实现这个功能。我在前面没有表述,让大家看着受累了。 多写写文字确实是不错,代码只是实现手段。 这点偶吸取教训,多写些文字。 |
|
返回顶楼 | |
发表时间:2010-02-22
刚才仿造你的代码测试了一下,结果和我原先想的有些出入。 我的测试结果是,不管是create还是build,最后out_placements中都是空数组。 我下面会说明我的测试方法,并说明原因。 现在先说说LZ的结论,其实对Rails的association而言,不管是使用create还是build都会相应的改变association中的缓存。要是这点都没想到DHH也可以退休了。 LZ的错误还是在Out#out_item_attributes方法上 def out_item_attributes=(out_item_attributes) out_item_attributes.each do |key,value| # 这里不该用OutItem直接查。 # 因为对这个OutItem查出来的对象(暂称oi)调用update_attributes的话, # 会间接调用oi的new_out_placement_attributes方法,虽然生成了OutPlacements,但更新的是oi.out_placements的缓存。 # 而oi会在这个each循环结束后销毁,自然什么都记不住。实际上这个oi和最外层Out对象的out_items是没有任何关系的。 OutItem.find_by_id(key).update_attributes(value) end end 所以对out对象调用如下方法: out.pick ... 时,out.out_items的缓存根本就没动过,实际上在update_attributes的整个阶段,它都应该没有查询过数据库,缓存自然无从谈起,直到下面一句each。 这时的问题才刚出现,刚才pick方法的第一句update_attributes已经将out_placements写进数据库了(不管是create还是build),这时执行第二句out_items.each时,才进行数据库查询(association只在“需要”的时候才会执行数据库查询,这就不多说了),那查询出来的结果应该是正确的,绝不可能是空数组。 而LZ碰到的是空数组,我想是因为在调用out.pick之前,在其他地方遍历过out.out_items,从而提前查出了数据,放进了out_items的缓存,之后在update_attributes时,又没有更新缓存,才出现的问题。所以不管create还是build,应该都会报空数组的错误的。 要改过来很简单,就照我上一个帖子的代码改。
下面附上本人的测试代码,和一些逻辑。我根据方法命名,猜测LZ做的这个功能是利用update_attributes来做批量数据提交。所以测试逻辑都是围绕这个来写的,如果错了,还请LZ指正。 # RAILS_ROOT/test/unit/out_test.rb require 'test_helper' class OutTest < ActiveSupport::TestCase fixtures :outs, :out_items def setup # 初始数据库环境如下: # outs表中有一条记录 # out_items表中也有一条记录,从属于outs表中的那一条记录 # out_placements表没有记录 @out = outs(:o1) @out_attrs = { :out_item_attrs => { # 更新一个id为1的OutItem对象 "1" => { :title => "out item 1", # 为out_placements写入两条记录 :new_out_placement_attributes => [ {:title => "placement 1"}, {:title => "placement 2"} ] } } } end # 测试1,调用@out.pick,再看之后的数据库记录是不是增加了 test "1" do # 直接执行pick,方法调用顺序如下: # Out#pick -> Out#update_attributes -> Out#out_item_attr= -> OutItem#Update_attributes -> OutItem#new_out_placement_attrs= @out.pick @out_attrs oi1 = @out.out_items[0] # 可以看到,写入了两条记录 assert_equal oi1.out_placements.size, 2 end # 测试2,和测试1唯一不同的就是在调用pick之前,先把@out.out_items的数据读出来,生成缓存了 test "2" do # 这段代码没其他目的,就是提前把out_placements的数据读到内存中来 # 此时out_placements是空数组 @out.out_items.each { |oi| oi.out_placements.each {|op|} } @out.pick @out_attrs oi1 = @out.out_items[0] # 这里因为缓存原因,还是0条记录,其实数据已经写进去了 assert_equal oi1.out_placements.size, 0 # reload之后就显示真实的数字了 assert_equal oi1.reload.out_placements.size, 2 end end 然后是fixtures,很简单,就不解释了 # RAILS_ROOT/test/fixtures/outs.yml # outs表,title属性是随意加的,要不要无所谓 o1: id: 1 title: out # RAILS_ROOT/test/fixtures/out_items.yml # out_items表 oi1: id: 1 title: out item 1 out_id: 1 运行 ruby -I test ./test/unit/out_test.rb |
|
返回顶楼 | |
发表时间:2010-02-22
orcl_zhang 写道 引用 # 看你这种写法,可能是从前端用params批量传值过来,进行批量提交的,所以key可能是字符串,采用id.to_s比较 我很是不同意这点。就算key是字符串怎样?本来他也就是字符串。 你可以在console下试下,A.find('1')。 引用 oi = out_items.detect {|o| o.id.to_s == key } oi.update_attributes value 至于这段代码,我觉得也没有必要这样写。 这点你误会了,使用find时,当然不管是字符串还是数字都可以。 我注明这点只是因为 o.id.to_s == key 这句可能看起来有点奇怪,所以对为什么要把o.id变成字符串来再比较,做的解释。 至于那样改写的原因,只是要利用out_items这个association找出来的对象,而不是OutItem.find,这也是我和你代码的不同,原因我在上面一个帖子里都写明了。 再说一句,console中看到的东西不见得是真相哦。我刚开始学习Rails被它欺骗了好一阵子。所以在console中测试不是最好的办法。 不信你可以分别试试下面两句,其中第一句是不会查询数据库的,但第二句会(其实真实运行环境中都是不会查数据库的,这是console的一点小把戏): out.out_items; nil out.out_items 你可以在console中敲一句,然后在日志中看看生成了SQL语句没有。 最后,如果不明白,你可以找一下irb和inspect方法的文章。 |
|
返回顶楼 | |
发表时间:2010-02-23
最后修改:2010-02-23
out.out_items这个,第一句执行应该是要查询数据库的吧?第二次就不需要了。应该是rails的缓存。应该是类似
def current_user @current_user ||= User.find(session[:user_id]) end 这样的方式实现的。 引用 console中看到的东西不见得是真相哦。
这个还是真不知道。。 out.out_items; nil 这句确实没有查询。暂时还不知道原因。 有空了看下irb和inspect方法的文章。 挺晚了,睡觉。明天继续搞。 |
|
返回顶楼 | |
发表时间:2010-03-12
发现其实在rails里面,在表间关联中很多这样的例子。
self.my_steps.build(:name => name) self.my_steps.each do |mstep| mstep.save(false) end ms = self.my_steps 如果这样写 self.my_steps.create(:name => name) ms = self.my_steps 则返回的ms是不同的。 |
|
返回顶楼 | |
浏览 3325 次