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

时间:2015-07-09 18:25:53

标签: ruby-on-rails ruby activerecord transactions

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

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

我的主要问题是:我的代码应该在哪里写这个交易?我认为它应该在List模型中,但我不确定;如果它应该在List模型中,我不知道 List模型中的位置。我认为它不应该在List控制器中,我发现advice on a comment on Mark Daggett's blog它可以在一个独立的数据访问对象中,但我不知道该怎么做。

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

如果它是相关的,我会在answers to another question之后得到这个问题,但我认为这应该是一个新问题,因为我没有找到类似于交易的类似问题。

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

class List < ActiveRecord::Base
  has_many :quantities
  has_many :foods, :through => :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

得到了相同的结果。

我很感激我对这个问题的任何指示或暗示都源于对一个非常基本的观点的误解(这是基本的,我无法在the docs中找到任何关于它的内容)。谢谢。

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

4 个答案:

答案 0 :(得分:1)

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

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

答案 1 :(得分:0)

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

关于你的第一个问题,有很多关于在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

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

答案 2 :(得分:0)

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

作为参考,this post on John Nunemaker's Railstips帮助我以一种可被列表控制器识别的方式定义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

如果您有任何想法,我将不胜感激。

答案 3 :(得分:0)

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

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

为了让所有这一切都有效,我在def initialize课程中添加了一个WriteList方法,您可能已经假定它在那里,但事实并非如此。我希望这是一件明智的事情(而不是古怪的事情)。

修改

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

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

结束修改

现在它正在为我的目的正常工作;我感谢任何关于组织/结构/建设的进一步反馈。

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

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