新手问题 after_create 后的 save! 有异常后会直接 rollbacke?新手疑问。

QueXuQ · 2012年11月03日 · 最后由 QueXuQ 回复于 2012年11月07日 · 4253 次阅读

描述一下我的需求: 假设场景为: 多张银行卡的金额和我唯一的存折金额是关联的,也就是如果我往银行卡 A 里存 100 元,储存成功后,存折帐号加 100 元。

然后我做这样的设计

class 银行卡
  after_create :存折金额加入

  def 存折金额加入
    存折金额 += 银行卡存入金额
    存折金额.save!
  end
end

这样的写法可以确保存折存入金额后在执行存折金额的调整。 但是如果因为某些问题发生,存折金额存储失败了!也就是存折金额.save!将会抛出异常,一旦异常是不是银行卡的存储也会失败? 也就是一旦存折存储出问题,银行卡的存储也必须返回。不然银行卡里存了钱,却在存折中不显示了。

请问正常的设计思路是这样的吗?

#1 楼 @jjym 这样的吗?那请问是应该怎么设计的呢?

@QueXuQ after_create 这个方法本身,跟 create 这个行为,处于同一个数据库的 Transaction,也就是说,如果 after_create 执行失败,抛出异常的话,整个 create 这个行为的 Transaction 就会 rollback。这是有意这么设计的。具体这里会有详细的说明 http://rails-bestpractices.com/posts/695-use-after_commit

#3 楼 @lgn21st 你发的是关于 after_commit,好像是关于一个执行速度的问题。 其实我不太懂 after_create 什么时候会 rollback。 例如说:存折金额.save!这个要是失败了,自然会有异常,after_create 会不会因为这个异常而 rollback 的?就是只要有一个保存失败,两个都不做保存。

链接中解释了为什么用 after_commit 而不是 after_create 的原因。after_create 抛出异常的话,create 行为会 rollback,什么都不会保存。

这个在 console 下面非常容易佐证,生成一个 scaffold,几行代码,然后你自己在 console 测试一下便知。

#5 楼 @lgn21st 恩。这个知道,关键我想不到怎么样存折金额.save!出问题。不好意思。^_^ 我得看看怎么弄弄好。

#6 楼 @QueXuQ 你这里只需要一个异常而已嘛。

class 银行卡
  after_create :存折金额加入

  def 存折金额加入
    raise "存折金额.save!出问题"
  end
end

#7 楼 @lgn21st 谢谢你的提示。

Started POST "/products" for 127.0.0.1 at 2012-11-03 22:53:04 +0800
Processing by ProductsController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"s0THldTdG2+EO+2fiU3yHH49A6LaYiktf9iBTxJVXRg=", "product"=>{"title"=>"Text", "description"=>"text"}, "commit"=>"Create Product"}
  User Load (0.3ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1
   (0.1ms)  begin transaction
  SQL (9.8ms)  INSERT INTO "products" ("created_at", "description", "title", "updated_at", "user_id") VALUES (?, ?, ?, ?, ?)  [["created_at", Sat, 03 Nov 2012 14:53:04 UTC +00:00], ["description", "text"], ["title", "Text"], ["updated_at", Sat, 03 Nov 2012 14:53:04 UTC +00:00], ["user_id", 1]]
   (0.2ms)  rollback transaction
Completed 500 Internal Server Error in 21ms

RuntimeError ():
  app/models/product.rb:8:in `judge_create'
  app/controllers/products_controller.rb:54:in `block in create'
  app/controllers/products_controller.rb:53:in `create'

确实是会 rollback,我想请问一下,怎么捕获这个异常呢?刚刚试一下:

after_create :judge_create

def judge_create
  begin
    raise
  rescue
    puts "Hello!"
  end
end

如果这样捕获,就会让他顺利存下来了,可是异常是他这里出现的。 怎么样才可以使它

if @product.save
  format.html { redirect_to @product, notice: 'Product was successfully created.' }
  format.json { render json: @product, status: :created, location: @product }
else
  format.html { render action: "new" }
  format.json { render json: @product.errors, status: :unprocessable_entity }
end

运行 else 那段,而不是页面出现异常呢?

create 方法设计成这样,不知道好不好?

def create
  @product = @user.products.build(params[:product])
  begin
    @product.save
    redirect_to @product, notice: 'Product was successfully created.'
  rescue
    render action: "new", notice: 'Product was failure created.'
  end
end

@jjym 很想了解,这个大哥说的好方法是指怎么设计呢?

#9 楼 @QueXuQ 哦,我喜欢另外建个方法,因为业务逻辑不一定是单一的。个人喜好而已。。

#10 楼 @jjym 就是在 controller 里面操作? 我现在就是用 controller 操作,在

银行卡.save
存折金额 += 银行卡.存入金额
存折金额.save

我现在就是都在 controller 里操作的,可是 controller 里怎么 rollback 呢?如果存折金额.save失败了。

这种业务最好是加入一个 Transaction 表,里面记录进账,出帐,时间,等等相关信息,每一笔都有据可查。 而且这种业务性比较强的操作,个人认为最好不要放在 call back 里面。

#12 楼 @pongyo 实际情况是这样的,是一个库存的设计。 已经有入库表,出库表,仓库表了。 操作就是,入库了,入了多少,储存后,通过 callback 保存到仓库表里。 例如今天入了 10 个饼干,我添加入库单信息,保存好后就自动往仓库表里存储了。

所以不可以出现一些错误,一旦有错误整个仓库就乱掉了,而且还不知道怎么乱的。

请问要是这样的情况,应该做则呢没有的设计好呢?不用 callback 用什么比较好?

联系两个的 save 让我很苦恼。

有几个建议:

1, 不要用 ruby 来计算价格,用 SQL 银行卡.where(:id => 银行卡#id).update_all('存折金额 = 存折金额 + 银行卡存入金额')

2, 有 Transaction 表

3, 有 monitor, 定时检查价格对的上对不上

#14 楼 @zhangjinzhu 哦。如果用 SQL 进行计算是不是可以更加确保安全性? Transaction 表和 monitor 指的是什么东西呢?第一次接触,可以给些相关文献看看吗?

用 SQL 防止你的 ruby 对象的数据过期

Transaction 可参考@pongyo 说的

monitor 就是加上个监控,每几分钟跑一下,检查下数据的一致性

#16 楼 @zhangjinzhu 哦。那不怕 SQL 执行会失败吗?就像存折金额.save出错而导致存储失败。 像你说的,update_all 也应该需要一个失败的 rollback 吧? monitor 就是加上个监控,这个好像很复杂,是用 ruby 去写吗?实在没有接触过。

哥们,请问有什么经验之谈吗?要是项目使用途中出问题就麻烦了。 希望给一点建议呢?好像没有想想的简单。。

最近也有个类似的项目,也是一个扣款操作,在金额扣减上直接采用数据库来计算,但涉及到 N 个表的顺序保存,如果任何一个保存出错了,都要回滚。继续留意此帖。

Model.transaction do 账户 1.加钱 账户 2.捡钱 end

#21 楼 @woaigithub 这样子不可以吧。你说的这个方法是需要在同一个 MODEL 下啊。 我这个是存折MODEL银行卡MODEL

Item.transaction do i = Item.lock.first i.name = 'Jones' i.save end

和几个 model 没有关系的

#24 楼 @woaigithub 哦。谢谢。那请问你了解 monitor 监控是在呢么回事吗? 用了事务,还需要进行监控吗?

#19 楼 @ruby_sky 那你目前是怎么处理这个问题的呢?

还没有接触过 monitor

#28 楼 @ruby_sky 哥们,看来我们还是用事务比较踏实吧。

#12 楼 @pongyo #16 楼 @zhangjinzhu 请问 Transaction 表是不是独立开来,不与别的表有关联的呢?

Transaction 就是存款,付款的所有操作的记录

#31 楼 @zhangjinzhu 是不是应该是独立开来的?例如: Transaction:里有 存款金额,而不是存款_id 呢?

需要 登录 后方可回复, 如果你还没有账号请 注册新账号