1

In my Rails 4 app I have a Service object that handles communication with Stripe Payments Processor. I want it as a service object so that multiple Controllers/Models can utilize the methods within it.

However, I also need to be able to trap errors when communicating with the Stripe API which then causes the problem as the errors need to be assigned to a particular object.

Here is a method in my StripeCommunicator.rb class:

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

as you can see - the errors are being added to the @account object - which essentially renders it useless when I want to use this method from another controller with a View that refers to another object to display errors.

Any ideas?

4

1 回答 1

3

最简单的事情是将@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

您可以使用José Valim在此处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后面就是 Account:

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

各种可能性。

于 2013-10-08T22:03:01.147 回答