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

Catherine · 2015年11月30日 · 最后由 billy 回复于 2015年11月30日 · 3100 次阅读

我写了一个回调 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 楼 已删除
需要 登录 后方可回复, 如果你还没有账号请 注册新账号