簡體   English   中英

ActiveRecord :: Base.transaction應該在哪里?

[英]where should ActiveRecord::Base.transaction be?

我有三種模型:清單,食物和數量。 列表和食物通過has_many:through通過數量關聯。 因此,每個數量具有三個參數:food_id,list_id和數量(整數)。

我的目標是每次創建列表時都創建一個新的數量(與該列表關聯)。 我希望使用事務來執行此操作,以便必須成功創建所有對象,否則將不會創建任何對象。

我的主要問題是:應在我的代碼中哪兒寫此事務? 我認為應該在List模型中,但是我不確定。 如果它應該在列表模型中,我不知道它列表模型中的位置。 我認為它不應該位於List控制器中,我在Mark Daggett的博客的評論中找到了建議,建議它可以位於獨立的數據訪問對象中,但是我不確定該怎么做。

其次:交易本身。 很難說出我的錯誤是在交易中還是在其位置。

如果相關,在回答另一個問題后我會提出這個問題 ,但是我認為這應該是一個新問題,因為我沒有找到專門針對交易的類似問題。

我的列表模型,該事務當前所在的位置:

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

沒有錯誤,但是沒有創建新的Quantity,這使我認為除了至少一件事出錯之外,我還在做正確的事情。

我也試過

List.transaction do

代替

ActiveRecord::Base.transaction do

並得到相同的結果。

我想對這個問題的任何方向或暗示,都是由於我對一個非常基本的觀點的誤解造成的(非常基本,以至於我無法在文檔中找到關於它的任何信息)。 謝謝。

Rails 4.2.3,Cloud9。 開發數據庫= SQLite3,生產數據庫= postgres heroku。

您會收到錯誤,但看不到它們。 如果要查看它們,請致電Quantity.create! 而不是Quantity.create。 (或者您可以分配給一個臨時對象並查看它們,例如:q = Quantity.create(...); q.errors

該錯誤是由於您在類List中進行了兩次驗證(順便說一句壞名)而檢查了日期和名稱。

正如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

如您所見,測試該類非常簡單! 大多數“正確方法”僅憑經驗顯示其真實價值,而且從來沒有獨特,正確的方法來做事,因此,請繼續嘗試不同的技術,直到您掌握解決問題的方法!

我終於使我的交易生效並開始提供服務了! 謝謝ilan和mrodrigues的建議。 創建的新的Quantity邏輯超出了控制器和模型的范圍,我正在接近“所有東西都瘦”的代碼,並且我有很多需要保存的地方。

作為參考, 這篇關於John Nunemaker的Railstips的帖子幫助我以可由Lists控制器識別的方式(通過使用self)定義WriteList方法。

這是我編寫的用於在創建列表時處理數量創建的服務:

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的反饋更新了代碼:

  • 為了清楚起見,請編輯變量和參數的名稱。
  • 添加救援,以便write方法在失敗時返回false。
  • 重新組織代碼,以便write方法(在服務中)可以實例化create方法(在控制器中)內的對象。
  • 重新定位將新列表實際保存到write方法的代碼。

為了使所有這些都能正常工作,我在WriteList類中添加了一個def initialize方法,您可能都假定它在那里,但實際上沒有。 我希望這是明智的選擇(而不是胡扯)。

編輯

在正確的軌道上,我進行了以下更改:

  • 將返回列表添加到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

順便說一句:我希望我發布與以前的答案/問題相比有所改善的答案的做法是適當的; 有人請告訴我是否不是。 我發現它對於跟蹤我的進度和收集更多反饋非常有幫助,希望其他人(尤其是像我這樣的初學者)可以從更新中有所收獲。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM