【问题标题】:Rails generic errors arrayRails 通用错误数组
【发布时间】:2013-10-16 00:05:57
【问题描述】:

在我的 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 引用另一个对象来显示错误。

有什么想法吗?

【问题讨论】:

    标签: ruby-on-rails error-handling ruby-on-rails-4


    【解决方案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 来处理机架级别的错误,正如 José Valim 描述的 here

    您也可以继承该方法而不是拥有一个单独的服务类,或者拥有一个关注点/模块。你甚至可以通过钩子来做,例如:

    # 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_actionafter_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
    

    各种可能性。

    【讨论】:

    • 感谢您提供如此详细的回复。真的很感激!我想在 Controller 中进行错误处理,但我不确定如何 - 请注意我将如何让 rescue_from 从我的控制器中工作,因为我认为这对我来说是最好的选择?
    • 更新了答案。希望对您有所帮助。
    • 经过更多的实验,我得到了一些意想不到的行为。我已经从服务对象中删除了我的rescue 语句,并将rescue_fromadd_error_message_to_base(e) 方法添加到调用Controller,但是当我强制出错时,我只会看到一个空白页,好像一切都停止运行了。之前,它会重定向!???
    • 我在add_error_message_to_base(e) 中添加了 render :new 并且现在似乎可以工作了。
    • 很抱歉。由于错误被添加到视图的实例中,因此可能更通用的方法是render params[:action]。将其添加到示例中
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-10-31
    • 2011-04-23
    • 1970-01-01
    相关资源
    最近更新 更多