ActiveRecord :: Base.transaction应该在哪里?

我有三种型号:List,Food和Quantity。 列表和食物通过数量通过has_many:through关联。 所以每个数量都有三个参数:food_id,list_id和amount(整数)。

我的目标是每次创建一个列表时创建一个新的数量(与该列表相关联)。 我希望使用事务执行此操作,以便必须成功创建所有对象,否则将不会。

我的主要问题是:我的代码应该在哪里写这个交易? 我认为它应该在List模型中,但我不确定; 如果它应该在List模型中,我不知道它 List模型中的位置。 我认为它不应该在List控制器中,我在Mark Daggett的博客评论中找到了建议 ,它可能是一个独立的数据访问对象,但我不知道该怎么做。

其次:交易本身。 我很难判断我的错误是在交易中还是在其位置上。

如果它是相关的,我在另一个问题的答案之后得到了这个问题,但我认为这应该是一个新问题,因为我没有找到一个特别关于交易的类似问题。

我的列表模型,当前交易所在的位置:

class List  :quantities before_save { self.name = name.downcase } validates :days, presence: true, :numericality => { :greater_than => 0 } validates :name, length: { maximum: 140 }, uniqueness: { case_sensitive: false } end ActiveRecord::Base.transaction do @list = List.create @a = Food.all.sample(1) Quantity.create(food_id: @a, list_id: @list.id, amount: rand(6)) end 

没有错误,但是新的数量没有被创建,这使我认为除了至少一件事之外,我正在做正确的事情。

我也试过了

 List.transaction do 

代替

 ActiveRecord::Base.transaction do 

并得到了相同的结果。

我希望我对这个问题的任何指导或暗示都源于对一个非常基本的观点的误解(基本上我在文档中找不到任何关于它的内容 )。 谢谢。

Rails 4.2.3,Cloud9。 开发数据库= SQLite3,生产数据库= postgres heroku。

正如ilan berci上面所说的那样,你可能没有看到错误。 使用事务时,最好使用bang版本的方法保存记录,这样当任何validation未通过时,您将获得exception,然后事务将被回滚。

至于你的第一个问题,有很多关于在Rails项目中放置复杂性的讨论。 几年前,座右铭是“瘦的控制器,胖模型”,这意味着应该提取模型的复杂性,使控制器更难以测试,应用程序的流程必须明显,更具可读性。 我更喜欢“瘦一切”的哲学:没有绝对的规则,你可以将所有东西放在任何地方,但是一旦它开始变得有点复杂,就把它提取到一个只负责一件或几件事的类。 保持小巧,简单,经过测试,并在需要时进行组合。

在您的情况下,您可以创建服务或用例,只需在控制器中使用它:

 class CreateList def create! ActiveRecord::Base.transaction do @list = List.create! @food = Food.all.sample(1) Quantity.create!(food: @food, list: @list, amount: rand(6)) end end end 

如您所见,测试此课程非常简单! 大多数“正确的方式”只能通过经验表现出真正的价值,而且从来没有一种独特,正确的做事方式,所以继续尝试不同的技巧,直到你能掌握自己的方法来解决问题!

除了没有看到错误之外,你会收到错误。 如果你想看到它们,那么请调用Quantity.create! 而不是Quantity.create。 (或者你可以分配给一个临时的,看看它们如:q = Quantity.create(…); q.errors

这些错误是因为您在class级列表中的2次validation(顺便说一句坏名称)检查日期和名称。

我终于让我的交易工作并进入服务! 感谢ilan和mrodrigues的建议。 我创建新的数量逻辑是在控制器和模型之外,我正在接近“瘦一切”代码,我有需要保存的刘海。

作为参考, John Nunemaker的Railstips上的这篇文章帮助我以一种可被Lists控制器识别的方式定义WriteList方法(通过使用self)。

这是我写的用于在创建List时处理Quantity的创建的服务:

 class WriteList def self.write!(a) ActiveRecord::Base.transaction do a.save! @a = Food.all.sample.id Quantity.create!(food_id: @a, list_id: a.id, amount: rand(6)) end end end 

这是我的控制器的创建部分:

 def create list = List.new(list_params) if WriteList.write!(list) flash[:success] = "A list has been created!" redirect_to list else render 'new' end end 

如果有什么事情对你不利,我将不胜感激。

我根据mrodrigues的反馈更新了我的代码:

  • 为清晰起见,编辑变量和参数的名称。
  • 添加rescue以使write方法在失败时返回false。
  • 重新组织代码,以便write方法(在服务中)可以在create方法中实例化一个对象(在控制器中)。
  • 将实际保存新列表的代码重新定位到write方法。

为了使这一切能够正常工作,我在WriteList类中添加了一个def initialize方法,你可能已经假定它在那里,但WriteList并非如此。 我希望这是一个明智的(而不是古怪的)事情。

编辑

我走上正轨后做了这些改变:

  • 将返回列表添加到write方法的末尾。
  • 使用不保存的参数创建了一个新列表,而不是将其设置为false; 这会导致有关validation问题的错误消息正确读取。

结束编辑

它现在正适合我的目的; 我感谢任何有关组织/结构/建设的长期反馈。

app/services/write_list.rb

 class WriteList def initialize(params) @params=params end def write ActiveRecord::Base.transaction do food = Food.all.sample.id list = List.new(days: @params[:days], name: @params[:name]) list.save! Quantity.create!(food_id: food, list_id: list[:id], amount: 1+rand(6)) return list end rescue return List.new(days: @params[:days], name: @params[:name]) end end 

app\controllers\lists_controller相关部分:

 def create @list = WriteList.new(list_params).write if @list.save flash[:success] = "A list has been created!" redirect_to @list else render 'new' end end 

顺便说一句:我希望我的回答能够改进以前的答案/问题是合适的; 有人请告诉我,如果不是。 我发现跟踪我的进度和收集更多反馈非常有帮助,我希望其他人(特别是像我这样的初学者)可以从更新中获得一些东西。