Массив ошибок Rails

В моем приложении Rails 4 у ​​меня есть объект Service, который обрабатывает связь с Stripe Payments Processor. Я хочу это как служебный объект, чтобы несколько контроллеров / моделей могли использовать методы внутри него.

Однако мне также нужно иметь возможность перехватывать ошибки при обмене данными с Stripe API, что затем вызывает проблему, поскольку ошибки должны быть назначены определенному объекту.

Вот метод в моем StripeCommunicator.rb учебный класс:

def create_customer(token,object)
  customer = Stripe::Customer.create(:description => 'Accommodation', :email => object.email, :card => token)
  return customer

rescue Stripe::CardError => e
  @account.errors.add :base, e.message
  false
end

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

Есть идеи?

1 ответ

Решение

Самое простое - просто передать @account экземпляр в качестве другого аргумента. Ошибки будут на любом экземпляре модели, например

def create_customer(token,object,model_instance)
  Stripe::Customer.create(description: 'Accommodation', email: object.email, card: token)
  # return customer <- don't need this. whatever is last evaluated will be returned
rescue Stripe::CardError => e
  model_instance.errors.add :base, e.message
  false
end

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

rescue_from Stripe::CardError, with: :add_error_message_to_base

def add_error_message_to_base(e)
  # this assumes that you set @instance in the controller's action method.
  @instance.errors.add :base, e.message
  respond_with @instance
end

или более обобщенно:

rescue_from Stripe::CardError, with: :add_error_message_to_base

def add_error_message_to_base(e)
  model_class_name = self.class.name.chomp('Controller').split('::').last.singularize
  instance_value = instance_variable_get("@#{model_class_name}")
  instance_value.errors.add :base, e.message if instance_value
  respond_with instance_value
end

или в беспокойстве, вы можете сделать одно из вышеперечисленных, поместив rescue_from во включенный блок:

module StripeErrorHandling
  extend ::ActiveSupport::Concern

  included do
    rescue_from Stripe::CardError, with: :add_error_message_to_base
  end

  def add_error_message_to_base(e)
    # see comment above...
    @instance.errors.add :base, e.message
    respond_with @instance
  end
end

И вы можете использовать config.exceptions_app обрабатывать ошибки на уровне стойки, как здесь описывает Хосе Валим.

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

# not exactly what you were doing but just for example.
# could put in app/controller/concerns among other places.
module ActionsCreateStripeCustomer
  extend ::ActiveSupport::Concern

  included do
    around_action :create_stripe_customer
  end

  def create_stripe_customer
    # this (indirectly) calls the action method, and you will
    # set @instance in your action method for this example.
    yield
    customer = Stripe::Customer.find_or_create_by(description: 'Accommodation', email: object.email, card: token)
    # could set customer on @instance here and save if needed, etc.
  rescue Stripe::CardError => e
    if @instance
      @instance.errors.add :base, e.message
      respond_with @instance
    else
      logger.warn("Expected @instance to be set by #{self.class.name}##{params[:action]}")
      raise e
    end
  end
end

Тогда в контроллере:

include ActionsCreateStripeCustomer

Существует также before_action, after_actionи т. д. Кроме того, вы можете просто включить модули и при вызове методов экземпляра сначала вызывать включаемый экземпляр класса, затем первый включенный модуль, затем второй и т. д., если вы делаете super if defined?(super) вызвать предыдущий метод, и он автоматически вставит все аргументы и блок.

И если бы речь шла о получении имени класса модели, а не экземпляра, это тоже легко. Скажем, класс, из которого вы звонили, был AccountStripeCommunicator, затем @model_class после следующего будет аккаунт:

qualified_class_name = self.class.name.chomp('StripeCommunictor')
@model_class = qualified_class_name.split('::').last.singularize.constantize

Все виды возможностей.

Другие вопросы по тегам