【问题标题】:Rails; Validate parameters within service object导轨;验证服务对象中的参数
【发布时间】:2017-05-11 08:49:44
【问题描述】:

我在 Rails 中创建了一个服务对象来封装为 Stripe(https://stripe.com/)创建计划的业务逻辑。 有没有什么好的模式来处理Service对象的参数验证?

对于验证:

  • 我必须检查所有输入是否为 nil 或类型错误。有什么方法可以轻松验证吗?也许是 Rails 扩展?

这是一个例子;

# app/services/service.rb
module Service
  extend ActiveSupport::Concern

  included do
    def self.call(*args)
      new(*args).call
    end
  end
end

# app/services/plan/create.rb
class Plan::Create
  include Service

  attr_reader :params

  def initialize(params = {})
    @params = params.dup
  end

  def call
    plan = Plan.new(attrs)
    return plan unless plan.valid?

    begin
      external_card_plan_service.create(api_attrs)
    rescue Stripe::StripeError => e
      plan.errors[:base] << e.message
      return plan
    end

    plan.save
    plan.update(is_active: true, activated_at: Time.now.utc)
    plan
  end

  private

  def external_card_plan_service
    Stripe::Plan
  end

  def build_data_hash
    {
      id: params.fetch(:stripe_plan_id)
      stripe_plan_id: params.fetch(:stripe_plan_id)
      amount: params.fetch(:amount)
      currency: params.fetch(:currency)
      interval: params.fetch(:interval)
      name: params.fetch(:name)
      description: params.fetch(:description)
    }
  end

  def attrs
    build_data_hash.slice(:stripe_plan_id, :amount, :currency, :interval, :name, :description)
  end

  def api_attrs
    build_data_hash.slice(:id, :amount, :currency, :interval, :name)
  end
end

参考:http://brewhouse.io/blog/2014/04/30/gourmet-service-objects.

更新

上面的例子并不复杂。 但是,如果在服务对象中调用服务对象,最好验证参数。 CreateUser.call(email_address)

class CreateSubscription
  def self.call(plan, email_address, token)
    user, raw_token = CreateUser.call(email_address)

    subscription = Subscription.new(
      plan: plan,
      user: user
    )

    begin
      stripe_sub = nil
      if user.stripe_customer_id.blank?
        customer = Stripe::Customer.create(
          source: token,
          email: user.email,
          plan: plan.stripe_id,
        )
        user.stripe_customer_id = customer.id
        user.save!
        stripe_sub = customer.subscriptions.first
      else
        customer = Stripe::Customer.retrieve(user.stripe_customer_id)
        stripe_sub = customer.subscriptions.create(
          plan: plan.stripe_id
        )
      end

      subscription.stripe_id = stripe_sub.id

      subscription.save!
    rescue Stripe::StripeError => e
      subscription.errors[:base] << e.message
    end

    subscription
  end 
end

我尝试使用 Virtus(https://github.com/solnic/virtus)。 但是我不知道如何在这种类型的服务对象中使用它。

更新

# api/v1/controller/plans_controller.rb
    module Api
      module V1
        class PlansController < Api::V1::ApiController
          before_action :check_type, only: [:create]

          def create
            @result = Plan::Create.call(plan_info_params)

            if @result.errors.blank?
              resource = Api::V1::PlanResource.new(@result, nil)

              # NOTE: Include all domains created within this routine.
              serializer = JSONAPI::ResourceSerializer.new(Api::V1::PlanResource)
              json_body = serializer.serialize_to_hash(resource)
              render json: json_body, status: 201 # :ok
            else
              errors = jsonapi_errors(@result)
              response = { errors: errors }
              render json: response, status: 422 # :unprocessable_entity
            end
          end

          private

          def check_type
            data_type = 'plans'
            unless params.fetch('data', {}).fetch('type', {}) == data_type
              render json: { errors: [{ title: 'Unprocessable Entity', detail: "Type must be #{data_type}" }] }, status: 422
            end
          end

          def plan_info_params
            params.require(:data).require(:attributes).permit(:stripe_plan_id, :amount, :currency, :interval, :name, :description)
          end
        end
      end
    end

【问题讨论】:

    标签: ruby-on-rails


    【解决方案1】:

    dry-validation 是一个不错的解决方案。

    class PlanForm
        include DryValidationForm
    
        Schema = Dry::Validation.Form do
          required(:stripe_plan_id).filled(:int?)
          required(:stripe_plan_id).filled(:int?)
          required(:amount).filled(:int?, gt?: 0)
          required(:currency).filled(:str?)
          required(:name).filled(:str?)
          optional(:description).maybe(:str?)
        end
      end
    end
    

    调用表单的最佳位置可能是您获取参数的控制器。然后将有效参数传递给服务对象

    【讨论】:

    • 感谢您的回答。那么这里使用 Form 对象对吗?另外我不确定 Form 对象是否可以,因为我们一直在构建 API。
    • @TSH 表单用于验证和准备参数以进行进一步处理。所以是的,表单对象在这里是一个很好的选择,不管它是否用于 API。
    • 谢谢!你觉得 virtus + form object 怎么样?我很想知道利弊。
    • @TSH in out project,我们从基于virtus的表单(有gem reform切换到dry-validation,因为这样更方便。但是,我不记得 virtus 有什么特别的问题。当表单变得复杂时,会有一些麻烦 (nested objects, etc) 。不过,没有什么是无法解决的。
    • 听起来不错。我一直在尝试添加它,但我找不到任何应用内实现示例。如果您根据上面的示例向我展示如何在控制器中实现它,那就太好了。我更新了我的问题!
    猜你喜欢
    • 2015-02-04
    • 1970-01-01
    • 2011-07-03
    • 2010-11-01
    • 2011-08-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多