2016-12-27 26 views
0

我在Rails中创建了一个服务对象来封装为Stripe创建计划的业务逻辑(https://stripe.com/)。 对于服务对象是否有任何好的模式来处理参数验证?导轨;验证服务对象中的参数

为了验证:

  • 我要检查所有的输入,零或错误的类型。有什么方法可以轻松验证吗?也许是轨道延伸?

这里是一个例子;

# 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

UPDATE

上面的例子是不那么复杂。 但是,如果在服务对象中调用服务对象,那么验证参数会更好。 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)。 但是我不知道如何在这种类型的服务对象中使用它。

UPDATE

# 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 

回答

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 

也许最好的地方叫形式将从控制器你得到的参数。然后将有效的参数传递给服务对象

+0

感谢您的回答。那么这里Form对象是否正确使用?此外,我不确定Form对象是否会好,因为我们一直在构建API。 – Tosh

+0

@TSH表格用于进一步处理的验证和准备参数。所以是的,表单对象是一个很好的选择,不管它是否适用于API。 –

+0

谢谢!你如何看待virtus +表单对象?我很想知道优点和缺点。 – Tosh