为了瘦小的控制器,轨道型号是否应该关注其他型号?

我到处读到业务逻辑属于模型而不是控制器但是限制在哪里? 我正在玩个人会计应用程序。

Account Entry Operation 

创建操作时,只有创建相应的条目并将其链接到帐户才有效,以便操作平衡,例如购买6件装:

 o=Operation.new({:description=>"b33r", :user=>current_user, :date=>"2008/09/15"}) o.entries.build({:account_id=>1, :amount=>15}) o.valid? #=>false o.entries.build({:account_id=>2, :amount=>-15}) o.valid? #=>true 

现在,在基本操作的情况下向用户显示的表格被简化以隐藏条目细节,根据用户请求的操作类型(默认帐户 – >权益来计算,花费资产 – 选择帐户5) >费用,赚取收入 – >资产,借入负债 – >资产,支付债务资产 – >负债……)我想要从默认值创建的条目。

我还希望能够创建更复杂的操作(超过2个条目)。 对于第二个用例,我将有一个不同的forms,其中暴露了额外的复杂性。第二个用例使我无法在操作中包含借方和贷方字段并删除Entry链接。

哪种forms最好? 像我一样在SimpleOperationController中使用上面的代码,或者在Operation类上定义一个新方法,这样我就可以调用Operation.new_simple_operation(params [:operation])

是不是打破了关注点的分离,实际上是从Operation类创建和操作Entry对象?

我不是在寻找关于我扭曲的会计原则的建议:)

编辑 – 似乎我没有太清楚地表达自己。 我不太关心validation。 我更关心创建逻辑代码应该去哪里:

假设控制器上的操作被称为花费,当使用花费时,params散列将包含:金额,日期,描述。 借方和贷方账户将从被调用的操作中获得,但之后我必须创建所有对象。 拥有它会更好吗?

 #error and transaction handling is left out for the sake of clarity def spend amount=params[:operation].delete(:amount)#remove non existent Operation attribute op=Operation.new(params[:operation]) #select accounts in some way ... #build entries op.entries.build(...) op.entries.build(...) op.save end 

或者在操作上创建一个方法,使上面看起来像

 def spend op=Operation.new_simple_operation(params) op.save end 

这肯定会给一个更薄的控制器和一个更胖的模型,但随后该模型将创建和存储其他模型的实例,这是我的问题所在。

但随后模型将创建并存储其他模型的实例,这就是我的问题所在。

这有什么问题?

如果您的“业务逻辑”声明操作必须具有一组有效的条目,那么操作类肯定没有任何错误可以了解并处理您的Entry对象。

如果你把它放得太远,你只会遇到问题,并让你的模型操纵他们不需要知道的东西,比如EntryHtmlFormBuilder或其他:-)

虚拟属性( 此处和此处的更多信息)将极大地帮助您。 将整个参数传递回模型可以使控制器中的事情变得简单。 这将允许您动态构建表单并轻松构建条目对象。

 class Operation has_many :entries def entry_attributes=(entry_attributes) entry_attributes.each do |entry| entries.build(entry) end end end class OperationController < ApplicationController def create @operation = Operation.new(params[:opertaion]) if @operation.save flash[:notice] = "Successfully saved operation." redirect_to operations_path else render :action => 'new' end end end 

如果一切无效,保存将失败。 这让我们得到了validation。 因为每个条目都是独立的,您需要在“创建”中检查所有条目,您应该在操作中覆盖validation:

 class Operation # methods from above protected def validate total = 0 entries.each { |e| t += e.amount } errors.add("entries", "unbalanced transfers") unless total == 0 end end 

现在,您将收到一条错误消息,告知用户金额已关闭,他们应该解决问题。 你可以在这里得到真正的想象,并通过具体的问题增加很多价值,比如告诉他们他们有多少关闭。

根据每个实体validation自身,以及依赖于彼此的实体将其状态委托给其相关条目的状态,更容易思考。 在您的情况下,例如:

 class Operation < ActiveRecord::Base has_many :entries validates_associated :entries end 

validates_associated将检查每个关联实体是否有效(在这种情况下,如果操作有效,则所有条目都应该)。

尝试validation整个模型的整个层次结构是非常诱人的,但正如您所说,最容易完成的地方是控制器,它应该更像是处理请求和响应而不是处理业务。逻辑。

我看待它的方式是控制器应该反映最终用户视图并将请求转换为模型操作和响应,同时还要进行格式化。 在您的情况下,有两种操作表示使用默认帐户/条目的简单操作,以及具有用户选择的条目和帐户的更复杂操作。 表单应该反映用户视图(具有不同字段的2个表单),并且控制器中应该有2个匹配的操作。 然而,控制器应该没有与数据操作方式有关的逻辑,只有如何接收和响应。 我会在Operation类上有类方法,它从表单中获取适当的数据并根据需要创建一个或多个对象,或者将这些类方法放在不是AR模型的支持类上,但具有跨模型的业务逻辑边界。 单独的实用程序类的优点是它使每个模型都集中在一个目的上,缺点是实用程序类没有定义的存活位置。 我把它们放在lib /中但是Rails没有为模型助手指定一个位置。

如果您担心将此逻辑嵌入到任何特定模型中,为什么不将它们放入观察者类中,这将使您创建关联项的逻辑与所观察的类分开。