Должны ли модели rails быть связаны с другими моделями ради тощих контроллеров?

StackOverflow https://stackoverflow.com/questions/64214

  •  09-06-2019
  •  | 
  •  

Вопрос

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

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(параметры [:operation])

Разве это не нарушает разделение задач, чтобы фактически создавать объекты ввода из класса Operation и манипулировать ими?

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

редактировать - Кажется, я выразился не слишком ясно.Я не так уж обеспокоен проверкой.Меня больше беспокоит, куда должен идти логический код создания :

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

#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

или создать метод on Operation, который придавал бы описанному выше виду

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

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

Это было полезно?

Решение

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

Что в этом плохого?

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

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

Другие советы

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top