将事务操作从控制器移开 [英] Moving transactional operations away from the controller

查看:53
本文介绍了将事务操作从控制器移开的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

  def cancel

    begin
      to_bank = @transfer.main_to_bank
      to_bank.with_lock do
        to_bank.locked_balance -= @transfer.amount
        to_bank.available_balance += @transfer.amount
        to_bank.save!
        @transfer.cancel
        @transfer.save!
      end
    rescue ActiveRecord::ActiveRecordError => e
      redirect_to admin_transfer_url(@transfer), alert: "Error while cancelling."
      return
    end

    redirect_to admin_transfer_url(@transfer), notice: 'Transfer was successfully cancelled.'
  end

我想将上面的代码重构到 Transfer 模型或其他地方,因为在其他地方使用了相同的代码.然而,ActiveRecord 在模型中做了一些事务魔法,所以我担心我可能会通过简单地移动模型下的代码来引入一些意想不到的副作用.

I would want to refactor the above code to the Transfer model or some other place, because this same code is used elsewhere. However, ActiveRecord does some transaction magic within the model, so I'm worried I might introduce some unexpected side effects by simply moving the code under the model.

我的担心是不是毫无根据?通常如何将上述代码重构到控制器之外以实现可重用性?

Are my worries unfounded and how would the above code typically be refactored to be outside of the controller for reusability?

更新:这似乎是服务对象的完美位置,如下所述 http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/.

Update: This seems like the perfect spot for a service object, as described here http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/.

推荐答案

1) 正如您在更新中提到的,这非常适合服务对象.将它们放在 app/services 之类的目录中,因为/app 中的任何内容都是自动加载的.有两种流行的实施方式:

1) As you mention in your update, this is a perfect fit for service objects. Put them in a directory like app/services since anything in /app is autoloaded. There are two popular ways of implementing them:

作为静态类:

AccountService.transfer(from_account, to_account, amount)

作为对象:

service = AccountService.new(from_account, to_account)
service.transfer(amount)

我更喜欢来自 Java 企业开发背景的选项一,您将类似地使用 Java bean.

I prefer option one coming from a Java enterprise development background where you would use Java beans similarly.

作为一项规则,我还建议从所有服务返回结果对象.这意味着您创建了一个名为ServiceResult"的小类,它包含一个布尔标志,表示调用是否成功,一条用户友好的消息和一个可选的结果对象(如果没有服务结果对象,这是方法返回值).换句话说,检查控制器或任何其他地方的结果将是:

I would also recommend returning result objects from all services as a rule. This means you create a small class called "ServiceResult" which contains a boolean flag of whether or not the call was successful, a user friendly message and optionally a result object (which is the method return value if you didn't have service result objects). In other words checking the result from the controller or any other place would be:

response = AccountService.transfer(from, to, amount)

if response.success?
  flash[:notice] = response.message
else
  flash[:alert] = response.message
end

您始终可以将其重构为一个方法:

You can always refactor this into a method:

flash_service_result response

向您的服务添加一些辅助方法后,服务方法可能如下所示:

After adding some helper methods to your services, a service method could look like this:

def self.transfer(from_account, to_account, amount)

   ActiveRecord::Base.transaction do
     ..do stuff..
     from_account.save!
     to_account.save!

     service_success("Transfer succesfull...")
   end

 rescue SomeException => error
   service_error("Failed to transfer...")

 rescue ActiveRecord::RecordInvalid => invalid_record_error
   service_validation_error(invalid_record_error)
 end

使用结果对象时,切勿从您希望处理的服务中引发异常(其他语言中的检查异常).

When using result objects, never raise an exception from the service that you expect to be handled (checked exceptions in other languages).

2) 无论从何处调用,使用活动记录事务方法的行为都相同.它不会增加任何副作用.所以是的,您可以从控制器或服务调用它.

2) Using the active record transactional methods will behave the same regardless of where it's called from. It will not add any side effects. So yes you can call it from a controller or service.

这篇关于将事务操作从控制器移开的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆