Üzüm Mücevheri Eğitimi: Ruby'de REST-Benzeri Bir API Nasıl Oluşturulur

Yayınlanan: 2022-03-11

Ruby On Rails geliştiricileri olarak, JavaScript ağırlıklı Zengin İnternet istemcilerini veya yerel iPhone ve Android uygulamalarını desteklemek için uygulamalarımızı genellikle API uç noktalarıyla genişletmemiz gerekir. Uygulamanın tek amacının bir JSON API aracılığıyla iPhone/Android uygulamalarına hizmet vermek olduğu bazı durumlar da vardır.

Bu öğreticide, bir JSON API için Rails'de arka uç desteği oluşturmak üzere Ruby için REST benzeri bir API mikro çerçevesi olan Grape'in nasıl kullanılacağını gösteriyorum. Grape, web uygulamalarımıza müdahale etmeden onları tamamlamak için monte edilebilir bir raf motoru olarak çalışacak şekilde tasarlanmıştır.

Ruby'de Grape Gem kullanan Web API'si

Kullanım Örneği

Bu eğitim için odaklanacağımız kullanım örneği, çift programlama oturumlarını yakalayıp gözden geçirebilen bir uygulamadır. Uygulamanın kendisi ObjectiveC'de iOS için yazılacak ve verileri depolamak ve almak için bir arka uç hizmetiyle iletişim kurması gerekecek. Bu eğitimdeki odak noktamız, bir JSON API'sini destekleyen sağlam ve güvenli bir arka uç hizmeti oluşturmaktır.

API, aşağıdakiler için yöntemleri destekleyecektir:

  • Sisteme giriş
  • Çift programlama oturumu incelemelerini sorgulama

NOT: Gerçek API'nin, ikili programlama oturum incelemelerini sorgulama yeteneği sağlamasına ek olarak, veritabanına dahil edilmek üzere ikili programlama incelemelerini göndermek için bir olanak sağlaması gerekir. Bunu API aracılığıyla desteklemek bu öğreticinin kapsamı dışında olduğundan, veritabanının bir örnek çiftli programlama incelemesi seti ile doldurulduğunu varsayacağız.

Temel teknik gereksinimler şunları içerir:

  • Her API çağrısı geçerli JSON döndürmelidir
  • Başarısız olan her API çağrısı, daha sonra yeniden üretilebilmesi ve gerekirse hata ayıklanması için yeterli bağlam ve bilgilerle kaydedilmelidir.

Ayrıca, uygulamamızın harici müşterilere hizmet vermesi gerekeceğinden, güvenlikle ilgilenmemiz gerekecek. Bu amaçla:

  • Her istek, izlediğimiz geliştiricilerin küçük bir alt kümesiyle sınırlandırılmalıdır.
  • Tüm isteklerin (giriş/kayıt dışında) kimliğinin doğrulanması gerekir

Test Odaklı Geliştirme ve RSpec

API'mizin belirleyici davranışını sağlamaya yardımcı olmak için yazılım geliştirme yaklaşımımız olarak Teste Dayalı Geliştirme'yi (TDD) kullanacağız.

Test amacıyla RubyOnRails topluluğunda iyi bilinen bir test çerçevesi olan RSpec'i kullanacağız. Bu nedenle bu makalede “testler” yerine “özellikler”e değineceğim.

Kapsamlı bir test metodolojisi hem "pozitif" hem de "negatif" testlerden oluşur. Negatif özellikler, örneğin, bazı parametreler eksik veya yanlışsa API uç noktasının nasıl davranacağını belirler. Olumlu özellikler, API'nin doğru şekilde çağrıldığı durumları kapsar.

Başlarken

Arka uç API'mizin temelini oluşturalım. Öncelikle yeni bir Rails uygulaması oluşturmamız gerekiyor:

 rails new toptal_grape_blog

Ardından, gem dosyamıza rspec-rails ekleyerek RSpec'i yükleyeceğiz:

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

Ardından komut satırımızdan çalıştırmamız gerekiyor:

 rails generate rspec:install

Test çerçevemiz için mevcut bazı açık kaynaklı yazılımlardan da yararlanabiliriz. özellikle:

  • Devise - Rails için Warden'a dayalı esnek bir kimlik doğrulama çözümü
  • fabrika_girl_rails - Ruby nesnelerini test verileri olarak ayarlamak için bir kitaplık olan fabrika_girl için Rails entegrasyonu sağlar

Adım 1: Bunları gem dosyamıza ekleyin:

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

Adım 2: Bir kullanıcı modeli oluşturun, devise mücevherini başlatın ve onu kullanıcı modeline ekleyin (bu, kullanıcı sınıfının kimlik doğrulama için kullanılmasını sağlar).

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

Adım 3: Kullanıcı oluşturmanın kısaltılmış sürümünü özelliklerimizde kullanmak için, rails_helper.rb factory_girl sözdizimi yöntemini dahil edin:

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

Adım 4: Üzüm taşını DSL'imize ekleyin ve kurun:

 gem 'grape' bundle

Kullanıcı Girişi Kullanım Örneği ve Spesifikasyonu

Arka ucumuzun temel bir oturum açma özelliğini desteklemesi gerekecek. Geçerli bir oturum açma isteğinin kayıtlı bir e-posta adresi ve parola çiftinden oluştuğunu varsayarak login_spec iskeletini oluşturalım:

 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

Parametrelerden herhangi biri eksikse, istemci, 'e-posta eksik' veya 'parola eksik' hata mesajıyla birlikte 400'lük bir HTTP dönüş durum kodu (yani, Kötü İstek) almalıdır.

Testimiz için geçerli bir kullanıcı oluşturacağız ve bu test paketi için kullanıcının e-postasını ve şifresini orijinal parametreler olarak ayarlayacağız. Ardından, parolayı/e-postayı atlayarak veya geçersiz kılarak bu parametre karmasını her belirli özellik için özelleştireceğiz.

Spesifikasyonun başında kullanıcıyı ve parametre karmasını oluşturalım. Bu kodu açıklama bloğundan sonra koyacağız:

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

Daha sonra 'eksik params'/'parola' bağlamımızı aşağıdaki gibi genişletebiliriz:

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

Ancak, 'e-posta' ve 'şifre' bağlamlarında beklentileri tekrarlamak yerine, beklentilerle aynı paylaşılan örnekleri kullanabiliriz. Bunun için rails_helper.rb dosyamızdaki bu satırı uncomment'a almamız gerekiyor:

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

Ardından 3 RSpec paylaşılan örneğini spec/support/shared.rb içine eklememiz gerekiyor:

 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

Bu paylaşılan örnekler, API uç noktasını spesifikasyonumuzda (DRY ilkesine uygun olarak) yalnızca bir kez tanımlamamızı sağlayan api_call yöntemini çağırıyor. Bu yöntemi şu şekilde tanımlıyoruz:

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

Ayrıca fabrikayı kullanıcımız için özelleştirmemiz gerekecek:

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

Ve son olarak, özelliklerimizi çalıştırmadan önce geçişlerimizi çalıştırmamız gerekiyor:

 rake db:migrate

Yine de, API uç noktamızı henüz uygulamadığımız için özelliklerin bu noktada başarısız olacağını unutmayın. Sıradaki.

Login API Endpoint'i Uygulama

Yeni başlayanlar için, oturum açma API'miz için boş bir iskelet yazacağız ( 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

Ardından, API uç noktalarını ( app/api/api.rb ) toplayan bir toplayıcı sınıf yazacağız:

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

Tamam, şimdi API'mizi rotalara bağlayabiliriz:

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

Şimdi eksik parametreleri kontrol etmek için kodu ekleyelim. Bu kodu api.rb Grape::Exceptions::ValidationErrors kurtararak ekleyebiliriz.

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

Geçersiz şifre için, http yanıt kodunun yetkisiz erişim anlamına gelen 401 olup olmadığını kontrol edeceğiz. Bunu 'yanlış şifre' bağlamına ekleyelim:

 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'

Aynı mantık daha sonra 'var olmayan bir oturum açma' bağlamına da eklenir.

Ardından, geçersiz kimlik doğrulama girişimlerini işleyen mantığı login.rb aşağıdaki gibi uygularız:

 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

Bu noktada, oturum açma api'si için tüm olumsuz özellikler düzgün şekilde davranacaktır, ancak yine de oturum açma api'miz için olumlu özellikleri desteklememiz gerekiyor. Olumlu özelliğimiz, uç noktanın geçerli JSON ve geçerli bir belirteçle 200'lük bir HTTP yanıt kodu (yani, başarı) döndürmesini bekleyecektir:

 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

spec/support/shared.rb 200 yanıt kodu beklentisini de ekleyelim:

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

Başarılı oturum açma durumunda, kullanıcının e-postasıyla birlikte ilk geçerli kimlik doğrulama_tokenini şu biçimde döndüreceğiz:

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

Henüz böyle bir belirteç yoksa, mevcut kullanıcı için bir tane oluşturacağız:

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

Bunun çalışması için kullanıcıya ait bir AuthenticationToken sınıfına ihtiyacımız olacak. Bu modeli oluşturacağız, ardından ilgili geçişi çalıştıracağız:

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

Ayrıca ilgili ilişkilendirmeyi kullanıcı modelimize eklememiz gerekiyor:

 class User < ActiveRecord::Base has_many :authentication_tokens end

Ardından, AuthenticationToken sınıfına geçerli kapsamı ekleyeceğiz:

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

where , ifadede Ruby sözdizimini kullandığımızı unutmayın. Bu, aktif kayıt sorgularında Ruby sözdizimi için destek sağlayan squeel kullanmamızla sağlanır.

Doğrulanmış bir kullanıcı için, grape-entity mücevherinin özelliklerinden yararlanarak “belirteç varlığı olan kullanıcı” olarak adlandıracağımız bir varlık oluşturacağız.

Varlığımızın özelliklerini yazalım ve onu user_with_token_entity_spec.rb dosyasına koyalım:

 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

Şimdi varlıkları user_entity.rb :

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

Ve son olarak user_with_token_entity.rb başka bir sınıf ekleyin:

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

Belirteçlerin süresiz olarak geçerli kalmasını istemediğimiz için, bir gün sonra süresinin dolmasını sağlıyoruz:

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

Tüm bunları yaptıktan sonra, yeni yazılmış UserWithTokenEntity ile beklenen JSON biçimini döndürebiliriz:

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

Serin. Artık tüm özelliklerimiz geçiyor ve temel oturum açma api uç noktasının işlevsel gereksinimleri destekleniyor.

Eşli Programlama Oturumu İnceleme API'sı Uç Noktası: Başlarken

Arka ucumuzun, oturum açmış yetkili geliştiricilerin çift programlama oturum incelemelerini sorgulamasına izin vermesi gerekecek.

Yeni API uç /api/pair_programming_session ve bir projeye ait incelemeleri döndürecektir. Bu özellik için temel bir iskelet yazarak başlayalım:

 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

Karşılık gelen boş bir API bitiş noktası da yazacağız ( 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

Ardından yeni api'mizi ( app/api/api.rb ) bağlayalım:

 ... mount Login mount PairProgrammingSessions end

Spesifikasyonu ve API uç noktasını gereksinimlere göre tek tek genişletelim.

Eşli Programlama Oturumu İnceleme API'si Bitiş Noktası: Doğrulama

İşlevsel olmayan en önemli güvenlik gereksinimlerimizden biri, API erişimini izlediğimiz küçük bir geliştirici alt kümesine kısıtlamaktı, bu nedenle şunu belirtelim:

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

Ardından, talebin kayıtlı geliştiricilerimizden birinden geldiğini doğrulamak için shared.rb shared_example bir paylaşılan_örnek oluşturacağız:

 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

Ayrıca bir ErrorCodes sınıfı oluşturmamız gerekecek ( app/models/error_codes.rb içinde):

 module ErrorCodes DEVELOPER_KEY_MISSING = 1001 end

API'mizin gelecekte genişlemesini beklediğimizden, yalnızca kayıtlı geliştiricilere erişimi kısıtlamak için uygulamadaki tüm API uç noktalarında yeniden kullanılabilen bir authorization_helper uygulayacağız:

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

ApiHelpers::AuthenticationHerlper modülünde ( app/api/api_helpers/authentication_helper.rb ) restrict_access_to_developers yöntemini tanımlayacağız. Bu yöntem, başlıklar altındaki Authorization anahtarının geçerli bir ApiKey içerip içermediğini kontrol eder. (API'ye erişmek isteyen her geliştirici, geçerli bir ApiKey gerektirir. Bu, bir sistem yöneticisi tarafından veya bazı otomatik kayıt işlemleri aracılığıyla sağlanabilir, ancak bu mekanizma bu makalenin kapsamı dışındadır.)

 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

Daha sonra ApiKey modelini oluşturmamız ve geçişleri çalıştırmamız gerekiyor: raylar g model api_key token rake db:migrate

Bunu yaptıktan sonra, spec/api/pair_programming_spec.rb kullanıcının kimliğinin doğrulanıp doğrulanmadığını kontrol edebiliriz:

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

Ayrıca, tüm özelliklerde ( spec/support/shared.rb ) yeniden kullanılabilen, unauthenticated bir paylaşılan örnek tanımlayalım:

 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

Bu paylaşılan örnek, geliştirici başlığında belirteç gerektiriyor, bu yüzden bunu spesifikasyonumuza ekleyelim ( spec/api/pair_programming_spec.rb ):

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

Şimdi, app/api/pair_programming_session.rb , kullanıcının kimliğini doğrulamaya çalışalım:

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

authenticate! AuthenticationHelper yöntem ( 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 hata kodunu ErrorCodes sınıfımıza eklememiz gerektiğini unutmayın.)

Ardından, geliştirici API'yi geçersiz bir belirteçle çağırırsa ne olacağını belirtelim. Bu durumda geri dönüş kodu, 'yetkisiz erişim' sinyalini veren 401 olacaktır. Sonuç JSON olmalı ve bir denetlenebilir oluşturulmalıdır. Bunu 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 ...

"Denetlenebilir oluşturuldu", "hata kodu içerir" ve "hata mesajı içerir" paylaşılan örnekleri 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 ...

Ayrıca bir denetim_log modeli oluşturmamız gerekiyor:

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

Eşli Programlama Oturumu İnceleme API'sı Uç Noktası: Sonuçları Döndürme

Kimliği doğrulanmış ve yetkili bir kullanıcı için, bu API uç noktasına yapılan bir çağrı, projeye göre gruplandırılmış bir çift programlama oturumu incelemesi döndürmelidir. spec/api/pair_programming_spec.rb buna göre değiştirelim:

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

Bu, geçerli api_key ve geçerli parametrelerle gönderilen bir isteğin 200 (yani, başarı) HTTP kodunu döndürdüğünü ve sonucun geçerli JSON biçiminde döndürüldüğünü belirtir.

Katılımcıdan herhangi birinin geçerli kullanıcı olduğu bu ikili programlama oturumlarını sorgulayıp JSON biçiminde döndüreceğiz ( 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 ...

Çift programlama oturumları, veritabanında aşağıdaki gibi modellenir:

  • Projeler ve eşli programlama oturumları arasında 1'e çok ilişki
  • Çift programlama oturumları ve incelemeler arasında 1'e çok ilişki
  • İncelemeler ve kod örnekleri arasında 1'e çok ilişki

Modelleri buna göre oluşturalım ve ardından geçişleri çalıştıralım:

 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

Ardından, has_many ilişkilendirmelerini içerecek şekilde PairProgrammingSession ve Review sınıflarımızı değiştirmemiz gerekiyor:

 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

NOT: Normal şartlarda, bu sınıfları önce onların özelliklerini yazarak oluştururdum, ancak bu makalenin kapsamı dışında olduğu için bu adımı atlayacağım.

Şimdi modellerimizi JSON temsillerine dönüştürecek olan sınıfları yazmamız gerekiyor (üzüm terminolojisinde üzüm varlıkları olarak adlandırılır). Basit olması için, modeller ve üzüm varlıkları arasında 1'e 1 eşleme kullanacağız.

CodeSampleEntity code alanını göstererek başlıyoruz ( api/entities/code_sample_entity.rb ):

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

Ardından, önceden tanımlanmış UserEntity ve CodeSampleEntity öğelerini yeniden kullanarak user ve ilişkili code_samples ortaya çıkarırız:

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

ProjectEntity name alanını da gösteririz:

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

Son olarak, project , host_user , visitor_user ve reviews ortaya çıkardığımız yeni bir PairProgrammingSessionsEntity içinde varlığı birleştiririz:

 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

Ve bununla birlikte, API'miz tamamen uygulandı!

Test Verisi Oluşturma

Test amacıyla, db/seeds.rb içinde bazı örnek veriler oluşturacağız. Bu dosya, veritabanını varsayılan değerleriyle tohumlamak için gereken tüm kayıt oluşturma işlemlerini içermelidir. Veriler daha sonra rake db:seed ile yüklenebilir (veya db:setup çağrıldığında db ile oluşturulabilir). Bunun neler içerebileceğine dair bir örnek:

 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'

Artık uygulamamız kullanıma hazır ve Rails sunucumuzu çalıştırabiliriz.

API'yi test etme

API'mizin bazı manuel tarayıcı tabanlı testlerini yapmak için Swagger'ı kullanacağız. Yine de Swagger'ı kullanabilmemiz için birkaç kurulum adımı gerekiyor.

İlk olarak, gem dosyamıza birkaç mücevher eklememiz gerekiyor:

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

Daha sonra bu taşları yüklemek için bundle çalıştırırız.

Bunları ayrıca varlıklar boru hattımıza varlıklara eklememiz gerekiyor ( config/initializers/assets.rb ):

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

Son olarak, app/api/api.rb swagger oluşturucuyu monte etmemiz gerekiyor:

 ... add_swagger_documentation end ...

Artık sadece http://localhost:3000/api/swagger giderek API'mizi keşfetmek için Swagger'ın güzel kullanıcı arayüzünden yararlanabiliriz.

Swagger, API uç noktalarımızı güzel bir şekilde keşfedilebilir bir şekilde sunar. Bir uç noktaya tıklarsak Swagger işlemlerini listeler. Bir işleme tıklarsak, Swagger gerekli ve isteğe bağlı parametrelerini ve veri türlerini gösterir.

Devam etmeden önce kalan bir ayrıntı: API geliştiricilerinin kullanımını geçerli bir api_key ile kısıtladığımız için, sunucu HTTP başlığında geçerli bir api_key gerektireceğinden API uç noktasına doğrudan tarayıcıdan erişemeyeceğiz. Bunu, Google Chrome için Modify Headers for Google Chrome eklentisini kullanarak Google Chrome'da test amacıyla gerçekleştirebiliriz. Bu eklenti, HTTP başlığını düzenlememizi ve geçerli bir api_key (veritabanı tohum dosyamıza dahil ettiğimiz 12345654321 kukla api_key kullanacağız).

Tamam, şimdi test etmeye hazırız!

pair_programming_sessions API uç noktasını çağırmak için önce oturum açmamız gerekiyor. Yalnızca veritabanı tohum dosyamızdaki e-posta ve parola kombinasyonlarını kullanacağız ve aşağıda gösterildiği gibi Swagger aracılığıyla oturum açma API uç noktasına göndereceğiz.

Yukarıda görebileceğiniz gibi, o kullanıcıya ait jeton döndürülür ve bu, login API'sinin istendiği gibi düzgün şekilde çalıştığını gösterir. Artık bu belirteci GET /api/pair_programming_sessions.json işlemini başarıyla gerçekleştirmek için kullanabiliriz.

Gösterildiği gibi, sonuç düzgün biçimlendirilmiş hiyerarşik bir JSON nesnesi olarak döndürülür. Proje birden çok incelemeye sahip olduğundan ve bir incelemede birden çok kod örneğine sahip olduğundan, JSON yapısının iki iç içe 1'den çoğa ilişkilendirmeyi yansıttığına dikkat edin. Yapıyı bu şekilde döndürmezsek, API'mizi arayan kişinin API uç noktamıza N sorgu göndermeyi gerektiren her proje için incelemeleri ayrı olarak talep etmesi gerekir. Dolayısıyla bu yapı ile N+1 sorgu performans sorununu çözmüş oluyoruz.

Sarmak

Burada gösterildiği gibi, API'niz için kapsamlı özellikler, uygulanan API'nin amaçlanan (ve istenmeyen!) kullanım durumlarını doğru ve yeterli bir şekilde ele almasını sağlamaya yardımcı olur.

Bu öğreticide sunulan örnek API oldukça basit olsa da, gösterdiğimiz yaklaşım ve teknikler, Grape gem kullanılarak rastgele karmaşıklığa sahip daha karmaşık API'lerin temeli olarak hizmet edebilir. Bu öğretici, Üzüm'ün Rails uygulamalarınızda bir JSON API'sinin uygulanmasını kolaylaştırmaya yardımcı olabilecek kullanışlı ve esnek bir mücevher olduğunu umarız göstermiştir. Eğlence!