دروس عن جوهرة العنب: كيفية بناء واجهة برمجة تطبيقات تشبه REST في روبي

نشرت: 2022-03-11

بصفتنا مطوري Ruby On Rails ، نحتاج غالبًا إلى توسيع نطاق تطبيقاتنا بنقاط نهاية API لدعم عملاء الإنترنت الغني بكفاءة JavaScript أو تطبيقات iPhone و Android الأصلية. هناك أيضًا بعض الحالات التي يكون فيها الغرض الوحيد للتطبيق هو خدمة تطبيقات iPhone / Android عبر واجهة برمجة تطبيقات JSON.

في هذا البرنامج التعليمي ، أوضحت كيفية استخدام Grape - إطار عمل صغير لواجهة برمجة تطبيقات تشبه REST لـ Ruby - لبناء دعم الخلفية في Rails لواجهة برمجة تطبيقات JSON. تم تصميم Grape ليعمل كمحرك رف قابل للتركيب لاستكمال تطبيقات الويب الخاصة بنا ، دون التدخل فيها.

واجهة برمجة تطبيقات الويب في Ruby باستخدام Grape Gem

حالة الاستخدام

حالة الاستخدام التي سنركز عليها في هذا البرنامج التعليمي هي تطبيق قادر على التقاط ومراجعة جلسات البرمجة الزوجية. سيتم كتابة التطبيق نفسه لنظام iOS في ObjectiveC وسيحتاج إلى الاتصال بخدمة الخلفية لتخزين البيانات واستردادها. ينصب تركيزنا في هذا البرنامج التعليمي على إنشاء خدمة خلفية قوية وآمنة تدعم واجهة برمجة تطبيقات JSON.

ستدعم واجهة برمجة التطبيقات طرقًا لـ:

  • تسجيل الدخول إلى النظام
  • الاستعلام عن مراجعات جلسة البرمجة الزوجية

ملاحظة: بالإضافة إلى توفير القدرة على الاستعلام عن مراجعات جلسة البرمجة الزوجية ، ستحتاج واجهة برمجة التطبيقات الحقيقية أيضًا إلى توفير وسيلة لإرسال مراجعات البرمجة الزوجية لإدراجها في قاعدة البيانات. نظرًا لأن دعم ذلك عبر واجهة برمجة التطبيقات خارج نطاق هذا البرنامج التعليمي ، سنفترض ببساطة أن قاعدة البيانات قد تم ملؤها بمجموعة عينة من مراجعات البرمجة الزوجية.

تشمل المتطلبات الفنية الرئيسية ما يلي:

  • يجب أن ترجع كل استدعاء لواجهة برمجة التطبيقات JSON صالحًا
  • يجب تسجيل كل استدعاء فاشل لواجهة برمجة التطبيقات مع سياق ومعلومات مناسبة لتكون قابلة للتكرار فيما بعد ، وتصحيح أخطائها إذا لزم الأمر

أيضًا ، نظرًا لأن تطبيقنا سيحتاج إلى خدمة العملاء الخارجيين ، فسنحتاج إلى الاهتمام بالأمان. وتحقيقا لهذه الغاية:

  • يجب أن يقتصر كل طلب على مجموعة فرعية صغيرة من المطورين الذين نتتبعهم
  • يجب مصادقة جميع الطلبات (بخلاف تسجيل الدخول / التسجيل)

اختبار التنمية مدفوعة و RSpec

سوف نستخدم التطوير المدفوع بالاختبار (TDD) كنهجنا لتطوير البرمجيات للمساعدة في ضمان السلوك الحتمي لواجهة برمجة التطبيقات الخاصة بنا.

لأغراض الاختبار ، سنستخدم RSpec ، وهو إطار اختبار معروف جيدًا في مجتمع RubyOnRails. لذلك سأشير في هذه المقالة إلى "المواصفات" بدلاً من "الاختبارات".

تتكون منهجية الاختبار الشامل من اختبارات "إيجابية" و "سلبية". ستحدد المواصفات السلبية ، على سبيل المثال ، كيفية عمل نقطة نهاية واجهة برمجة التطبيقات إذا كانت بعض المعلمات مفقودة أو غير صحيحة. تغطي المواصفات الإيجابية الحالات التي يتم فيها استدعاء API بشكل صحيح.

ابدء

دعنا نضع الأساس لواجهة برمجة التطبيقات الخلفية الخاصة بنا. أولاً ، نحتاج إلى إنشاء تطبيق ريلز جديد:

 rails new toptal_grape_blog

بعد ذلك ، سنقوم بتثبيت RSpec عن طريق إضافة rspec-rails إلى ملف gemfile الخاص بنا:

 group :development, :test do gem 'rspec-rails', '~> 3.2' end

ثم من سطر الأوامر ، نحتاج إلى تشغيل:

 rails generate rspec:install

يمكننا أيضًا الاستفادة من بعض البرامج مفتوحة المصدر الحالية لإطار الاختبار الخاص بنا. خاصة:

  • ابتكار - حل مصادقة مرن لـ Rails استنادًا إلى Warden
  • factory_girl_rails - يوفر تكامل Rails لـ factory_girl ، مكتبة لإعداد كائنات Ruby كبيانات اختبار

الخطوة 1: أضف هذه إلى ملف gemfile الخاص بنا:

 ... gem 'devise' ... group :development, :test do ... gem 'factory_girl_rails', '~> 4.5' ... end

الخطوة 2: إنشاء نموذج مستخدم ، وتهيئة devise المبتكرة ، وإضافتها إلى نموذج المستخدم (يتيح ذلك استخدام فئة المستخدم للمصادقة).

 rails g model user rails generate devise:install rails generate devise user

الخطوة 3: قم بتضمين طريقة بناء الجملة factory_girl في rails_helper.rb من أجل استخدام النسخة المختصرة لإنشاء المستخدم في المواصفات الخاصة بنا:

 RSpec.configure do |config| config.include FactoryGirl::Syntax::Methods

الخطوة الرابعة: قم بإضافة جوهرة العنب إلى DSL وقم بتثبيته:

 gem 'grape' bundle

حالة استخدام تسجيل دخول المستخدم والمواصفات

ستحتاج الخلفية الخاصة بنا إلى دعم إمكانية تسجيل الدخول الأساسية. لنقم بإنشاء الهيكل العظمي لـ login_spec الخاص بنا ، على افتراض أن طلب تسجيل الدخول الصالح يتكون من عنوان بريد إلكتروني مسجل وزوج كلمة مرور:

 require 'rails_helper' describe '/api/login' do context 'negative tests' do context 'missing params' do context 'password' do end context 'email' do end end context 'invalid params' do context 'incorrect password' do end context 'with a non-existent login' do end end end context 'positive tests' do context 'valid params' do end end end

إذا كان أي من المعلمات مفقودًا ، يجب أن يتلقى العميل رمز حالة إرجاع HTTP من 400 (أي طلب غير صالح) ، إلى جانب رسالة خطأ "البريد الإلكتروني مفقود" أو "كلمة المرور مفقودة".

بالنسبة للاختبار الذي أجريناه ، سننشئ مستخدمًا صالحًا ونضبط البريد الإلكتروني وكلمة المرور للمستخدم كمعلمات أصلية لمجموعة الاختبار هذه. بعد ذلك سنخصص تجزئة المعلمة هذه لكل مواصفات محددة إما عن طريق حذف كلمة المرور / البريد الإلكتروني أو تجاوزها.

دعونا ننشئ المستخدم والمعلمة تجزئة في بداية المواصفات. سنضع هذا الرمز بعد كتلة الوصف:

 describe '/api/login' do let(:email) { user.email } let(:password) { user.password } let!(:user) { create :user } let(:original_params) { { email: email, password: password } } let(:params) { original_params } ...

يمكننا بعد ذلك توسيع سياق "المعلمات المفقودة" / "كلمة المرور" على النحو التالي:

 let(:params) { original_params.except(:password) } it_behaves_like '400' it_behaves_like 'json result' it_behaves_like 'contains error msg', 'password is missing'

ولكن بدلاً من تكرار التوقعات عبر سياقات "البريد الإلكتروني" و "كلمة المرور" ، يمكننا استخدام نفس الأمثلة المشتركة مثل التوقعات. لهذا ، نحتاج إلى إلغاء تعليق هذا السطر في ملف rails_helper.rb بنا:

 Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }

نحتاج بعد ذلك إلى إضافة أمثلة RSpec المشتركة الثلاثة إلى spec/support/shared.rb .

 RSpec.shared_examples 'json result' do specify 'returns JSON' do api_call params expect { JSON.parse(response.body) }.not_to raise_error end end RSpec.shared_examples '400' do specify 'returns 400' do api_call params expect(response.status).to eq(400) end end RSpec.shared_examples 'contains error msg' do |msg| specify "error msg is #{msg}" do api_call params json = JSON.parse(response.body) expect(json['error_msg']).to eq(msg) end end

تستدعي هذه الأمثلة المشتركة طريقة api_call التي تمكننا من تحديد نقطة نهاية واجهة برمجة التطبيقات مرة واحدة فقط في مواصفاتنا (تمشيا مع مبدأ DRY). نحدد هذه الطريقة على النحو التالي:

 describe '/api/login' do ... def api_call *params post "/api/login", *params end ...

سنحتاج أيضًا إلى تخصيص المصنع لمستخدمنا:

 FactoryGirl.define do factory :user do password "Passw0rd" password_confirmation { |u| u.password } sequence(:email) { |n| "test#{n}@example.com" } end end

وأخيرًا ، قبل تشغيل المواصفات الخاصة بنا ، نحتاج إلى تشغيل عمليات الترحيل الخاصة بنا:

 rake db:migrate

ومع ذلك ، ضع في اعتبارك أن المواصفات ستظل تفشل في هذه المرحلة ، نظرًا لأننا لم ننفذ نقطة نهاية API الخاصة بنا بعد. هذا بعد ذلك.

تنفيذ نقطة نهاية واجهة برمجة تطبيقات تسجيل الدخول

بالنسبة للمبتدئين ، سنكتب هيكلاً فارغًا لواجهة برمجة تطبيقات تسجيل الدخول الخاصة بنا ( app/api/login.rb ):

 class Login < Grape::API format :json desc 'End-points for the Login' namespace :login do desc 'Login via email and password' params do requires :email, type: String, desc: 'email' requires :password, type: String, desc: 'password' end post do end end end

بعد ذلك ، سنكتب فئة مجمِّع تجمع نقاط نهاية واجهة برمجة التطبيقات ( app/api/api.rb ):

 class API < Grape::API prefix 'api' mount Login end

حسنًا ، يمكننا الآن تحميل واجهة برمجة التطبيقات الخاصة بنا في المسارات:

 Rails.application.routes.draw do ... mount API => '/' ... end

الآن دعنا نضيف الكود للتحقق من المعلمات المفقودة. يمكننا إضافة هذا الكود إلى api.rb عن طريق الإنقاذ من Grape::Exceptions::ValidationErrors .

 rescue_from Grape::Exceptions::ValidationErrors do |e| rack_response({ status: e.status, error_msg: e.message, }.to_json, 400) end

بالنسبة لكلمة المرور غير الصالحة ، سوف نتحقق مما إذا كان رمز استجابة http هو 401 مما يعني الوصول غير المصرح به. دعنا نضيف هذا إلى سياق "كلمة المرور غير الصحيحة":

 let(:params) { original_params.merge(password: 'invalid') } it_behaves_like '401' it_behaves_like 'json result' it_behaves_like 'contains error msg', 'Bad Authentication Parameters'

ثم يُضاف نفس المنطق إلى سياق "مع تسجيل دخول غير موجود" أيضًا.

نقوم بعد ذلك بتنفيذ المنطق الذي يعالج محاولات المصادقة غير الصالحة في login.rb بنا على النحو التالي:

 post do user = User.find_by_email params[:email] if user.present? && user.valid_password?(params[:password]) else error_msg = 'Bad Authentication Parameters' error!({ 'error_msg' => error_msg }, 401) end end

في هذه المرحلة ، ستعمل جميع المواصفات السلبية لواجهة برمجة تطبيقات تسجيل الدخول بشكل صحيح ، لكننا ما زلنا بحاجة إلى دعم المواصفات الإيجابية لواجهة برمجة تطبيقات تسجيل الدخول الخاصة بنا. ستتوقع مواصفاتنا الإيجابية أن تعرض نقطة النهاية رمز استجابة HTTP يبلغ 200 (أي نجاح) مع JSON صالح ورمز مميز صالح:

 it_behaves_like '200' it_behaves_like 'json result' specify 'returns the token as part of the response' do api_call params expect(JSON.parse(response.body)['token']).to be_present end

دعنا نضيف أيضًا توقع كود الاستجابة 200 إلى spec/support/shared.rb :

 RSpec.shared_examples '200' do specify 'returns 200' do api_call params expect(response.status).to eq(200) end end

في حالة تسجيل الدخول بنجاح ، سنقوم بإرجاع أول مصادقة صالحة مع البريد الإلكتروني للمستخدم بهذا التنسيق:

 {'email':<the_email_of_the_user>, 'token':<the users first valid token>}

إذا لم يكن هناك مثل هذا الرمز المميز حتى الآن ، فسننشئ واحدًا للمستخدم الحالي:

 ... if user.present? && user.valid_password?(params[:password]) token = user.authentication_tokens.valid.first || AuthenticationToken.generate(user) status 200 else ...

لكي يعمل هذا ، سنحتاج إلى فئة AuthenticationToken التي تنتمي إلى المستخدم. سننشئ هذا النموذج ، ثم ننفذ الترحيل المقابل:

 rails g model authentication_token token user:references expires_at:datetime rake db:migrate

نحتاج أيضًا إلى إضافة الارتباط المقابل إلى نموذج المستخدم الخاص بنا:

 class User < ActiveRecord::Base has_many :authentication_tokens end

ثم سنضيف النطاق الصالح إلى فئة AuthenticationToken :

 class AuthenticationToken < ActiveRecord::Base belongs_to :user validates :token, presence: true scope :valid, -> { where{ (expires_at == nil) | (expires_at > Time.zone.now) } } end

لاحظ أننا استخدمنا بناء جملة Ruby في بيان where . يتم تمكين ذلك من خلال squeel التي تتيح دعم بناء جملة روبي في استعلامات Activerecord.

بالنسبة للمستخدم الذي تم التحقق من صحته ، سننشئ كيانًا سنشير إليه باسم "المستخدم مع كيان الرمز المميز" ، مع الاستفادة من ميزات جوهرة grape-entity .

لنكتب المواصفات الخاصة بكياننا ونضعها في ملف user_with_token_entity_spec.rb :

 require 'rails_helper' describe Entities::UserWithTokenEntity do describe 'fields' do subject(:subject) { Entities::UserWithTokenEntity } specify { expect(subject).to represent(:email)} let!(:token) { create :authentication_token } specify 'presents the first available token' do json = Entities::UserWithTokenEntity.new(token.user).as_json expect(json[:token]).to be_present end end end

بعد ذلك ، دعنا نضيف الكيانات إلى user_entity.rb :

 module Entities class UserEntity < Grape::Entity expose :email end end

وأخيرًا ، أضف فئة أخرى إلى user_with_token_entity.rb :

 module Entities class UserWithTokenEntity < UserEntity expose :token do |user, options| user.authentication_tokens.valid.first.token end end end

نظرًا لأننا لا نريد أن تظل الرموز المميزة صالحة إلى أجل غير مسمى ، فقد انتهت صلاحيتها بعد يوم واحد:

 FactoryGirl.define do factory :authentication_token do token "MyString" expires_at 1.day.from_now user end end

بعد الانتهاء من كل هذا ، يمكننا الآن إرجاع تنسيق JSON المتوقع باستخدام UserWithTokenEntity المكتوب حديثًا:

 ... user = User.find_by_email params[:email] if user.present? && user.valid_password?(params[:password]) token = user.authentication_tokens.valid.first || AuthenticationToken.generate(user) status 200 present token.user, with: Entities::UserWithTokenEntity else ...

بارد. تم الآن تمرير جميع المواصفات الخاصة بنا ويتم دعم المتطلبات الوظيفية لنقطة نهاية API لتسجيل الدخول الأساسية.

نقطة نهاية واجهة برمجة التطبيقات لمراجعة جلسة البرمجة الزوجية: الشروع في العمل

ستحتاج الخلفية الخاصة بنا إلى السماح للمطورين المعتمدين الذين قاموا بتسجيل الدخول للاستعلام عن مراجعات جلسة البرمجة الزوجية.

سيتم تثبيت نقطة نهاية API الجديدة الخاصة بنا على /api/pair_programming_session وستقوم بإرجاع المراجعات الخاصة بالمشروع. لنبدأ بكتابة هيكل أساسي لهذه المواصفات:

 require 'rails_helper' describe '/api' do describe '/pair_programming_session' do def api_call *params get '/api/pair_programming_sessions', *params end context 'invalid params' do end context 'valid params' do end end end

سنكتب نقطة نهاية API فارغة مقابلة ( app/api/pair_programming_sessions.rb ) أيضًا:

 class PairProgrammingSessions < Grape::API format :json desc 'End-points for the PairProgrammingSessions' namespace :pair_programming_sessions do desc 'Retrieve the pairprogramming sessions' params do requires :token, type: String, desc: 'email' end get do end end end

ثم لنقم بتركيب واجهة برمجة التطبيقات الجديدة ( app/api/api.rb ):

 ... mount Login mount PairProgrammingSessions end

دعنا نوسع المواصفات ، ونقطة نهاية API ، مقابل المتطلبات واحدًا تلو الآخر.

نقطة نهاية واجهة برمجة تطبيقات مراجعة جلسة البرمجة الزوجية: التحقق من الصحة

كان أحد أهم متطلبات الأمان غير الوظيفية هو تقييد وصول واجهة برمجة التطبيقات إلى مجموعة فرعية صغيرة من المطورين الذين نتتبعهم ، لذلك دعنا نحدد ما يلي:

 ... def api_call *params get '/api/pair_programming_sessions', *params end let(:token) { create :authentication_token } let(:original_params) { { token: token.token} } let(:params) { original_params } it_behaves_like 'restricted for developers' context 'invalid params' do ...

سننشئ بعد ذلك نموذجًا shared.rb shared_example أن الطلب وارد من أحد المطورين المسجلين لدينا:

 RSpec.shared_examples 'restricted for developers' do context 'without developer key' do specify 'should be an unauthorized call' do api_call params expect(response.status).to eq(401) end specify 'error code is 1001' do api_call params json = JSON.parse(response.body) expect(json['error_code']).to eq(ErrorCodes::DEVELOPER_KEY_MISSING) end end end

سنحتاج أيضًا إلى إنشاء فئة ErrorCodes (في app/models/error_codes.rb ):

 module ErrorCodes DEVELOPER_KEY_MISSING = 1001 end

نظرًا لأننا نتوقع أن تتوسع واجهة برمجة التطبيقات الخاصة بنا في المستقبل ، فسنقوم بتنفيذ authorization_helper والتي يمكن إعادة استخدامها عبر جميع نقاط نهاية API في التطبيق لتقييد الوصول إلى المطورين المسجلين فقط:

 class PairProgrammingSessions < Grape::API helpers ApiHelpers::AuthenticationHelper before { restrict_access_to_developers }

سنقوم بتعريف طريقة restrict_access_to_developers في وحدة ApiHelpers::AuthenticationHerlper ( app/api/api_helpers/authentication_helper.rb ). ستتحقق هذه الطريقة ببساطة مما إذا كان Authorization المفتاح الموجود تحت الرؤوس يحتوي على ApiKey صالح. (كل مطور يرغب في الوصول إلى واجهة برمجة التطبيقات سيطلب ApiKey صالحًا. يمكن أن يتم توفير ذلك من قبل مسؤول النظام أو عبر بعض عمليات التسجيل الآلي ، ولكن هذه الآلية خارج نطاق هذه المقالة.)

 module ApiHelpers module AuthenticationHelper def restrict_access_to_developers header_token = headers['Authorization'] key = ApiKey.where{ token == my{ header_token } } Rails.logger.info "API call: #{headers}\tWith params: #{params.inspect}" if ENV['DEBUG'] if key.blank? error_code = ErrorCodes::DEVELOPER_KEY_MISSING error_msg = 'please aquire a developer key' error!({ :error_msg => error_msg, :error_code => error_code }, 401) # LogAudit.new({env:env}).execute end end end end

نحتاج بعد ذلك إلى إنشاء نموذج ApiKey وتشغيل عمليات الترحيل: Rails g model api_key token rake db: migrate

بعد القيام بذلك ، في spec/api/pair_programming_spec.rb يمكننا التحقق مما إذا كان المستخدم قد تمت مصادقته:

 ... it_behaves_like 'restricted for developers' it_behaves_like 'unauthenticated' ...

دعنا أيضًا نحدد مثالًا مشتركًا غير مصدق يمكن إعادة استخدامه عبر جميع المواصفات ( spec/support/shared.rb unauthenticated :

 RSpec.shared_examples 'unauthenticated' do context 'unauthenticated' do specify 'returns 401 without token' do api_call params.except(:token), developer_header expect(response.status).to eq(401) end specify 'returns JSON' do api_call params.except(:token), developer_header json = JSON.parse(response.body) end end end

يحتاج هذا المثال المشترك إلى الرمز المميز في رأس المطور ، لذا دعنا نضيف ذلك إلى المواصفات الخاصة بنا ( spec/api/pair_programming_spec.rb ):

 ... describe '/api' do let(:api_key) { create :api_key } let(:developer_header) { {'Authorization' => api_key.token} } ...

الآن ، في app/api/pair_programming_session.rb ، دعنا نحاول مصادقة المستخدم:

 ... class PairProgrammingSessions < Grape::API helpers ApiHelpers::AuthenticationHelper before { restrict_access_to_developers } before { authenticate! } ...

دعونا ننفذ authenticate! الطريقة في AuthenticationHelper ( app/api/api_helpers/authentication_helper.rb ):

 ... module ApiHelpers module AuthenticationHelper TOKEN_PARAM_NAME = :token def token_value_from_request(token_param = TOKEN_PARAM_NAME) params[token_param] end def current_user token = AuthenticationToken.find_by_token(token_value_from_request) return nil unless token.present? @current_user ||= token.user end def signed_in? !!current_user end def authenticate! unless signed_in? AuditLog.create data: 'unauthenticated user access' error!({ :error_msg => "authentication_error", :error_code => ErrorCodes::BAD_AUTHENTICATION_PARAMS }, 401) end end ...

(لاحظ أننا بحاجة إلى إضافة رمز الخطأ BAD_AUTHENTICATION_PARAMS إلى فئة أكواد ErrorCodes الخاصة بنا.)

بعد ذلك ، دعنا نحدد ما يحدث إذا قام المطور باستدعاء API برمز مميز غير صالح. في هذه الحالة ، سيكون رمز الإرجاع 401 للإشارة إلى "وصول غير مصرح به". يجب أن تكون النتيجة JSON ويجب إنشاء ملف قابل للتدقيق. لذلك نضيف هذا إلى spec/api/pair_programming_spec.rb :

 ... context 'invalid params' do context 'incorrect token' do let(:params) { original_params.merge(token: 'invalid') } it_behaves_like '401' it_behaves_like 'json result' it_behaves_like 'auditable created' it_behaves_like 'contains error msg', 'authentication_error' it_behaves_like 'contains error code', ErrorCodes::BAD_AUTHENTICATION_PARAMS end end ...

سنضيف الأمثلة المشتركة "تم إنشاؤه قابل للتدقيق" و "يحتوي على رمز خطأ" و "يحتوي على رسالة خطأ" إلى spec/support/shared.rb :

 ... RSpec.shared_examples 'contains error code' do |code| specify "error code is #{code}" do api_call params, developer_header json = JSON.parse(response.body) expect(json['error_code']).to eq(code) end end RSpec.shared_examples 'contains error msg' do |msg| specify "error msg is #{msg}" do api_call params, developer_header json = JSON.parse(response.body) expect(json['error_msg']).to eq(msg) end end RSpec.shared_examples 'auditable created' do specify 'creates an api call audit' do expect do api_call params, developer_header end.to change{ AuditLog.count }.by(1) end end ...

نحتاج أيضًا إلى إنشاء نموذج Audit_log:

 rails g model audit_log backtrace data user:references rake db:migrate

نقطة نهاية واجهة برمجة تطبيقات مراجعة جلسة البرمجة الزوجية: إرجاع النتائج

بالنسبة للمستخدم المعتمد والمصرح له ، يجب أن تؤدي المكالمة إلى نقطة نهاية واجهة برمجة التطبيقات هذه إلى إرجاع مجموعة من مراجعات جلسة البرمجة الزوجية المجمعة حسب المشروع. دعنا نعدل spec/api/pair_programming_spec.rb وفقًا لذلك:

 ... context 'valid params' do it_behaves_like '200' it_behaves_like 'json result' end ...

يحدد هذا أن الطلب المقدم مع api_key صالح ومعلمات صالحة يرجع رمز HTTP 200 (أي نجاح) وأن النتيجة يتم إرجاعها في شكل JSON صالح.

سنقوم بالاستعلام ثم العودة بتنسيق JSON لجلسات البرمجة الزوجية هذه حيث يكون أي من المشاركين هو المستخدم الحالي ( app/api/pair_programming_sessions.rb ):

 ... get do sessions = PairProgrammingSession.where{(host_user == my{current_user}) | (visitor_user == my{current_user})} sessions = sessions.includes(:project, :host_user, :visitor_user, reviews: [:code_samples, :user] ) present sessions, with: Entities::PairProgrammingSessionsEntity end ...

تم تصميم جلسات البرمجة الزوجية على النحو التالي في قاعدة البيانات:

  • علاقة واحد بأطراف بين المشاريع وجلسات البرمجة الزوجية
  • علاقة واحد لأكثر بين جلسات البرمجة الزوجية والمراجعات
  • علاقة واحد بأطراف بين المراجعات وعينات التعليمات البرمجية

دعنا ننشئ النماذج وفقًا لذلك ثم ننفذ عمليات الترحيل:

 rails g model project name rails g model pair_programming_session project:references host_user:references visitor_user:references rails g model review pair_programming_session:references user:references comment rails g model code_sample review:references code:text rake db:migrate

ثم نحتاج إلى تعديل فئات PairProgrammingSession Review لتحتوي على has_many من الارتباطات:

 class Review < ActiveRecord::Base belongs_to :pair_programming_session belongs_to :user has_many :code_samples end class PairProgrammingSession < ActiveRecord::Base belongs_to :project belongs_to :host_user, class_name: :User belongs_to :visitor_user, class_name: 'User' has_many :reviews end

ملاحظة: في الظروف العادية ، سأُنشئ هذه الفئات عن طريق كتابة المواصفات لها أولاً ، ولكن نظرًا لأن هذا خارج نطاق هذه المقالة ، سأتخطى هذه الخطوة.

نحتاج الآن إلى كتابة تلك الفئات التي ستحول نماذجنا إلى تمثيلات JSON الخاصة بهم (يشار إليها باسم كيانات العنب في مصطلحات العنب). من أجل التبسيط ، سنستخدم رسم خرائط 1 إلى 1 بين النماذج وكيانات العنب.

نبدأ بكشف حقل code من CodeSampleEntity (في api/entities/code_sample_entity.rb ):

 module Entities class CodeSampleEntity < Grape::Entity expose :code end end

ثم نكشف عن user وما يرتبط به من code_samples عن طريق إعادة استخدام UserEntity المحدد بالفعل و CodeSampleEntity :

 module Entities class ReviewEntity < Grape::Entity expose :user, using: UserEntity expose :code_samples, using: CodeSampleEntity end end

نكشف أيضًا عن حقل name من ProjectEntity :

 module Entities class ProjectEntity < Grape::Entity expose :name end end

أخيرًا ، نقوم بتجميع الكيان في PairProgrammingSessionsEntity جديدة حيث نعرض project host_user visitor_user reviews :

 module Entities class PairProgrammingSessionsEntity < Grape::Entity expose :project, using: ProjectEntity expose :host_user, using: UserEntity expose :visitor_user, using: UserEntity expose :reviews, using: ReviewEntity end end

وبهذا ، يتم تنفيذ API الخاص بنا بالكامل!

توليد بيانات الاختبار

لأغراض الاختبار ، سننشئ بعض عينات البيانات في db/seeds.rb . يجب أن يحتوي هذا الملف على كل عمليات إنشاء السجلات اللازمة لبذر قاعدة البيانات بقيمها الافتراضية. يمكن بعد ذلك تحميل البيانات باستخدام rake db:seed (أو إنشاؤها باستخدام db عند استدعاء db:setup ). فيما يلي مثال على ما يمكن أن يشمله هذا:

 user_1 = User.create email: '[email protected]', password: 'password', password_confirmation: 'password' user_2 = User.create email: '[email protected]', password: 'password', password_confirmation: 'password' user_3 = User.create email: '[email protected]', password: 'password', password_confirmation: 'password' ApiKey.create token: '12345654321' project_1 = Project.create name: 'Time Sheets' project_2 = Project.create name: 'Toptal Blog' project_3 = Project.create name: 'Hobby Project' session_1 = PairProgrammingSession.create project: project_1, host_user: user_1, visitor_user: user_2 session_2 = PairProgrammingSession.create project: project_2, host_user: user_1, visitor_user: user_3 session_3 = PairProgrammingSession.create project: project_3, host_user: user_2, visitor_user: user_3 review_1 = session_1.reviews.create user: user_1, comment: 'Please DRY a bit your code' review_2 = session_1.reviews.create user: user_1, comment: 'Please DRY a bit your specs' review_3 = session_2.reviews.create user: user_1, comment: 'Please DRY your view templates' review_4 = session_2.reviews.create user: user_1, comment: 'Please clean your N+1 queries' review_1.code_samples.create code: 'Lorem Ipsum' review_1.code_samples.create code: 'Do not abuse the single responsibility principle' review_2.code_samples.create code: 'Use some shared examples' review_2.code_samples.create code: 'Use at the beginning of specs'

الآن تطبيقنا جاهز للاستخدام ويمكننا تشغيل خادم ريلز الخاص بنا.

اختبار API

سنستخدم Swagger لإجراء بعض الاختبارات اليدوية المعتمدة على المتصفح لواجهة برمجة التطبيقات الخاصة بنا. بعض خطوات الإعداد مطلوبة حتى نتمكن من الاستفادة من Swagger بالرغم من ذلك.

أولاً ، نحتاج إلى إضافة زوجين من الأحجار الكريمة إلى ملف الأحجار الكريمة الخاص بنا:

 ... gem 'grape-swagger' gem 'grape-swagger-ui' ...

ثم نقوم بتشغيل bundle لتثبيت هذه الأحجار الكريمة.

نحتاج أيضًا إلى إضافة هذه إلى الأصول إلى خط أنابيب الأصول (في config/initializers/assets.rb ):

 Rails.application.config.assets.precompile += %w( swagger_ui.js ) Rails.application.config.assets.precompile += %w( swagger_ui.css )

أخيرًا ، في app/api/api.rb ، نحتاج إلى تحميل مُنشئ Swagger:

 ... add_swagger_documentation end ...

يمكننا الآن الاستفادة من واجهة مستخدم Swagger الرائعة لاستكشاف واجهة برمجة التطبيقات الخاصة بنا من خلال الانتقال إلى http://localhost:3000/api/swagger .

يقدم Swagger نقاط نهاية API الخاصة بنا بطريقة سهلة الاستكشاف. إذا نقرنا على نقطة نهاية ، فإن Swagger يسرد عملياته. إذا نقرنا على عملية ما ، فسيعرض Swagger المعلمات المطلوبة والاختيارية وأنواع بياناتها.

هناك تفصيل واحد متبقي قبل المتابعة: نظرًا لأننا قيدنا استخدام مطوري واجهة برمجة التطبيقات api_key صالح ، فلن نتمكن من الوصول إلى نقطة نهاية واجهة برمجة التطبيقات مباشرةً من المتصفح لأن الخادم سيتطلب api_key صالحًا في رأس HTTP. يمكننا تحقيق ذلك لأغراض الاختبار في Google Chrome من خلال الاستفادة من تعديل الرؤوس لمكوِّن Google Chrome الإضافي. سيمكننا هذا المكون الإضافي من تحرير رأس HTTP وإضافة api_key صالح (سنستخدم api_key وهمي لـ 12345654321 الذي قمنا بتضمينه في ملف أساسي لقاعدة البيانات الخاصة بنا).

حسنًا ، نحن الآن جاهزون للاختبار!

من أجل الاتصال بنقطة نهاية API pair_programming_sessions ، نحتاج أولاً إلى تسجيل الدخول. سنستخدم فقط مجموعات البريد الإلكتروني وكلمة المرور من ملف قاعدة البيانات الأولية الخاصة بنا وإرسالها عبر Swagger إلى نقطة نهاية واجهة برمجة تطبيقات تسجيل الدخول ، كما هو موضح أدناه.

كما ترى أعلاه ، يتم إرجاع الرمز المميز الخاص بهذا المستخدم ، مما يشير إلى أن واجهة برمجة تطبيقات تسجيل الدخول تعمل بشكل صحيح على النحو المنشود. يمكننا الآن استخدام هذا الرمز المميز لإجراء عملية GET /api/pair_programming_sessions.json .

كما هو موضح ، يتم إرجاع النتيجة ككائن JSON هرمي منسق بشكل صحيح. لاحظ أن بنية JSON تعكس ارتباطات متداخلة من 1 إلى عدة ، نظرًا لأن المشروع يحتوي على مراجعات متعددة ، وللمراجعة عينات تعليمات برمجية متعددة. إذا لم نعيد البنية بهذه الطريقة ، فسيحتاج من يستدعي واجهة برمجة التطبيقات (API) الخاصة بنا إلى طلب المراجعات بشكل منفصل لكل مشروع مما قد يتطلب إرسال استعلامات N إلى نقطة نهاية API الخاصة بنا. من خلال هذه البنية ، نقوم بالتالي بحل مشكلة أداء استعلام N + 1.

يتم إحتوائه

كما هو موضح هنا ، تساعد المواصفات الشاملة لواجهة برمجة التطبيقات (API) الخاصة بك على ضمان أن واجهة برمجة التطبيقات التي تم تنفيذها تعالج بشكل صحيح وكاف حالات الاستخدام المقصودة (وغير المقصودة!).

في حين أن مثال API المقدم في هذا البرنامج التعليمي أساسي إلى حد ما ، فإن النهج والتقنيات التي أظهرناها يمكن أن تكون بمثابة الأساس لواجهات برمجة التطبيقات الأكثر تعقيدًا ذات التعقيد التعسفي باستخدام Grape gem. من المأمول أن يوضح هذا البرنامج التعليمي أن Grape هو جوهرة مفيدة ومرنة يمكن أن تساعد في تسهيل تنفيذ JSON API في تطبيقات Rails الخاصة بك. استمتع!