Üzüm Mücevheri Eğitimi: Ruby'de REST-Benzeri Bir API Nasıl Oluşturulur
Yayınlanan: 2022-03-11Ruby 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.
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!