2008-09-15 15 views
5

Leo en todas partes que la lógica comercial pertenece a los modelos y no al controlador, pero ¿dónde está el límite? Estoy jugando con una aplicación de contabilidad personal.¿Los modelos de rieles deberían preocuparse por otros modelos en aras de los controladores delgados?

Account 
Entry 
Operation 

Al crear una operación sólo es válido si se crean y vinculadas a las cuentas de manera que la operación es equilibrado para exemple las entradas correspondientes comprar un paquete de 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 

Ahora la forma mostrada para el usuario en el caso de operaciones básicas se simplifica para ocultar los detalles de las entradas, las cuentas se seleccionan entre 5 por defecto por el tipo de operación solicitada por el usuario (cuenta inicial -> patrimonio para contabilizar, gastar activos -> gastos , gana ingresos-> activos, endeudamientos-> activos, paga activos de deuda-> pasivos ...) Quiero las entradas creadas a partir de los valores predeterminados.

También quiero ser capaz de crear operaciones más complejas (más de 2 entradas). Para este segundo caso de uso tendré una forma diferente en la que se expondrá la complejidad adicional. Este segundo caso de uso me impide incluir un campo de débito y crédito en la Operación y deshacerme del enlace de Entrada.

¿Cuál es la mejor forma? Usando el código anterior en un SimpleOperationController como lo hago por el momento, o definiendo un nuevo método en la clase Operation para poder llamar Operation.new_simple_operation (params [: operation])

¿No está rompiendo la separación de preocupaciones? para realmente crear y manipular objetos de entrada de la clase de Operación?

No estoy buscando consejo sobre mis principios de contabilidad retorcidos :)

edición - Parece que no me expreso con demasiada claridad. No estoy tan preocupado por la validación. Me preocupa más dónde debe ir el código lógico de creación:

asumiendo que la operación en el controlador se denomina gasto, al usar gasto, el hash de parámetros contendrá: cantidad, fecha, descripción. Las cuentas de débito y crédito se derivarían de la acción que se llama, pero luego tengo que crear todos los objetos. ¿Sería mejor tener

#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 

o para crear un método de operación que haría que el aspecto anterior como

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

esto sin duda dará un controlador mucho más delgada y un modelo más gordos, pero entonces el modelo creará y almacenará instancias de otros modelos, que es donde está mi problema.

Respuesta

6

pero luego el modelo creará y almacenará instancias de otros modelos que es donde está mi problema.

¿Qué pasa con esto?

Si su 'lógica de negocios' indica que una Operación debe tener un conjunto válido de Entradas, entonces seguramente no hay nada de malo en que la clase Operación conozca y trate sus Objetos de Entrada.

sólo obtendrá problemas si se toma esto demasiado lejos, y tienen sus modelos de manipulación de las cosas que no hacen necesidad de conocer, como un EntryHtmlFormBuilder o lo que sea :-)

0

Es más fácil pensar en términos de que cada entidad se valide a sí misma y de que las entidades que dependen una de otra deleguen su estado en el estado de sus entradas asociadas. En su caso, por ejemplo:

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

validates_associated comprobará si cada entidad asociada es válida (en este caso, todas las entradas debe si la operación es ser válida).

Es muy tentador intentar validar jerarquías completas de modelos en conjunto, pero como dijiste, el lugar donde sería más fácil hacerlo es el controlador, que debería actuar más como enrutador de solicitudes y respuestas que al tratar con la lógica de negocios.

0

La forma en que lo veo es que el controlador debe reflejar la vista del usuario final y traducir las solicitudes en las operaciones y respuestas del modelo al mismo tiempo que se formatea. En su caso, hay dos tipos de operaciones que representan operaciones simples con una cuenta/entrada predeterminada, y operaciones más complejas que tienen entradas y cuentas seleccionadas por el usuario. Los formularios deben reflejar la vista del usuario (2 formularios con diferentes campos), y debe haber 2 acciones en el controlador para que coincidan. Sin embargo, el controlador no debería tener ninguna lógica relacionada con la forma en que se manipulan los datos, solo cómo recibir y responder. Tendría métodos de clase en la clase Operación que toman los datos adecuados de los formularios y crea uno o más objetos según sea necesario, o coloca esos métodos de clase en una clase de soporte que no es un modelo AR, pero tiene lógica de negocios que cruza el modelo límites. La ventaja de la clase de utilidad separada es que mantiene a cada modelo enfocado en un propósito, el inconveniente es que las clases de utilidad no tienen un lugar definido para vivir. Los puse en lib/pero Rails no especifica un lugar para los ayudantes modelo como tal.

2

Atributos virtuales (más información here y here) ayudará con esto en gran medida. Pasar todo el parámetro al modelo mantiene las cosas simples en el controlador. Esto le permitirá construir dinámicamente su formulario y construir fácilmente los objetos de las entradas.

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 

El guardado fallará si todo no es válido. Lo que nos lleva a la validación. Debido a que cada entrada es independiente y es necesario comprobar todas las entradas en la "creación" probablemente debería anular validar en la Operación:

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 

Ahora usted recibirá un mensaje de error que indica al usuario que las cantidades están apagadas y se debe corregir el problema. Aquí puede ser realmente elegante y agregar un gran valor al ser específico sobre el problema, como decirles cuánto están perdiendo.

+0

Originalmente acepté su respuesta pero asume que los parámetros para las entradas están definidos, que no es el caso. Entonces tendría que crear los parámetros correctos en el controlador que es lo mismo que crear los objetos :) pero su truco será útil en otros lugares y se fijará en los raíles 2.2 si el IRRC – Jean

0

Si está preocupado acerca de cómo integrar esta lógica en cualquier modelo en particular, ¿por qué no ponerlos en una clase de observador, que mantendrá la lógica para la creación de los elementos asociados separados de las clases observadas?

Cuestiones relacionadas