Каковы некоторые общие шаблоны для обеспечения постоянства данных для моделей Rails в нескольких системах?

Допустим, у нас есть следующий сценарий:

class MyModel < ActiveRecord::Base
  after_save :throw_after_save
  after_commit :throw_after_commit

  private
    def throw_after_save
      raise "raising on after_save"
    end

    def throw_after_commit
      raise "raising on after_commit"
    end
end

class MyController < ApplicationController
  def callback
    begin
      MyModel.new(params).save
    rescue
      flash[:alert] = "Failed persisting to external system. Try again."
      Airbrake.notify(
        error_class: "External System Persistence",
        error_message: "External System Persistence: Failed to persist data",
        parameters: params
      )
    end

    redirect_to root_path
  end
end

Получаем обратный вызов от внешней системы (где пользователь заполняет какие-то данные и создает временный набор атрибутов для учетной записи).

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

Цель состоит в том, чтобы зафиксировать внешние исключения сохраняемости, а также успехи и действовать соответствующим образом. В случае успеха все в порядке: дополнительные данные сохраняются локально, происходит redirect_to root_path. Однако в случае исключений мы хотели бы указать это пользователю (возможно, установить flash[:alert] для отображения в представлении).

Мы пытались использовать ActiveRecord::Callbacks, чтобы генерировать исключение из моделей after_save и after_commit и обрабатывать это исключение в контроллере, устанавливая предупреждение и, возможно, передавая исключение в какую-либо систему уведомления об исключениях (например, Airbrake). В случае after_save исключение выбрасывается моделью и ловится контроллером, но запись не сохраняется (а у нас есть требование сохранения частичных данных даже в случае исключений с внешней системой - это не приемлемый). В случае after_commit исключение не генерируется и принимается контроллером, но частичная запись сохраняется. Это означает, что мы не можем уведомить пользователя об исключении (если только мы не реализуем какой-либо механизм отправки уведомлений, что является излишним).

Как оказалось, мы можем устанавливать ошибки в модели на after_save, и это хорошо. Но является ли это хорошим общим шаблоном для такого сценария?


person zealoushacker    schedule 07.06.2012    source источник
comment
Похоже, это действительно полезный ресурс... rubysource.com/ Сейчас читаю. Также: railsconf2012.com/sessions/50   -  person zealoushacker    schedule 08.06.2012


Ответы (1)


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

Например:

class MyModelPersistenceService

    def save_model( model )
      result = model.save
      if result
        call_external_service
      end
    end 

end

Службы встречаются не так часто. в проектах Ruby/Rails, но они хорошо подходят для такого рода использования (и злоупотребление обратными вызовами AR обычно приводит к трудно тестируемым объектам).

person Maurício Linhares    schedule 07.06.2012
comment
Шаблон обслуживания имеет смысл. Он будет инкапсулировать постоянство внешней службы, а не полагаться на саму модель, чтобы справиться с этим. Единственная проблема с этим, конечно, заключается в том, что он нарушает стандартный шаблон Rails instance = Model.new(attributes), за которым следует instance.save. Мне все еще интересно, возможно ли изящно инкапсулировать логику внутри самой модели, чтобы избежать выхода за рамки общих шаблонов Rails. В основном меня это интересует с чисто информационной точки зрения. - person zealoushacker; 08.06.2012
comment
Вы не должны бояться выпрыгивать из общих шаблонов рельсов, многие из этих шаблонов подходят для простых приложений, но очень плохи, когда они становятся сложными, обратные вызовы спагетти - один из таких примеров, как только вы получите слишком много, ваш код AR кажется, что он творит слишком много магии, и его становится трудно проверить и рассуждать об этом. Есть много других решений для этого, которые будут отличаться от решений Rails по умолчанию, и это нормально, это разные решения одних и тех же проблем. - person Maurício Linhares; 08.06.2012