2016-12-27 70 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