2013-03-27 39 views
9

我在控制器中有一个示例操作。RAILS 3 - 控制器中的事务

def some_action 
product = Product.new 
product.name = "namepro" 
    if product.save 
    client.update_attribute(:product_id,product.id) 
    end 
end 

如何为此代码添加事务?我尝试用这个例子代码:

def some_action 
**transaction do** 
    product = Product.new 
    product.name = "namepro" 
    if product.save 
    client.update_attribute(:product_create,Time.now) 
    end 
**end** 
end 

但它会产生这个错误:

undefined method `transaction' 

我读到的控制器使用的交易是一个不好的做法,但我不知道为什么原因(http://markdaggett.com/blog/2011/12/01/transactions-in-rails/

在此示例中,如果产品已创建并保存,并且客户端更新失败...... Rails一定不能做任何事情。

谢谢。

回答

25

如果您确实想要,可以在控制器中使用事务。正如你所指出的,这是不好的做法,但如果你想这样做,只需拨打Product.transaction do而不是transaction dotransactionActiveRecord::Base上的一个类方法,因此您需要在ActiveRecord派生类上调用它。在你的应用程序中的任何模型类都会做(无奈地提醒:如果你连接到不同模型的不同数据库,这可能不是真的......但你可能不这么做)。

这是一种不好的做法,原因是根据MVC范式它没有适当地区分问题。你的控制器不应该如此关心你的数据持久化实现。更好的方法是将方法添加到Product。也许是这样的:

def save_and_update_create_time 
    transaction do 
    if save 
     client.update_attribute(:product_create, Time.now) 
    end 
    end 
end 

然后,而不是在你的控制器调用product.save,叫product.save_and_update_client_create_time。您可能还需要将client也传递给该方法;目前还不清楚你的代码来自何处client。如果它是product上的属性,那么上面的方法应该可以工作。

Railsy还有更好的方法来做到这一点,特别是如果product知道它的client而不需要任何控制器数据。然后,你可以使用一个after_save回调,像这样(添加到Product类):

after_save :update_client 

private 

def update_client(product) 
    product.client.update_attribute(:product_create, Time.now) 
end 

然后每一个Product保存时间,相关的客户端上的字段将被更新。您可能必须先介绍一些代码,以便首先检查是否存在client

除了更干净的代码之外,使用回调的好处在于整个回调链随着保存一起运行在单个事务中;您不需要手动创建交易。您可以在Rails documentation中了解更多关于回调的信息。

+0

非常感谢吉姆,你真的帮我解释一下! – user1364684 2013-04-05 09:12:38

+3

如果将事务逻辑放入模型中,那么交易不会被限制为一个模型那么,为了不破坏单独的关注规则?通常,交易跨越多个模型的可能性很高,这些模型在数据库层面不一定相互关联。 – xSNRG 2014-01-15 16:23:54

+1

是的,我在评论的这个特定方面已经改变了内心。我确实喜欢把它放在控制器外面的想法,但是多模型交互应该被封装在某个地方。也许是另一个阶级,但在某些情况下,控制者毕竟可能是正确的。 – 2014-01-15 17:07:09