【问题标题】:Should rails models be concerned with other models for the sake of skinny controllers?为了瘦控制器,rails 模型是否应该与其他模型有关?
【发布时间】:2010-09-09 00:24:59
【问题描述】:

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

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 对象不是破坏了关注点分离吗?

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

edit -- 看来我表达的不是很清楚。 我不太关心验证。我比较关心创建逻辑代码应该去哪里:

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

#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

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

【问题讨论】:

    标签: ruby-on-rails ruby


    【解决方案1】:

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

    这有什么问题?

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

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

    【讨论】:

      【解决方案2】:

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

      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
      

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

      【讨论】:

      • 我最初接受了您的回答,但它假定条目的参数已定义,但事实并非如此。然后我必须在控制器中创建正确的参数,这与创建对象相同:) 但是如果 IRRC
      【解决方案3】:

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

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

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

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

      【讨论】:

        【解决方案4】:

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

        【讨论】:

          【解决方案5】:

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

          【讨论】:

            猜你喜欢
            • 2011-07-06
            • 1970-01-01
            • 2012-02-02
            • 2011-10-17
            • 1970-01-01
            • 2012-01-25
            • 1970-01-01
            • 2018-09-04
            • 1970-01-01
            相关资源
            最近更新 更多