5

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

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 个条目)。对于第二个用例,我将有一个不同的形式,其中暴露了额外的复杂性。第二个用例阻止我在操作中包含借方和贷方字段并摆脱输入链接。

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

从 Operation 类中实际创建和操作 Entry 对象不是破坏了关注点分离吗?

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

编辑——看来我表达得不太清楚。我不太关心验证。我更关心创建逻辑代码应该去哪里:

假设控制器上的操作称为支出,当使用支出时,参数哈希将包含:金额、日期、描述。借方和贷方账户将派生自被调用的操作,但随后我必须创建所有对象。拥有会不会更好

#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

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

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

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

4

5 回答 5

6

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

这有什么问题?

如果您的“业务逻辑”声明 Operation 必须具有一组有效的 Entries,那么 Operation 类肯定没有任何问题可以了解和处理您的 Entry 对象。

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

于 2008-09-15T21:19:57.783 回答
2

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

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

如果一切都无效,则保存将失败。这给我们带来了验证。因为每个条目都是独立的,您需要在“创建”时检查所有条目,您可能应该在操作中覆盖验证:

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

现在您将收到一条错误消息,告诉用户金额已关闭,他们应该解决问题。你可以在这里变得非常花哨,并通过具体说明问题来增加很多价值,比如告诉他们他们有多少损失。

于 2008-09-15T21:08:43.927 回答
0

更容易考虑每个实体验证自己,以及相互依赖的实体将其状态委托给其关联条目的状态。在您的情况下,例如:

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

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

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

于 2008-09-15T16:23:46.700 回答
0

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

于 2008-09-15T19:35:14.183 回答
0

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

于 2008-09-19T17:16:16.983 回答