新手问题 回调怎么执行了两次?

Catherine · November 30, 2015 · Last by billy replied at November 30, 2015 · 3100 hits

我写了一个回调 after_save :import,上传文件后会创建该文件的一些属性 name, content-type 之类的,import 是为了在创建这些文件属性并保存后执行的方法。是用的一个插件,没法定制方法用 form_for 之类的来接受参数。

:import 方法是用来获取上传的 excel 文件,并解析里面的数据,并且将需要 2 条数据存入数据库 (使用的是 update_attributes)。测试时,excel 里面有 2 条数据。使用的 each 来迭代处理。

上传部分没有错误,导入模块也是单独测试过,没有问题。连接两部分的时候使用回调处理,数据库会在执行第一个 update_attributes 后,执行 # ROLLBACK,然后报错,在 import 方法的第一行地方中断

逻辑:

  • 上传 excel 文件
  • 执行 import 方法 (获取文件,解析文件,使用 update_attributes 更新数据库)

查看了文档,是因为回调里的操作都是在一个事物里,所以我这里,用了 each 迭代两次就不是在一个事务里,第一个 update_attributes 完成后 rails 认为这一个事务完成了,于是该终止执行了,但 each 又开始迭代,所以数据库会终止事务,执行 rollback

所以解决方案是想办法把所有需要修改的数据处理在一个事务里?

    (0.3ms)  UPDATE `select_items` SET `excel_file_name` = 'Excelfile.xls', `updated_at` = '2015-11-30 12:46:01' WHERE `select_items`.`id` = 1
  SelectItem Load (1.9ms)  SELECT `select_items`.* FROM `select_items` WHERE `select_items`.`id` = 1 LIMIT 1
   (2.5ms)  ROLLBACK
Completed 500 Internal Server Error in 102ms

TypeError (can't convert nil into String):
  app/models/select_item.rb:32:in `+'
  app/models/select_item.rb:32:in `import'
  app/models/select_item.rb:47:in `block in import'
  app/models/select_item.rb:36:in `each'
  app/models/select_item.rb:36:in `import'


  Rendered /var/lib/gems/1.9.1/gems/actionpack-3.2.14/lib/action_dispatch/middleware/templates/rescues/_trace.erb (3.1ms)
  Rendered /var/lib/gems/1.9.1/gems/actionpack-3.2.14/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb (1.1ms)
  Rendered /var/lib/gems/1.9.1/gems/actionpack-3.2.14/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (10.2ms)


Started GET "/buyer/select_items/2/edit?select_list_id=1&association=select_items&parent_scaffold=buyer/select_lists&select_list_id=1&adapter=_list_inline_adapter" for 127.0.0.1 at 2015-11-30 12:46:27 +0800
Processing by Buyer::SelectItemsController#edit as JS
  Parameters: {"select_list_id"=>"1", "association"=>"select_items", "parent_scaffold"=>"buyer/select_lists", "adapter"=>"_list_inline_adapter", "id"=>"2"}
Creating scope :seller_admin. Overwriting existing method User.seller_admin.
  User Load (1.6ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
  SelectList Load (0.1ms)  SELECT `select_lists`.* FROM `select_lists` WHERE `select_lists`.`id` = 1 LIMIT 1
  SelectItem Load (0.4ms)  SELECT `select_items`.* FROM `select_items` WHERE `select_items`.`select_list_id` = 1 AND `select_items`.`id` = 2 LIMIT 1
  Rendered /var/lib/gems/1.9.1/gems/active_scaffold-3.3.3/app/views/active_scaffold_overrides/_form_messages.html.erb (0.3ms)
  Rendered /var/lib/gems/1.9.1/gems/active_scaffold-3.3.3/app/views/active_scaffold_overrides/_form.html.erb (8.0ms)
  Rendered /var/lib/gems/1.9.1/gems/active_scaffold-3.3.3/app/views/active_scaffold_overrides/_base_form.html.erb (15.1ms)
  Rendered /var/lib/gems/1.9.1/gems/active_scaffold-3.3.3/app/views/active_scaffold_overrides/_update_form.html.erb (17.8ms)
  Rendered /var/lib/gems/1.9.1/gems/active_scaffold-3.3.3/app/views/active_scaffold_overrides/_messages.html.erb (0.1ms)
  Rendered /var/lib/gems/1.9.1/gems/active_scaffold-3.3.3/app/views/active_scaffold_overrides/_list_inline_adapter.html.erb (2.5ms)
Completed 200 OK in 304ms (Views: 5.4ms | ActiveRecord: 7.3ms | Solr: 0.0ms)


Started PUT "/buyer/select_items/2?association=select_items&iframe=true&parent_scaffold=buyer%2Fselect_lists&select_list_id=1" for 127.0.0.1 at 2015-11-30 12:46:31 +0800
Processing by Buyer::SelectItemsController#update as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"NOQy+DATRkkOoCyrfgsDDkLUq4ey0lvsUzFiGzfOG9g=", "record"=>{"op_platform"=>"op2", "select_location"=>"sl2", "excel"=>#<ActionDispatch::Http::UploadedFile:0xb3e348e8 @original_filename="'Excelfile.xls", @content_type="application/vnd.ms-excel", @headers="Content-Disposition: form-data; name=\"record[excel]\"; filename=\"\xE5\x88\x86\xE6\x8B\xA3\xE5\x8D\x95_20151130095730.xls\"\r\nContent-Type: application/vnd.ms-excel\r\n", @tempfile=#<File:/tmp/RackMultipart20151130-4227-7a78ap>>}, "commit"=>"Update", "association"=>"select_items", "iframe"=>"true", "parent_scaffold"=>"buyer/select_lists", "select_list_id"=>"1", "id"=>"2"}
  User Load (0.4ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
  SelectList Load (0.1ms)  SELECT `select_lists`.* FROM `select_lists` WHERE `select_lists`.`id` = 1 LIMIT 1
  SelectItem Load (0.1ms)  SELECT `select_items`.* FROM `select_items` WHERE `select_items`.`select_list_id` = 1 AND `select_items`.`id` = 2 LIMIT 1
   (0.1ms)  BEGIN
  Order Load (0.1ms)  SELECT `orders`.* FROM `orders` WHERE `orders`.`id` = 1 LIMIT 1
  CACHE (0.0ms)  SELECT `select_lists`.* FROM `select_lists` WHERE `select_lists`.`id` = 1 LIMIT 1
   (0.4ms)  UPDATE `select_items` SET `excel_file_name` = NULL, `updated_at` = '2015-11-30 12:46:31' WHERE `select_items`.`id` = 2
  SelectItem Load (0.3ms)  SELECT `select_items`.* FROM `select_items` WHERE `select_items`.`id` = 1 LIMIT 1
   (2.3ms)  ROLLBACK
Completed 500 Internal Server Error in 176ms

TypeError (can't convert nil into String):
  app/models/select_item.rb:30:in `+'
  app/models/select_item.rb:30:in `import'
  app/models/select_item.rb:44:in `block in import'
  app/models/select_item.rb:33:in `each'
  app/models/select_item.rb:33:in `import'

数据库会在执行第一个update_attributes后,执行 # ROLLBACK,然后报错 那么问题应该就出在 update_attributes 上,可以提供错误日志,然后判断问题原因

#1 楼 @hging 谢谢回复,我添加在原文里了。那个 500 的报错应该是来自我试用的一个插件。之后就开始报错 import 方法的第一行的位置了。

#2 楼 @catherine 额。这个信息有点少。还是放全一点的日志信息吧。

#2 楼 @catherine can't convert nil into String 关键词是这个。你看是不是在import这个方法中,读取到的文件是 nil

#3 楼 @hging 我单独打印了第一句,没有问题的啊。。我再看看哪里出错了

file = (("#{Rails.root}/public/assets/products/#{self.id}/" + self.excel_file_name).to_s)

使用 update_columns 代替 update_attributes 就不会再次触发 after_save 了

update_attributesupdate的别名 update会触发after_save

#7 楼 @zhang_soledad 谢谢!那不是只要在回调里使用.save,.update 成功后,就会无限回调了。。。?

如果你需要再次更新本模型,那么你不应该使用 after_save 的回调。after_save 的常见场景是通知其他部分,比如通知其他 model 更新,更新缓存,发送 queue 等等。

本模型的两个更新动作可以包裹在一个 transaction 里面执行。你这个例子里面第一个动作还包含了 IO,因此除了 ActiveRecord 的常规错误之外你还可以监视 IO 错误来直接 Rollback。

10 Floor has deleted
You need to Sign in before reply, if you don't have an account, please Sign up first.