Computer >> कंप्यूटर >  >> प्रोग्रामिंग >> Ruby

रूबी ऑन रेल्स में सर्विस ऑब्जेक्ट्स का उपयोग करना

<ब्लॉकक्वॉट>

इस लेख को Playbook उनतीस - न्यूनतम टूलिंग के साथ इंटरएक्टिव वेब ऐप्स शिपिंग के लिए एक गाइड में इसके मूल स्वरूप से संशोधित किया गया है। , और AppSignal के लिए इस अतिथि पोस्ट को फिट करने के लिए तैयार किया गया है।

आपके ऐप को संभालने के लिए बहुत सारी कार्यक्षमता है, लेकिन यह तर्क जरूरी नहीं कि नियंत्रक या मॉडल में भी हो। कुछ उदाहरणों में कार्ट से चेक आउट करना, साइट के लिए पंजीकरण करना या सदस्यता शुरू करना शामिल है।

आप इस सारे लॉजिक को कंट्रोलर में शामिल कर सकते हैं, लेकिन आप उन सभी जगहों पर एक ही लॉजिक को कॉल करते हुए खुद को दोहराते रहेंगे। आप तर्क को एक मॉडल में रख सकते हैं, लेकिन कभी-कभी, आपको उन चीजों तक पहुंच की आवश्यकता होती है जो नियंत्रक में आसानी से उपलब्ध होती हैं, जैसे आईपी पता, या यूआरएल में पैरामीटर। आपको जो चाहिए वह एक सेवा वस्तु है।

सर्विस ऑब्जेक्ट का काम कार्यक्षमता को एनकैप्सुलेट करना, एक सेवा को निष्पादित करना और विफलता का एकल बिंदु प्रदान करना है। सर्विस ऑब्जेक्ट का उपयोग करने से डेवलपर्स को एक ही कोड को बार-बार लिखने से रोकता है जब इसे एप्लिकेशन के विभिन्न हिस्सों में उपयोग किया जाता है।

एक सेवा वस्तु सिर्फ एक सादा पुरानी रूबी वस्तु ("पोरो") है। यह सिर्फ एक फाइल है जो एक विशिष्ट निर्देशिका के अंतर्गत रहती है। यह एक रूबी वर्ग है जो अनुमानित प्रतिक्रिया देता है। प्रतिक्रिया को प्रेडिक्टेबल बनाता है जो तीन प्रमुख भागों के कारण होता है। सभी सेवा वस्तुओं को समान पैटर्न का पालन करना चाहिए।

  • परम तर्क के साथ एक आरंभीकरण विधि है।
  • कॉल नाम की एक ही सार्वजनिक विधि है।
  • सफलता के साथ ओपनस्ट्रक्चर लौटाता है? और या तो पेलोड या त्रुटि।

ओपनस्ट्रक्चर क्या है?

यह एक वर्ग और हैश के दिमाग की उपज जैसा है। आप इसे एक मिनी-क्लास के रूप में सोच सकते हैं जो मनमाना गुण प्राप्त कर सकता है। हमारे मामले में, हम इसे एक अस्थायी डेटा संरचना के रूप में उपयोग कर रहे हैं जो केवल दो विशेषताओं को संभालती है।

अगर सफलता true है , यह डेटा का पेलोड लौटाता है।

OpenStruct.new({success ?:true, payload: 'some-data'})

अगर सफलता false है तो , यह एक त्रुटि देता है।

OpenStruct.new({success ?:false, error: 'some-error'})

यहां एक सेवा ऑब्जेक्ट का उदाहरण दिया गया है जो ऐपसिग्नल्स के नए एपीआई से डेटा तक पहुंचता है और पकड़ लेता है, जो वर्तमान में बीटा में है।

module AppServices
 
  class AppSignalApiService
 
    require 'httparty'
 
    def initialize(params)
      @endpoint   = params[:endpoint] || 'markers'
    end
 
    def call
      result = HTTParty.get("https://appsignal.com/api/#{appsignal_app_id}/#{@endpoint}.json?token=#{appsignal_api_key}")
    rescue HTTParty::Error => e
      OpenStruct.new({success?: false, error: e})
    else
      OpenStruct.new({success?: true, payload: result})
    end
 
    private
 
      def appsignal_app_id
        ENV['APPSIGNAL_APP_ID']
      end
 
      def appsignal_api_key
        ENV['APPSIGNAL_API_KEY']
      end
 
  end
end

आप उपरोक्त फ़ाइल को AppServices::AppSignalApiService.new({endpoint: 'markers'}).call से कॉल करेंगे। . मैं उम्मीद के मुताबिक प्रतिक्रिया देने के लिए ओपनस्ट्रक्चर का उदार उपयोग करता हूं। जब परीक्षण लिखने की बात आती है तो यह वास्तव में मूल्यवान होता है क्योंकि तर्क के सभी वास्तुशिल्प पैटर्न समान होते हैं।

मॉड्यूल क्या है?

मॉड्यूल का उपयोग हमें नाम-रिक्ति प्रदान करता है और अन्य वर्गों से टकराने से रोकता है। इसका मतलब है कि आप सभी वर्गों में एक ही विधि नामों का उपयोग कर सकते हैं और वे एक विशिष्ट नाम स्थान के अंतर्गत होने के कारण आपस में टकराएंगे नहीं।

मॉड्यूल नाम का एक अन्य महत्वपूर्ण हिस्सा यह है कि हमारे ऐप में फाइलों को कैसे व्यवस्थित किया जाता है। सेवा वस्तुओं को प्रोजेक्ट में सेवा फ़ोल्डर में रखा जाता है। ऊपर दिया गया सर्विस ऑब्जेक्ट उदाहरण, मॉड्यूल नाम AppServices . के साथ , AppServices . के अंतर्गत आता है सेवा निर्देशिका में फ़ोल्डर।

मैं अपनी सेवा निर्देशिका को कई फ़ोल्डरों में व्यवस्थित करता हूं, प्रत्येक में एप्लिकेशन के एक विशिष्ट भाग के लिए कार्यक्षमता होती है।

उदाहरण के लिए, CloudflareServices निर्देशिका Cloudflare पर उप डोमेन बनाने और हटाने के लिए विशिष्ट सेवा ऑब्जेक्ट रखती है। Wistia और Zapier सेवाओं में उनकी संबंधित सेवा फ़ाइलें होती हैं।

अपनी सेवा वस्तुओं को इस तरह व्यवस्थित करने से कार्यान्वयन की बात आने पर बेहतर पूर्वानुमान मिलता है, और एक नज़र में यह देखना आसान है कि ऐप 10k-foot दृश्य से क्या कर रहा है।

आइए StripeServices के बारे में जानें निर्देशिका। यह निर्देशिका स्ट्राइप्स एपीआई के साथ बातचीत करने के लिए अलग-अलग सेवा ऑब्जेक्ट रखती है। दोबारा, ये फ़ाइलें केवल एक चीज करती हैं जो हमारे एप्लिकेशन से डेटा लेती है और इसे स्ट्राइप को भेजती है। यदि आपको कभी भी StripeService . में API कॉल को अपडेट करने की आवश्यकता है ऑब्जेक्ट जो सदस्यता बनाता है, आपके पास ऐसा करने के लिए केवल एक ही स्थान है।

भेजे जाने वाले डेटा को एकत्रित करने वाले सभी तर्क AppServices में रहते हुए, एक अलग सेवा ऑब्जेक्ट में किए जाते हैं निर्देशिका। ये फ़ाइलें हमारे एप्लिकेशन से डेटा एकत्र करती हैं और बाहरी एपीआई के साथ इंटरफेस करने के लिए इसे संबंधित सेवा निर्देशिका में भेजती हैं।

यहां एक दृश्य उदाहरण दिया गया है:मान लें कि हमारे पास कोई है जो एक नई सदस्यता शुरू कर रहा है। सब कुछ एक नियंत्रक से उत्पन्न होता है। यह रहा SubscriptionsController

class SubscriptionsController < ApplicationController
 
  def create
    @subscription = Subscription.new(subscription_params)
 
    if @subscription.save
 
      result = AppServices::SubscriptionService.new({
        subscription_params: {
          subscription: @subscription,
          coupon: params[:coupon],
          token: params[:stripeToken]
        }
      }).call
 
      if result && result.success?
        sign_in @subscription.user
        redirect_to subscribe_welcome_path, success: 'Subscription was successfully created.'
      else
        @subscription.destroy
        redirect_to subscribe_path, danger: "Subscription was created, but there was a problem with the vendor."
      end
 
    else
      redirect_to subscribe_path, danger:"Error creating subscription."
    end
  end
end

हम पहले ऐप में सब्सक्रिप्शन बनाएंगे, और अगर यह सफल होता है, तो हम उसे, स्ट्राइपटोकन, और कूपन जैसी सामग्री को AppServices::SubscriptionService नामक फ़ाइल में भेजते हैं। ।

AppServices::SubscriptionService . में फ़ाइल, ऐसी कई चीजें हैं जिन्हें होने की आवश्यकता है। जो कुछ हो रहा है उसमें जाने से पहले यह रही वह वस्तु:

module AppServices
  class SubscriptionService
 
    def initialize(params)
      @subscription     = params[:subscription_params][:subscription]
      @token            = params[:subscription_params][:token]
      @plan             = @subscription.subscription_plan
      @user             = @subscription.user
    end
 
    def call
 
      # create or find customer
      customer ||= AppServices::StripeCustomerService.new({customer_params: {customer:@user, token:@token}}).call
 
      if customer && customer.success?
 
        subscription ||= StripeServices::CreateSubscription.new({subscription_params:{
          customer: customer.payload,
          items:[subscription_items],
          expand: ['latest_invoice.payment_intent']
        }}).call
 
        if subscription && subscription.success?
          @subscription.update_attributes(
            status: 'active',
            stripe_id: subscription.payload.id,
            expiration: Time.at(subscription.payload.current_period_end).to_datetime
          )
          OpenStruct.new({success?: true, payload: subscription.payload})
        else
          handle_error(subscription&.error)
        end
 
      else
        handle_error(customer&.error)
      end
 
    end
 
    private
 
      attr_reader :plan
 
      def subscription_items
        base_plan
      end
 
      def base_plan
        [{ plan: plan.stripe_id }]
      end
 
      def handle_error(error)
        OpenStruct.new({success?: false, error: error})
      end
  end
end

एक उच्च-स्तरीय अवलोकन से, हम यहां देख रहे हैं:

हमें पहले स्ट्राइप ग्राहक आईडी प्राप्त करनी होगी ताकि हम सब्सक्रिप्शन बनाने के लिए इसे स्ट्राइप को भेज सकें। यह अपने आप में एक पूरी तरह से अलग सेवा वस्तु है जो ऐसा करने के लिए कई चीजें करती है।

  1. हम यह देखने के लिए जांच करते हैं कि stripe_customer_id उपयोगकर्ता की प्रोफ़ाइल पर सहेजा गया है। यदि ऐसा है, तो हम ग्राहक को स्ट्राइप से केवल यह सुनिश्चित करने के लिए पुनः प्राप्त करते हैं कि ग्राहक वास्तव में मौजूद है, फिर इसे हमारे ओपनस्ट्रक्चर के पेलोड में वापस कर दें।
  2. यदि ग्राहक मौजूद नहीं है, तो हम ग्राहक बनाते हैं, stripe_customer_id सहेजते हैं, फिर उसे OpenStruct के पेलोड में वापस कर देते हैं।

किसी भी तरह से, हमारी CustomerService स्ट्राइप ग्राहक आईडी लौटाता है, और यह वही करेगा जो इसे करने के लिए आवश्यक है। यह रही वह फ़ाइल:

module AppServices
  class CustomerService
 
    def initialize(params)
      @user               = params[:customer_params][:customer]
      @token              = params[:customer_params][:token]
      @account            = @user.account
    end
 
    def call
      if @account.stripe_customer_id.present?
        OpenStruct.new({success?: true, payload: @account.stripe_customer_id})
      else
        if find_by_email.success? && find_by_email.payload
          OpenStruct.new({success?: true, payload: @account.stripe_customer_id})
        else
          create_customer
        end
      end
    end
 
    private
 
      attr_reader :user, :token, :account
 
      def find_by_email
        result ||= StripeServices::RetrieveCustomerByEmail.new({email: user.email}).call
        handle_result(result)
      end
 
      def create_customer
        result ||= StripeServices::CreateCustomer.new({customer_params:{email:user.email, source: token}}).call
        handle_result(result)
      end
 
      def handle_result(result)
        if result.success?
          account.update_column(:stripe_customer_id, result.payload.id)
          OpenStruct.new({success?: true, payload: account.stripe_customer_id})
        else
          OpenStruct.new({success?: false, error: result&.error})
        end
      end
 
  end
end
 

उम्मीद है, आप यह देखना शुरू कर सकते हैं कि हम अपने तर्क को कई सेवा वस्तुओं में क्यों बनाते हैं। क्या आप कल्पना कर सकते हैं कि इस तर्क के साथ एक फ़ाइल के एक विशाल विशालकाय व्यक्ति की कल्पना की जा सकती है? बिलकुल नहीं!

हमारे AppServices::SubscriptionService . पर वापस जाएं फ़ाइल। अब हमारे पास एक ग्राहक है जिसे हम स्ट्राइप को भेज सकते हैं, जो स्ट्राइप पर सब्सक्रिप्शन बनाने के लिए आवश्यक डेटा को पूरा करता है।

अब हम अंतिम सर्विस ऑब्जेक्ट को कॉल करने के लिए तैयार हैं, StripeServices::CreateSubscription फ़ाइल।

फिर से, StripeServices::CreateSubscription सेवा वस्तु कभी नहीं बदलती। इसकी एक ही जिम्मेदारी है, और वह है डेटा लेना, उसे स्ट्राइप को भेजना, और या तो एक सफलता लौटाना या वस्तु को पेलोड के रूप में वापस करना।

module StripeServices
 
  class CreateSubscription
 
    def initialize(params)
      @subscription_params = params[:subscription_params]
    end
 
    def call
      subscription = Stripe::Subscription.create(@subscription_params)
    rescue Stripe::StripeError => e
      OpenStruct.new({success?: false, error: e})
    else
      OpenStruct.new({success?: true, payload: subscription})
    end
 
  end
 
end

बहुत आसान है ना? लेकिन आप शायद सोच रहे हैं, यह छोटी सी फाइल ओवरकिल है। आइए ऊपर दी गई फ़ाइल के समान फ़ाइल का एक और उदाहरण देखें, लेकिन इस बार हमने इसे स्ट्राइप कनेक्ट के माध्यम से एक बहु-किरायेदार एप्लिकेशन के साथ उपयोग के लिए संवर्धित किया है।

यहां चीजें दिलचस्प हो जाती हैं। हम यहां एक उदाहरण के रूप में मावेनसीड का उपयोग कर रहे हैं, हालांकि यही तर्क स्पोर्टकीपर पर भी चलता है। हमारा बहु-किरायेदार ऐप साइट_आईडी कॉलम द्वारा अलग किए गए एक एकल मोनोलिथ, साझा करने वाली टेबल है। प्रत्येक टैनेंट स्ट्राइप कनेक्ट के माध्यम से स्ट्राइप से जुड़ता है, और फिर हमें टैनेंट के खाते में सहेजने के लिए एक स्ट्राइप खाता आईडी प्राप्त होता है।

हमारे समान स्ट्राइप एपीआई कॉल का उपयोग करके, हम बस कनेक्टेड अकाउंट के स्ट्राइप अकाउंट को पास कर सकते हैं, और स्ट्राइप कनेक्टेड अकाउंट की ओर से एपीआई कॉल करेगा।

तो एक तरह से हमारी StripeService ऑब्जेक्ट एक ही फ़ाइल को कॉल करने के लिए, लेकिन अलग-अलग डेटा भेजने के लिए, मुख्य एप्लिकेशन और किरायेदारों दोनों के साथ डबल-ड्यूटी कर रहा है।

module StripeServices
 
  class CreateSubscription
 
    def initialize(params)
      @subscription_params  = params[:subscription_params]
      @stripe_account       = params[:stripe_account]
      @stripe_secret_key    = params[:stripe_secret_key] ? params[:stripe_secret_key] : (Rails.env.production? ? ENV['STRIPE_LIVE_SECRET_KEY'] : ENV['STRIPE_TEST_SECRET_KEY'])
    end
 
    def call
      subscription = Stripe::Subscription.create(@subscription_params, account_params)
    rescue Stripe::StripeError => e
      OpenStruct.new({success?: false, error: e})
    else
      OpenStruct.new({success?: true, payload: subscription})
    end
 
    private
 
      attr_reader :stripe_account, :stripe_secret_key
 
      def account_params
        {
          api_key: stripe_secret_key,
          stripe_account: stripe_account,
          stripe_version: ENV['STRIPE_API_VERSION']
        }
      end
  end
 
end

इस फ़ाइल पर कुछ तकनीकी नोट:मैं एक सरल उदाहरण साझा कर सकता था, लेकिन मुझे वास्तव में लगता है कि आपके लिए यह देखना महत्वपूर्ण है कि एक उचित सेवा वस्तु कैसे संरचित होती है, जिसमें उसकी प्रतिक्रियाएँ भी शामिल हैं।

सबसे पहले, "कॉल" विधि में बचाव और अन्य कथन होता है। यह निम्नलिखित लिखने जैसा ही है:

def call
   begin
   rescue Stripe ::StripeError  => e
   else
   end
end

लेकिन रूबी विधियां स्वचालित रूप से एक ब्लॉक को अंतर्निहित रूप से शुरू करती हैं, इसलिए प्रारंभ और अंत जोड़ने का कोई कारण नहीं है। यह कथन पढ़ता है, "सदस्यता बनाएं, यदि कोई त्रुटि है तो एक त्रुटि लौटाएं, अन्यथा सदस्यता वापस कर दें।"

सरल, संक्षिप्त और सुरुचिपूर्ण। रूबी वास्तव में एक सुंदर भाषा है और सेवा वस्तुओं का उपयोग वास्तव में इस पर प्रकाश डालता है।

मुझे आशा है कि आप हमारे अनुप्रयोगों में सेवा फ़ाइलों का महत्व देख सकते हैं। वे हमारे तर्क को व्यवस्थित करने का एक बहुत ही संक्षिप्त तरीका प्रदान करते हैं जो न केवल अनुमान लगाने योग्य है बल्कि आसानी से बनाए रखने योग्य है!

पी.एस. यदि आप रूबी मैजिक पोस्ट को प्रेस से बाहर होते ही पढ़ना चाहते हैं, तो हमारे रूबी मैजिक न्यूजलेटर की सदस्यता लें और एक भी पोस्ट मिस न करें!

——

मेरी नई किताब प्लेबुक उनतालीस - न्यूनतम टूलिंग के साथ इंटरएक्टिव वेब ऐप्स शिपिंग के लिए एक गाइड उठाकर इस अध्याय और अधिक पढ़ें। . इस पुस्तक में, मैं सामान्य पैटर्न और तकनीकों को कवर करने में एक टॉप-डाउन दृष्टिकोण लेता हूं, जो पूरी तरह से एकल-डेवलपर बिल्डिंग के रूप में मेरे पहले अनुभव पर आधारित है और कई उच्च-ट्रैफ़िक, उच्च-राजस्व वेबसाइट अनुप्रयोगों को बनाए रखता है।

कूपन कोड का प्रयोग करें appsignalrocks और 30% बचाएं!


  1. रेल के साथ कोणीय का उपयोग करना 5

    आपने पहले कहानी सुनी है। आपके पास पहले से ही आपके विकेन्द्रीकृत और पूरी तरह से काम कर रहे बैक-एंड एपीआई और किसी भी सामान्य टूलसेट से बने फ्रंट-एंड पर चलने वाला एक एप्लिकेशन है। अब, आप कोणीय पर आगे बढ़ना चाहते हैं। या, शायद आप अपनी रेल परियोजनाओं के साथ एंगुलर को एकीकृत करने का एक तरीका ढूंढ रहे हैं

  1. रूबी ऑन रेल्स क्या है और यह क्यों उपयोगी है?

    रूबी ऑन रेल्स (कभी-कभी RoR) सबसे लोकप्रिय ओपन-सोर्स वेब एप्लिकेशन फ्रेमवर्क है। इसे रूबी प्रोग्रामिंग भाषा के साथ बनाया गया है। आप अनुप्रयोगों को बनाने में मदद करने के लिए रेल का उपयोग कर सकते हैं, सरल से जटिल तक, रेल के साथ आप क्या हासिल कर सकते हैं इसकी कोई सीमा नहीं है! ढांचा क्या है? फ़्रेम

  1. रूबी फ्रीज विधि - वस्तु परिवर्तनशीलता को समझना

    किसी वस्तु के परिवर्तनशील होने का क्या अर्थ है? फैंसी शब्दों को भ्रमित न होने दें, “परिवर्तनशीलता ” का सीधा सा मतलब है कि किसी वस्तु की आंतरिक स्थिति को बदला जा सकता है। जमे हुए . को छोड़कर, यह सभी वस्तुओं का डिफ़ॉल्ट है , या वे जो विशेष वस्तुओं की सूची का हिस्सा हैं। दूसरे शब्दों में, रूबी में सभ