ActiveRecord::Base.transaction 应该在哪里?
where should ActiveRecord::Base.transaction be?
我有三个模型:列表、食物和数量。 List 和 Food 通过 Quantity via has_many :through 关联。所以每个 Quantity 都有三个参数:food_id、list_id 和 amount(一个整数)。
我的目标是在每次创建列表时创建一个新的数量(与该列表关联)。我希望使用事务来执行此操作,以便必须成功创建所有对象,否则 none 将是。
我的主要问题是:我应该在我的代码中的什么地方写这个交易?我认为它应该在 List 模型中,但我不确定;如果它应该在列表模型中,我不知道它会在 列表模型中的什么位置。我认为它不应该在 List 控制器中,我发现 advice on a comment on Mark Daggett's blog 它可以在一个独立的数据访问对象中,但我不确定该怎么做。
其次:交易本身。很难说我的错误是在交易中还是在交易中。
万一它是相关的,我在 之后提出了这个问题,但我认为这应该是一个新问题,因为我没有找到专门针对交易的类似问题。
我的列表模型,交易当前所在的位置:
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
得到了相同的结果。
我将不胜感激关于这个问题的任何指导或提示,我认为这些指导或提示源于对一个非常基本的观点的误解(如此基本以至于我在 the docs 中找不到任何关于它的信息)。谢谢。
Rails 4.2.3,Cloud9。开发数据库 = SQLite3,生产数据库 = postgres heroku。
您遇到了错误,只是您没有看到它们。如果您想看到他们,请致电 Quantity.create!而不是 Quantity.create。 (或者您可以分配给临时对象并查看它们,例如:q = Quantity.create(...); q.errors
这些错误是因为您在 class 列表(顺便说一句,名称不正确)中进行了 2 次验证,检查了日期和名称。
正如 ilan berci 上面所说,可能存在您没有发现的错误。使用事务时,最好使用 bang 版本的方法保存记录,这样当任何验证未通过时您将得到一个异常,然后事务将被回滚。
关于您的第一个问题,有很多关于在 Rails 项目中放置复杂性的讨论。几年前,座右铭是 "skinny controllers, fat models",这意味着应该提取模型的复杂性,使更难测试且应用程序流程必须显而易见的控制器更具可读性。我更喜欢 "skinny everything" 的哲学:没有绝对的规则,你可以把所有东西放在任何地方,但是一旦它开始变得有点复杂,就把它提取到 class 中,它只负责一个或很少的事情.保持小、简单、经过测试,并在需要时组合它们。
对于您的情况,您可以创建一个服务或用例,然后简单地在控制器中使用它:
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
如您所见,测试这个class非常简单!大多数 "right ways" 只有通过经验才能显示出它们的真正价值,而且从来没有唯一的、正确的做事方法,因此请不断尝试不同的技术,直到您掌握自己的方法来解决问题![=11=]
我终于让我的交易开始工作并进入服务!感谢 ilan 和 mrodrigues 的建议。我的 create new Quantity 逻辑不在控制器和模型中,我正在处理 "skinny everything" 代码,我有刘海需要保存。
作为参考,this post on John Nunemaker's Railstips 帮助我以列表控制器可以识别的方式定义了 WriteList 方法(通过使用 self.)。
这是我编写的用于在创建列表时处理数量创建的服务:
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
方法在失败时 return 为 false。
- 重新组织代码,以便
write
方法(在服务中)可以在 create
方法(在控制器中)中实例化一个对象。
- 将实际保存新列表的代码重新定位到
write
方法。
为了让这一切都起作用,我在 WriteList
class 中添加了一个 def initialize
方法,你们可能都认为它在那里,但实际上并没有。我希望这是明智的(而不是古怪的)做法。
编辑
我在走上正轨后做了这些改变:
- 将 return 列表添加到 write 方法的末尾。
- 而不是拯救到假,使用不保存的参数创建一个新列表;这会导致有关正确读取验证问题的错误消息。
结束编辑
它现在可以正常工作以达到我的目的;对于长期 organization/structure/building 的任何进一步反馈,我表示感谢。
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
顺便说一句:我希望我发布比我以前 answers/question 有所改进的答案的做法是合适的;有人请告诉我是否不是。我发现它对跟踪我的进度和收集更多反馈很有帮助,我希望其他人(尤其是像我这样的初学者)可以从更新中有所收获。
我有三个模型:列表、食物和数量。 List 和 Food 通过 Quantity via has_many :through 关联。所以每个 Quantity 都有三个参数:food_id、list_id 和 amount(一个整数)。
我的目标是在每次创建列表时创建一个新的数量(与该列表关联)。我希望使用事务来执行此操作,以便必须成功创建所有对象,否则 none 将是。
我的主要问题是:我应该在我的代码中的什么地方写这个交易?我认为它应该在 List 模型中,但我不确定;如果它应该在列表模型中,我不知道它会在 列表模型中的什么位置。我认为它不应该在 List 控制器中,我发现 advice on a comment on Mark Daggett's blog 它可以在一个独立的数据访问对象中,但我不确定该怎么做。
其次:交易本身。很难说我的错误是在交易中还是在交易中。
万一它是相关的,我在
我的列表模型,交易当前所在的位置:
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
得到了相同的结果。
我将不胜感激关于这个问题的任何指导或提示,我认为这些指导或提示源于对一个非常基本的观点的误解(如此基本以至于我在 the docs 中找不到任何关于它的信息)。谢谢。
Rails 4.2.3,Cloud9。开发数据库 = SQLite3,生产数据库 = postgres heroku。
您遇到了错误,只是您没有看到它们。如果您想看到他们,请致电 Quantity.create!而不是 Quantity.create。 (或者您可以分配给临时对象并查看它们,例如:q = Quantity.create(...); q.errors
这些错误是因为您在 class 列表(顺便说一句,名称不正确)中进行了 2 次验证,检查了日期和名称。
正如 ilan berci 上面所说,可能存在您没有发现的错误。使用事务时,最好使用 bang 版本的方法保存记录,这样当任何验证未通过时您将得到一个异常,然后事务将被回滚。
关于您的第一个问题,有很多关于在 Rails 项目中放置复杂性的讨论。几年前,座右铭是 "skinny controllers, fat models",这意味着应该提取模型的复杂性,使更难测试且应用程序流程必须显而易见的控制器更具可读性。我更喜欢 "skinny everything" 的哲学:没有绝对的规则,你可以把所有东西放在任何地方,但是一旦它开始变得有点复杂,就把它提取到 class 中,它只负责一个或很少的事情.保持小、简单、经过测试,并在需要时组合它们。
对于您的情况,您可以创建一个服务或用例,然后简单地在控制器中使用它:
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
如您所见,测试这个class非常简单!大多数 "right ways" 只有通过经验才能显示出它们的真正价值,而且从来没有唯一的、正确的做事方法,因此请不断尝试不同的技术,直到您掌握自己的方法来解决问题![=11=]
我终于让我的交易开始工作并进入服务!感谢 ilan 和 mrodrigues 的建议。我的 create new Quantity 逻辑不在控制器和模型中,我正在处理 "skinny everything" 代码,我有刘海需要保存。
作为参考,this post on John Nunemaker's Railstips 帮助我以列表控制器可以识别的方式定义了 WriteList 方法(通过使用 self.)。
这是我编写的用于在创建列表时处理数量创建的服务:
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
方法在失败时 return 为 false。 - 重新组织代码,以便
write
方法(在服务中)可以在create
方法(在控制器中)中实例化一个对象。 - 将实际保存新列表的代码重新定位到
write
方法。
为了让这一切都起作用,我在 WriteList
class 中添加了一个 def initialize
方法,你们可能都认为它在那里,但实际上并没有。我希望这是明智的(而不是古怪的)做法。
编辑
我在走上正轨后做了这些改变:
- 将 return 列表添加到 write 方法的末尾。
- 而不是拯救到假,使用不保存的参数创建一个新列表;这会导致有关正确读取验证问题的错误消息。
结束编辑
它现在可以正常工作以达到我的目的;对于长期 organization/structure/building 的任何进一步反馈,我表示感谢。
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
顺便说一句:我希望我发布比我以前 answers/question 有所改进的答案的做法是合适的;有人请告诉我是否不是。我发现它对跟踪我的进度和收集更多反馈很有帮助,我希望其他人(尤其是像我这样的初学者)可以从更新中有所收获。