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
我想重构上面的代码到传输模式或其他一些地方,因为这个相同的代码在其他地方使用。 然而,ActiveRecord的模型是否内的一些交易魔术,所以我很担心我可能会通过简单的模式下,移动代码引入一些意想不到的副作用。
是我的忧虑毫无根据,以及如何将上面的代码通常被重构到外面的控制器可重用性?
更新:这似乎是对服务对象的理想场所,这里描述http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/ 。
1)当你在你的更新提,这是一个非常适合的服务对象。 把它们像应用程序/服务的目录,因为在/ app什么是自动加载。 有实现它们的两种常用方法:
作为一个静态类:
AccountService.transfer(from_account, to_account, amount)
作为一个对象:
service = AccountService.new(from_account, to_account)
service.transfer(amount)
我宁愿选择一个从Java企业开发的背景,你会使用的Java bean同样的到来。
我也建议所有的服务作为一项规则返回结果的对象。 这意味着你创建一个小型的类名为“ServiceResult”,其中包含呼叫是否是成功的,用户友好的消息的布尔标志和可选的结果对象(这是,如果你没有服务结果对象方法的返回值)。 换句话说检查从控制器或任何其他地方将是结果:
response = AccountService.transfer(from, to, amount)
if response.success?
flash[:notice] = response.message
else
flash[:alert] = response.message
end
你总是可以重构为一个方法是:
flash_service_result response
添加一些辅助方法,以您的服务后,服务方法看起来是这样的:
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
当使用结果对象,永远不会提高从你期望得到处理的服务的异常(检查其他语言的例外)。
2)使用活动记录的交易方法将具有相同的行为无论在哪里,它是从调用。 它不会添加任何副作用。 所以,是的,你可以从一个控制器或服务调用它。