Tutoriel Grape Gem : comment créer une API de type REST dans Ruby

Publié: 2022-03-11

En tant que développeurs Ruby On Rails, nous avons souvent besoin d'étendre nos applications avec des points de terminaison d'API pour prendre en charge les clients Internet riches en JavaScript ou les applications iPhone et Android natives. Il existe également des cas où le seul but de l'application est de servir les applications iPhone/Android via une API JSON.

Dans ce didacticiel, je montre comment utiliser Grape - un micro-framework d'API de type REST pour Ruby - pour créer un support backend dans Rails pour une API JSON. Grape est conçu pour fonctionner comme un moteur de rack montable pour compléter nos applications Web, sans les interférer .

API Web en Ruby utilisant Grape Gem

Cas d'utilisation

Le cas d'utilisation sur lequel nous allons nous concentrer pour ce tutoriel est une application capable de capturer et de réviser des sessions de programmation en binôme. L'application elle-même sera écrite pour iOS en ObjectiveC et devra communiquer avec un service backend pour stocker et récupérer les données. Dans ce didacticiel, nous nous concentrons sur la création d'un service backend robuste et sécurisé prenant en charge une API JSON.

L'API prendra en charge les méthodes pour :

  • Connexion au système
  • Interroger les révisions des sessions de programmation en binôme

REMARQUE : En plus de fournir la possibilité d'interroger les révisions de session de programmation en binôme, la véritable API devrait également fournir une fonction permettant de soumettre des révisions de programmation en binôme pour inclusion dans la base de données. Étant donné que la prise en charge de cela via l'API dépasse le cadre de ce didacticiel, nous supposerons simplement que la base de données a été remplie avec un échantillon d'examens de programmation en binôme.

Les principales exigences techniques comprennent :

  • Chaque appel d'API doit renvoyer un JSON valide
  • Chaque appel d'API échoué doit être enregistré avec un contexte et des informations adéquats pour être ensuite reproductible et débogué si nécessaire

De plus, puisque notre application devra servir des clients externes, nous devrons nous préoccuper de la sécurité. Vers cette fin :

  • Chaque demande doit être limitée à un petit sous-ensemble de développeurs que nous suivons
  • Toutes les demandes (autres que la connexion/l'inscription) doivent être authentifiées

Développement piloté par les tests et RSpec

Nous utiliserons le Test Driven Development (TDD) comme approche de développement logiciel pour garantir le comportement déterministe de notre API.

À des fins de test, nous utiliserons RSpec, un framework de test bien connu dans la communauté RubyOnRails. Je parlerai donc dans cet article de « specs » plutôt que de « tests ».

Une méthodologie de test complète comprend à la fois des tests « positifs » et « négatifs ». Les spécifications négatives spécifieront, par exemple, comment le point de terminaison de l'API se comporte si certains paramètres sont manquants ou incorrects. Les spécifications positives couvrent les cas où l'API est invoquée correctement.

Commencer

Jetons les bases de notre API backend. Tout d'abord, nous devons créer une nouvelle application rails :

 rails new toptal_grape_blog

Ensuite, nous allons installer RSpec en ajoutant rspec-rails dans notre gemfile :

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

Ensuite, à partir de notre ligne de commande, nous devons exécuter :

 rails generate rspec:install

Nous pouvons également tirer parti de certains logiciels open source existants pour notre cadre de test. Spécifiquement:

  • Devise - une solution d'authentification flexible pour Rails basée sur Warden
  • factory_girl_rails - fournit l'intégration Rails pour factory_girl, une bibliothèque pour configurer des objets Ruby en tant que données de test

Étape 1 : Ajoutez-les dans notre gemfile :

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

Étape 2 : générer un modèle utilisateur, initialiser la devise de conception et l'ajouter au modèle utilisateur (cela permet d'utiliser la classe d'utilisateurs pour l'authentification).

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

Étape 3 : incluez la méthode de syntaxe factory_girl dans notre fichier rails_helper.rb afin d'utiliser la version abrégée de la création d'utilisateur dans nos spécifications :

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

Étape 4 : Ajoutez la gemme de raisin à notre DSL et installez-la :

 gem 'grape' bundle

Cas d'utilisation et spécifications de connexion utilisateur

Notre backend devra prendre en charge une capacité de connexion de base. Créons le squelette de notre login_spec , en supposant qu'une demande de connexion valide consiste en une adresse e-mail enregistrée et une paire de mot de passe :

 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

Si l'un des paramètres est manquant, le client doit recevoir un code d'état de retour HTTP de 400 (c'est-à-dire, Demande incorrecte), ainsi qu'un message d'erreur « e-mail manquant » ou « mot de passe manquant ».

Pour notre test, nous allons créer un utilisateur valide et définir l'e-mail et le mot de passe de l'utilisateur comme paramètres d'origine pour cette suite de tests. Ensuite, nous personnaliserons ce paramètre de hachage pour chaque spécification spécifique en omettant le mot de passe/e-mail ou en le remplaçant.

Créons l'utilisateur et le hash de paramètre au début de la spécification. Nous mettrons ce code après le bloc describe :

 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 } ...

Nous pouvons alors étendre notre contexte 'missing params'/'password' comme suit :

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

Mais au lieu de répéter les attentes dans les contextes « e-mail » et « mot de passe », nous pouvons utiliser les mêmes exemples partagés comme attentes. Pour cela, nous devons décommenter cette ligne dans notre fichier rails_helper.rb :

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

Nous devons ensuite ajouter les 3 exemples partagés RSpec dans 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

Ces exemples partagés appellent la méthode api_call qui nous permet de définir le point de terminaison de l'API une seule fois dans notre spécification (conformément au principe DRY). Nous définissons cette méthode comme suit :

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

Nous devrons également personnaliser l'usine pour notre utilisateur :

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

Et enfin, avant d'exécuter nos spécifications, nous devons exécuter nos migrations :

 rake db:migrate

Gardez à l'esprit, cependant, que les spécifications échoueront toujours à ce stade, car nous n'avons pas encore implémenté notre point de terminaison API. C'est la prochaine étape.

Implémentation du point de terminaison de l'API de connexion

Pour commencer, nous allons écrire un squelette vide pour notre API de connexion ( 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

Ensuite, nous allons écrire une classe d'agrégation qui agrège les points de terminaison de l'API ( app/api/api.rb ) :

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

OK, maintenant nous pouvons monter notre API dans les routes :

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

Ajoutons maintenant le code pour vérifier les paramètres manquants. Nous pouvons ajouter ce code à api.rb en sauvant Grape::Exceptions::ValidationErrors .

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

Pour le mot de passe invalide, nous vérifierons si le code de réponse http est 401, ce qui signifie un accès non autorisé. Ajoutons ceci au contexte 'mot de passe incorrect' :

 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'

La même logique est ensuite ajoutée au contexte "avec une connexion inexistante".

Nous implémentons ensuite la logique qui gère les tentatives d'authentification invalides dans notre login.rb comme suit :

 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

À ce stade, toutes les spécifications négatives de l'API de connexion se comporteront correctement, mais nous devons toujours prendre en charge les spécifications positives de notre API de connexion. Notre spécification positive s'attendra à ce que le point de terminaison renvoie un code de réponse HTTP de 200 (c'est-à-dire un succès) avec un JSON valide et un jeton valide :

 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

Ajoutons également l'attente pour le code de réponse 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

En cas de connexion réussie, nous allons renvoyer le premier authentication_token valide avec l'e-mail de l'utilisateur dans ce format :

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

S'il n'y a pas encore de jeton de ce type, nous en créerons un pour l'utilisateur actuel :

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

Pour que cela fonctionne, nous aurons besoin d'une classe AuthenticationToken qui appartient à l'utilisateur. Nous allons générer ce modèle, puis exécuter la migration correspondante :

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

Nous devons également ajouter l'association correspondante à notre modèle utilisateur :

 class User < ActiveRecord::Base has_many :authentication_tokens end

Ensuite, nous ajouterons la portée valide à la classe AuthenticationToken :

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

Notez que nous avons utilisé la syntaxe Ruby dans l'instruction where . Ceci est rendu possible par notre utilisation de la gemme squeel qui permet la prise en charge de la syntaxe Ruby dans les requêtes activerecord.

Pour un utilisateur validé, nous allons créer une entité que nous appellerons "l'entité utilisateur avec jeton", en tirant parti des fonctionnalités de la gemme de l' grape-entity .

Écrivons la spécification de notre entité et mettons-la dans le fichier 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

Ajoutons ensuite les entités à user_entity.rb :

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

Et enfin, ajoutez une autre classe à user_with_token_entity.rb :

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

Comme nous ne voulons pas que les jetons restent valides indéfiniment, nous les faisons expirer après un jour :

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

Avec tout cela fait, nous pouvons maintenant retourner le format JSON attendu avec notre nouvellement écrit 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 ...

Frais. Désormais, toutes nos spécifications sont acceptées et les exigences fonctionnelles du point de terminaison API de connexion de base sont prises en charge.

Point de terminaison de l'API d'examen de la session de programmation par paires : mise en route

Notre backend devra autoriser les développeurs autorisés qui se sont connectés à interroger les révisions des sessions de programmation en binôme.

Notre nouveau point de terminaison API sera monté sur /api/pair_programming_session et renverra les avis appartenant à un projet. Commençons par écrire un squelette de base pour cette spécification :

 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

Nous écrirons également un point de terminaison d'API vide correspondant ( 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

Montons ensuite notre nouvelle API ( app/api/api.rb ):

 ... mount Login mount PairProgrammingSessions end

Développons la spécification et le point de terminaison de l'API, par rapport aux exigences une par une.

Point de terminaison de l'API d'examen de la session de programmation par paires : validation

L'une de nos exigences de sécurité non fonctionnelles les plus importantes était de restreindre l'accès à l'API à un petit sous-ensemble de développeurs que nous suivons. Précisons donc que :

 ... 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 ...

Ensuite, nous créerons un shared_example dans notre shared.rb pour confirmer que la requête provient de l'un de nos développeurs enregistrés :

 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

Nous devrons également créer une classe ErrorCodes (dans app/models/error_codes.rb ) :

 module ErrorCodes DEVELOPER_KEY_MISSING = 1001 end

Puisque nous nous attendons à ce que notre API se développe à l'avenir, nous allons implémenter un authorization_helper qui peut être réutilisé sur tous les points de terminaison API de l'application pour restreindre l'accès aux développeurs enregistrés uniquement :

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

Nous allons définir la méthode restrict_access_to_developers dans le module ApiHelpers::AuthenticationHerlper ( app/api/api_helpers/authentication_helper.rb ). Cette méthode vérifiera simplement si la clé Authorization sous les en-têtes contient une ApiKey valide. (Chaque développeur souhaitant accéder à l'API aura besoin d'une ApiKey valide. Celle-ci peut être fournie par un administrateur système ou via un processus d'enregistrement automatisé, mais ce mécanisme dépasse le cadre de cet article.)

 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

Nous devons ensuite générer le modèle ApiKey et exécuter les migrations : rails g model api_key token rake db:migrate

Ceci fait, dans notre spec/api/pair_programming_spec.rb nous pouvons vérifier si l'utilisateur est authentifié :

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

Définissons également un exemple partagé unauthenticated qui peut être réutilisé dans toutes les spécifications ( spec/support/shared.rb ) :

 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

Cet exemple partagé a besoin du jeton dans l'en-tête du développeur, ajoutons-le donc à notre spécification ( spec/api/pair_programming_spec.rb ) :

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

Maintenant, dans notre app/api/pair_programming_session.rb , essayons d'authentifier l'utilisateur :

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

Implémentons l' authenticate! méthode dans 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 ...

(Notez que nous devons ajouter le code d'erreur BAD_AUTHENTICATION_PARAMS à notre classe ErrorCodes .)

Ensuite, spécifions ce qui se passe si le développeur appelle l'API avec un jeton non valide. Dans ce cas, le code de retour sera 401 signalant un "accès non autorisé". Le résultat doit être JSON et un auditable doit être créé. Nous ajoutons donc ceci à 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 ...

Nous ajouterons les exemples partagés "auditable created", "contains error code" et "contains error msg" à 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 ...

Nous devons également créer un modèle audit_log :

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

Point de terminaison de l'API d'examen de la session de programmation par paires : retour des résultats

Pour un utilisateur authentifié et autorisé, un appel à ce point de terminaison d'API doit renvoyer un ensemble d'examens de session de programmation en binôme regroupés par projet. Modifions notre spec/api/pair_programming_spec.rb conséquence :

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

Cela spécifie qu'une requête soumise avec une api_key valide et des paramètres valides renvoie un code HTTP de 200 (c'est-à-dire un succès) et que le résultat est renvoyé sous la forme d'un JSON valide.

Nous allons interroger puis renvoyer au format JSON les sessions de programmation en binôme où l'un des participants est l'utilisateur actuel ( 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 ...

Les séances de programmation en binôme sont modélisées comme suit dans la base de données :

  • Relation 1 à plusieurs entre les projets et les sessions de programmation en binôme
  • Relation 1 à plusieurs entre les sessions de programmation en binôme et les révisions
  • Relation 1 à plusieurs entre les avis et les exemples de code

Générons les modèles en conséquence, puis exécutons les migrations :

 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

Ensuite, nous devons modifier nos classes PairProgrammingSession et Review pour contenir les associations 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

REMARQUE : dans des circonstances normales, je générerais ces classes en écrivant d'abord leurs spécifications, mais comme cela dépasse le cadre de cet article, je sauterai cette étape.

Nous devons maintenant écrire les classes qui vont transformer nos modèles en leurs représentations JSON (appelées entités de raisin dans la terminologie du raisin). Pour plus de simplicité, nous utiliserons un mappage 1 à 1 entre les modèles et les entités de raisin.

Nous commençons par exposer le champ code de CodeSampleEntity (dans api/entities/code_sample_entity.rb ) :

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

Ensuite, nous exposons l' user et les code_samples associés en réutilisant le UserEntity déjà défini et le CodeSampleEntity :

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

Nous exposons également le champ name de ProjectEntity :

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

Enfin, nous assemblons l'entité dans une nouvelle PairProgrammingSessionsEntity où nous exposons le project , le host_user , le visitor_user et les 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

Et avec cela, notre API est entièrement implémentée !

Génération de données de test

À des fins de test, nous allons créer des exemples de données dans db/seeds.rb . Ce fichier doit contenir toutes les créations d'enregistrement nécessaires pour alimenter la base de données avec ses valeurs par défaut. Les données peuvent ensuite être chargées avec rake db:seed (ou créées avec db lorsque db:setup est invoqué). Voici un exemple de ce que cela pourrait inclure :

 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'

Maintenant, notre application est prête à être utilisée et nous pouvons lancer notre serveur rails.

Tester l'API

Nous utiliserons Swagger pour effectuer des tests manuels sur navigateur de notre API. Quelques étapes de configuration sont cependant nécessaires pour que nous puissions utiliser Swagger.

Tout d'abord, nous devons ajouter quelques gemmes à notre gemfile :

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

Nous exécutons ensuite bundle pour installer ces gems.

Nous devons également les ajouter aux actifs de notre pipeline d'actifs (dans config/initializers/assets.rb ):

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

Enfin, dans app/api/api.rb nous devons monter le générateur swagger :

 ... add_swagger_documentation end ...

Nous pouvons maintenant profiter de la belle interface utilisateur de Swagger pour explorer notre API en allant simplement sur http://localhost:3000/api/swagger .

Swagger présente nos points de terminaison API d'une manière agréablement explorable. Si nous cliquons sur un point de terminaison, Swagger liste ses opérations. Si nous cliquons sur une opération, Swagger affiche ses paramètres obligatoires et facultatifs et leurs types de données.

Un détail restant cependant avant de continuer : étant donné que nous avons limité l'utilisation des développeurs d'API avec une api_key valide, nous ne pourrons pas accéder au point de terminaison de l'API directement depuis le navigateur car le serveur nécessitera une api_key valide dans l'en-tête HTTP. Nous pouvons accomplir cela à des fins de test dans Google Chrome en utilisant le plug-in Modifier les en-têtes pour Google Chrome. Ce plugin nous permettra de modifier l'en-tête HTTP et d'ajouter une api_key valide (nous utiliserons l' api_key factice de 12345654321 que nous avons inclus dans notre fichier de départ de base de données).

OK, maintenant nous sommes prêts à tester !

Pour appeler le point de terminaison de l'API pair_programming_sessions , nous devons d'abord nous connecter. Nous utiliserons simplement les combinaisons d'e-mail et de mot de passe de notre fichier de départ de base de données et les soumettrons via Swagger au point de terminaison de l'API de connexion, comme indiqué ci-dessous.

Comme vous pouvez le voir ci-dessus, le jeton appartenant à cet utilisateur est renvoyé, indiquant que l'API de connexion fonctionne correctement comme prévu. Nous pouvons maintenant utiliser ce jeton pour réussir l'opération GET /api/pair_programming_sessions.json .

Comme indiqué, le résultat est renvoyé sous la forme d'un objet JSON hiérarchique correctement formaté. Notez que la structure JSON reflète deux associations 1-à-plusieurs imbriquées, puisque le projet a plusieurs révisions et qu'une révision a plusieurs exemples de code. Si nous ne retournions pas la structure de cette manière, l'appelant de notre API devrait demander séparément les révisions pour chaque projet, ce qui nécessiterait de soumettre N requêtes à notre point de terminaison API. Avec cette structure, nous résolvons donc le problème de performance de la requête N+1.

Emballer

Comme indiqué ici, les spécifications complètes de votre API permettent de garantir que l'API mise en œuvre répond correctement et adéquatement aux cas d'utilisation prévus (et imprévus !).

Bien que l'exemple d'API présenté dans ce didacticiel soit assez basique, l'approche et les techniques que nous avons démontrées peuvent servir de base à des API plus sophistiquées de complexité arbitraire utilisant la gemme Grape. Ce tutoriel a, espérons-le, démontré que Grape est un joyau utile et flexible qui peut aider à faciliter la mise en œuvre d'une API JSON dans vos applications Rails. Prendre plaisir!