2013-03-19 7 views
8

मेरे पास Grape::API का एक छोटा प्रोटोटाइप सबक्लास है जो रैक सेवा के रूप में है, और मैं अपने एप्लिकेशन की आंतरिक ऑब्जेक्ट्स को प्रस्तुत करने के लिए Grape::Entity का उपयोग कर रहा हूं।रूबी अंगूर JSON-over-HTTP एपीआई, कस्टम JSON प्रतिनिधित्व

मुझे Grape::Entity डीएसएल पसंद है, लेकिन मुझे यह पता लगाने में परेशानी हो रही है कि मुझे डिफ़ॉल्ट JSON प्रतिनिधित्व से परे कैसे जाना चाहिए, जो हमारे उद्देश्यों के लिए बहुत हल्का है। मुझे "जेएसएंड या इसी तरह के प्रारूप" में आउटपुट तैयार करने के लिए कहा गया है: http://labs.omniti.com/labs/jsend

मुझे बिल्कुल यकीन नहीं है कि अंगूर ढांचे के साथ परिवर्तन की प्रकृति सबसे अधिक है (मुझे कम से कम पथ- प्रतिरोध यहाँ)। क्या मुझे एक कस्टम अंगूर फॉर्मेटर बनाना चाहिए (मुझे यह नहीं पता कि यह कैसे करना है), नया रैक मिडलवेयर (I ने SysLog के माध्यम से एपीआई इन्स/आउट लॉग करने के लिए ऐसा किया है - लेकिन प्रारूपण खराब लगता है क्योंकि मुझे पार्स करना होगा कंटेनर स्तर जोड़ने के लिए जेएसओएन से वापस शरीर), या Grape::Entity से दूर बदलें Rabl?

उदाहरण कोड ("app.rb")

require "grape" 
require "grape-entity" 

class Thing 
    def initialize llama_name 
    @llama_name = llama_name 
    end 
    attr_reader :llama_name 
end 

class ThingPresenter < Grape::Entity 
    expose :llama_name 
end 

class MainService < Grape::API 
    prefix  'api' 
    version  'v2' 
    format  :json 
    rescue_from :all 

    resource :thing do 
    get do 
     thing = Thing.new 'Henry' 
     present thing, :with => ThingPresenter 
    end 
    end 
end 

Rackup फाइल "(config.ru")

require File.join(File.dirname(__FILE__), "app") 
run MainService 

मैं इसे शुरू अप:

rackup -p 8090 

और कॉल यह:

curl http://127.0.0.1:8090/api/v2/thing 
{"llama_name":"Henry"} 

मैं देखना चाहते हैं क्या:

curl http://127.0.0.1:8090/api/v2/thing 
{"status":"success","data":{"llama_name":"Henry"}} 

जाहिर है मैं सिर्फ कुछ की तरह

resource :thing do 
    get do 
     thing = Thing.new 'Henry' 
     { :status => "success", :data => present(thing, :with => ThingPresenter) } 
    end 
    end 
हर मार्ग में

कर सकता है - लेकिन यह है कि बहुत शुष्क प्रतीत नहीं होता। मैं कुछ क्लीनर की तलाश में हूँ, और कम खुला & पेस्ट त्रुटियों कटौती करने के लिए जब इस एपीआई बड़ा और पूरी टीम द्वारा बनाए रखा हो जाता है


अजीब तरह से, जब मैं grape 0.3.2 उपयोग करने की कोशिश { :status => "success", :data => present(thing, :with => ThingPresenter) }, मैं यह काम करने के लिए नहीं मिल सका । एपीआई ने केवल present से मूल्य वापस कर दिया - शुरू में मैंने सोचा था कि यहां और भी कुछ चल रहा है।

उत्तर

14

यह है कि मैं क्या अंगूर प्रलेखन पढ़ने का एक संयोजन के माध्यम से, के साथ समाप्त हो गया, Googling है और github पर कुछ पुल अनुरोधों को पढ़ना। असल में, :json प्रारूप घोषित करने के बाद (इसके साथ आने वाली सभी अन्य डिफ़ॉल्ट उपहारों को प्राप्त करने के लिए), मैं आउटपुट फॉर्मेटर्स को उन लोगों के साथ सवारी करता हूं जो जेएसएंड की रैपर परत जोड़ते हैं। यह अंगूर के #present सहायक (जो त्रुटियों को अच्छी तरह से कवर नहीं करता है) को लपेटने की कोशिश करने से कोड के लिए बहुत साफ हो जाता है, या एक रैक मिडलवेयर समाधान (जिसके लिए डी-सीरियलाइजिंग और जेएसओएन को पुन: क्रमबद्ध करने की आवश्यकता होती है, साथ ही त्रुटियों को कवर करने के लिए बहुत सारे अतिरिक्त कोड लेते हैं)।

require "grape" 
require "grape-entity" 
require "json" 

module JSendSuccessFormatter 
    def self.call object, env 
    { :status => 'success', :data => object }.to_json 
    end 
end 

module JSendErrorFormatter 
    def self.call message, backtrace, options, env 
    # This uses convention that a error! with a Hash param is a jsend "fail", otherwise we present an "error" 
    if message.is_a?(Hash) 
     { :status => 'fail', :data => message }.to_json 
    else 
     { :status => 'error', :message => message }.to_json 
    end 
    end 
end 

class Thing 
    def initialize llama_name 
    @llama_name = llama_name 
    end 
    attr_reader :llama_name 
end 

class ThingPresenter < Grape::Entity 
    expose :llama_name 
end 

class MainService < Grape::API 
    prefix  'api' 
    version  'v2' 
    format  :json 
    rescue_from :all 

    formatter :json, JSendSuccessFormatter 
    error_formatter :json, JSendErrorFormatter 

    resource :thing do 
    get do 
     thing = Thing.new 'Henry' 
     present thing, :with => ThingPresenter 
    end 
    end 

    resource :borked do 
    get do 
     error! "You broke it! Yes, you!", 403 
    end 
    end 
end 
+0

यदि आप फ़ॉर्मेटर्स को एक अलग फ़ाइल में निकालना चाहते हैं, तो वे कहां जाएंगे, और आप उनका उपयोग कैसे करेंगे? –

+1

@dtmunir - अंगूर में डिफ़ॉल्ट या अपेक्षित संरचनाएं नहीं होती हैं (जैसे रेल के पास), इसलिए सर्वर कोड में उनका उपयोग करने का प्रयास करने से पहले सहायक मॉड्यूल वाले फाइल कहीं भी जा सकती हैं या आपको 'आवश्यकता_संबंधी' प्रदान की जा सकती है। मेरे पास ऐप के शीर्ष पर एक 'आवश्यकता' है, और बदले में मैं अंगूर ऐप के लिए परिभाषित सभी चीजों की आवश्यकता 'की आवश्यकता है। जब मैंने इसे वास्तविक के लिए किया तो मुझे केवल कुछ कस्टम हेल्पर्स की आवश्यकता थी, इसलिए मैंने उन्हें एक फाइल 'helpers.rb' - YMMV में रखा। –

1

आप इसके लिए मिडलवेयर परत का उपयोग कर सकते हैं। अंगूर में Middleware::Base मॉड्यूल है जिसका उपयोग आप इस उद्देश्य के लिए कर सकते हैं। मेरे नहीं तो सुपर सुंदर कार्यान्वयन:

class StatusAdder < Grape::Middleware::Base 

    def initialize(app) 
    @app = app 
    end 

    def call(env) 
    status, headers, response = @app.call 
    response_hash = JSON.parse response.body.first 
    body = { :status => "success", :data => response_hash } if status == 200 

    response_string = body.to_json 
    headers['Content-Length'] = response_string.length.to_s 
    [status, headers, [response_string]] 
    end 
end 

और MainService कक्षा में, आप एक लाइन जोड़ेंगे: use ::StatusAdder

+1

धन्यवाद क्या है। अभी तक यह एकमात्र विकल्प है जिसे मैं काम देख सकता हूं, लेकिन इसके साथ मेरी समस्या यह है कि मिडलवेयर दृष्टिकोण डी-सीरियलिस और जेएसओएन को फिर से क्रमबद्ध करता है। निश्चित रूप से * वापस किए गए डेटा को लपेटने का एक तरीका होना चाहिए * इससे पहले * इसे क्रमबद्ध किया गया है? –

+0

अच्छा, यह आपकी आवश्यकता पर निर्भर करेगा कि प्रतिक्रिया के वास्तविक HTTP स्थिति कोड के आधार पर 'स्थिति' भाग बदलना चाहिए या नहीं। उदाहरण के लिए, यदि प्रतिक्रिया कोड "404" है, तो क्या आप अभी भी एक JSON स्ट्रिंग भेजना चाहते हैं जिसमें "स्थिति" है: "सफलता" प्रतिक्रिया में भाग? – Kashyap

+0

मैं स्थिति बदलना चाहता हूं, और HTTP स्थिति के बीच एक मजबूत पत्राचार होगा, और JSON हैश में "स्थिति" से जुड़े मान होंगे। Jsend प्रारूप में विशिष्ट उपयोगों के साथ दो अन्य स्थिति मान, "विफल" और "त्रुटि" हैं। इनपुट विफलता त्रुटियों पर "विफल" का उपयोग किया जाता है, और इसमें संकेत मिलता है कि कौन सा पैरामीटर विफल हुआ। –

1

आज की तारीख के रूप में, मेरा मानना ​​है कि अंगूर के साथ ऐसा करने का सही तरीका है:

rescue_from Grape::Exceptions::ValidationErrors do |e| 
     response = 
     { 
      'status' => 'fail', 
      'data' => { 
       'status' => e.status, 
       'message' => e.message, 
       'errors' => e.errors 
      } 
     } 
     Rack::Response.new(response.to_json, e.status) 
    end 
+0

धन्यवाद, मैं त्रुटि फॉर्मेटर (या बस संरचना में उचित स्थिति कोड निकालने के लिए) के प्रतिस्थापन के रूप में इसका उपयोग करने में सक्षम हो सकता हूं। ध्यान दें कि अंगूर की 'त्रुटि!' विधि कोई त्रुटि नहीं उठाती है - यह 'फेंक' का उपयोग करती है - इसलिए इसे इस से कवर नहीं किया जाता है। –

1

मैं @ एक अतिरिक्त संशोधन के साथ नील-स्लेटर के समाधान का उपयोग कर रहा मैंने सोचा था कि दूसरों को उपयोगी लग सकती ।

केवल rescue_from :all के साथ आम 404 त्रुटियों के परिणाम 403 Forbidden के रूप में लौटा दिए जाते हैं। साथ ही, स्थिति 'त्रुटि' होने पर 'त्रुटि' होती है। इन समस्याओं का समाधान करने के लिए मैं RecordNotFound के लिए एक बचाव हैंडलर कहा:

rescue_from ActiveRecord::RecordNotFound do |e| 
    Rails.logger.info e.message 
    error = JSendErrorFormatter.call({message: e.message}, e.backtrace, {}, nil) 
    Rack::Response.new(error, 404, 
        { "Content-type" => "text/error" }).finish 
end 

टिप्पणी - मैं रैक env उपयोग करने के लिए उचित तरीके से समझ नहीं सका, इसलिए आप देख सकते हैं मैं इसे एक शून्य मान के रूप में में गुजर रहा (जो ठीक है क्योंकि त्रुटि हैंडलर मान का उपयोग नहीं करता है)।

मुझे लगता है कि आप प्रतिक्रिया कोड हैंडलिंग को और परिष्कृत करने के लिए इस दृष्टिकोण को आगे बढ़ा सकते हैं। मेरे लिए, मुश्किल हिस्सा यह लगा रहा था कि मुझे Rack::Response ऑब्जेक्ट की आवश्यकता है कि मैं स्वरूपित त्रुटि संदेश को पास कर सकता हूं।

+0

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

2

मेरा मानना ​​है कि पूरा करता है अपने लक्ष्य का उपयोग करते समय grape

require "grape" 
require "grape-entity" 

class Thing 
    def initialize llama_name 
    @llama_name = llama_name 
    end 
    attr_reader :llama_name 
end 

class ThingPresenter < Grape::Entity 
    expose :llama_name 
end 

class MainService < Grape::API 
    prefix  'api' 
    version  'v2' 
    format  :json 
    rescue_from :all 

    resource :thing do 
    get do 
     thing = Thing.new 'Henry' 
     present :status, 'success' 
     present :data, thing, :with => ThingPresenter 
    end 
    end 
end 
+0

यह करता है - यह सुनिश्चित नहीं करता कि वह तीन-भाग संरचना 'वर्तमान' के लिए उपलब्ध थी (चाहे मुझे याद आए, या यह पिछले वर्ष में जोड़ा गया है)। हालांकि, मुझे प्रत्येक हैंडलर को अतिरिक्त 'वर्तमान: स्थिति,' सफलता 'जोड़ने की आवश्यकता होगी। अधिक उचित रूप से यह सामान्य प्रारूप का हिस्सा है, ऐसा कुछ नहीं जिसे मैं कई बार मैन्युअल रूप से कोड करने की अपेक्षा करता हूं। –

+0

@NeilSlater मैं सहमत हूं, लेकिन चूंकि यह आरईएसटी है "आपकी" प्रतिक्रिया किसी और की तुलना में 100% अलग हो सकती है। मुझे यकीन है कि टेम्पलेट की कुछ विधि है जो पुनरावृत्ति के बिना इस लक्ष्य को पूरा कर सकती है। उम्मीद है कि यह किसी की मदद करता है! एक अच्छा लें। – abc123

संबंधित मुद्दे