Должны ли модели рельсов быть связаны с другими моделями ради контроллеров-скинов? - PullRequest
5 голосов
/ 15 сентября 2008

Я везде читал, что бизнес-логика принадлежит моделям, а не контроллеру, но где предел? Я играю с личным бухгалтерским приложением.

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])

Не нарушает ли разделение задач фактическое создание объектов Entry и управление ими из класса Operation?

Я не ищу совета по поводу моих искаженных принципов бухгалтерского учета:)

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

это определенно даст гораздо более тонкий контроллер и более толковую модель, но тогда модель будет создавать и хранить экземпляры других моделей, где моя проблема.

Ответы [ 5 ]

6 голосов
/ 16 сентября 2008

но тогда модель создаст и сохранит экземпляры других моделей, где моя проблема.

Что с этим не так?

Если ваша «бизнес-логика» гласит, что у Операции должен быть действительный набор записей, то, конечно, нет ничего плохого в том, чтобы класс Операции знал о ваших объектах записей и имел дело с ними.

Проблемы возникнут только в том случае, если вы зайдете слишком далеко, и ваши модели будут манипулировать вещами, о которых не нужно знать, например, EntryHtmlFormBuilder или чем-то еще: -)

2 голосов
/ 16 сентября 2008

Виртуальные атрибуты (подробнее здесь и здесь ) помогут в этом. Передача всех параметров обратно в модель упрощает работу контроллера. Это позволит вам динамически создавать форму и легко создавать объекты записей.

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

Сохранить не удастся, если все не верно. Что приводит нас к проверке. Поскольку каждая запись стоит отдельно, и вам нужно проверить все записи при «создании», вы, вероятно, должны переопределить validate в Operation:

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

Теперь вы получите сообщение об ошибке, сообщающее пользователю, что суммы сняты, и они должны решить проблему. Вы можете по-настоящему придумать это и добавить большую ценность, если будете конкретно говорить о проблеме, например, расскажите им, сколько у них денег.

0 голосов
/ 19 сентября 2008

Если вас интересует встраивание этой логики в какую-либо конкретную модель, почему бы не поместить их в класс наблюдателя, который будет держать логику для создания связанных элементов отдельно от наблюдаемых классов.

0 голосов
/ 15 сентября 2008

Я смотрю на это так, что контроллер должен отражать представление конечного пользователя и преобразовывать запросы в операции модели и ответы, в то же время выполняя форматирование. В вашем случае есть 2 вида операций, которые представляют собой простые операции с учетной записью / записью по умолчанию, и более сложные операции, которые имеют выбранные пользователем записи и учетные записи. Формы должны отражать пользовательское представление (2 формы с разными полями), и в контроллере должно быть 2 действия для соответствия. Контроллер, однако, не должен иметь никакой логики, касающейся того, как данные обрабатываются, а только как получать и отвечать. Я хотел бы иметь методы класса для класса Operation, которые берут правильные данные из форм и создают один или несколько объектов по мере необходимости, или помещают эти методы класса в класс поддержки, который не является моделью AR, но имеет бизнес-логику, которая пересекает модель границы. Преимущество отдельного класса утилит состоит в том, что он сохраняет каждую модель ориентированной на одну цель, а недостатком является то, что классам утилит нет определенного места для жизни. Я помещаю их в lib /, но Rails не указывает место для помощников модели как таковых.

0 голосов
/ 15 сентября 2008

Проще думать с точки зрения каждой сущности, проверяющей себя, и сущностей, которые зависят друг от друга, делегируя свое состояние состоянию связанных записей. В вашем случае, например:

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

validates_associated будет проверять, является ли каждый связанный объект действительным (в этом случае все записи должны быть действительными, если операция должна быть действительной).

Очень заманчиво попытаться проверить целые иерархии моделей в целом, но, как вы сказали, место, где это будет легче всего сделать, - это контроллер, который должен действовать скорее как маршрутизатор запросов и ответов, чем дело с бизнес-логикой.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...