Grape Gem 튜토리얼: Ruby에서 REST와 유사한 API를 빌드하는 방법

게시 됨: 2022-03-11

Ruby On Rails 개발자로서 우리는 자바스크립트가 많은 리치 인터넷 클라이언트나 기본 iPhone 및 Android 앱을 지원하기 위해 API 끝점을 사용하여 애플리케이션을 확장해야 하는 경우가 많습니다. 애플리케이션의 유일한 목적이 JSON API를 통해 iPhone/Android 앱을 제공하는 것인 경우도 있습니다.

이 튜토리얼에서는 Ruby용 REST와 유사한 API 마이크로 프레임워크인 Grape를 사용하여 JSON API용 Rails에서 백엔드 지원을 구축하는 방법을 보여줍니다. Grape는 웹 애플리케이션을 방해 하지 않고 보완 하기 위해 장착 가능한 랙 엔진으로 실행되도록 설계되었습니다.

Grape Gem을 사용하는 Ruby의 웹 API

사용 사례

이 튜토리얼에서 집중할 사용 사례는 페어 프로그래밍 세션을 캡처하고 검토할 수 있는 애플리케이션입니다. 애플리케이션 자체는 iOS용으로 ObjectiveC로 작성되며 데이터 저장 및 검색을 위해 백엔드 서비스와 통신해야 합니다. 이 자습서에서는 JSON API를 지원하는 강력하고 안전한 백엔드 서비스를 만드는 데 중점을 둡니다.

API는 다음에 대한 메서드를 지원합니다.

  • 시스템에 로그인
  • 페어 프로그래밍 세션 검토 쿼리

참고: 페어 프로그래밍 세션 검토를 쿼리하는 기능을 제공하는 것 외에도 실제 API는 데이터베이스에 포함하기 위해 페어 프로그래밍 검토를 제출하는 기능도 제공해야 합니다. API를 통해 지원하는 것은 이 자습서의 범위를 벗어나므로 데이터베이스가 쌍 프로그래밍 검토의 샘플 세트로 채워졌다고 간단히 가정합니다.

주요 기술 요구 사항은 다음과 같습니다.

  • 모든 API 호출은 유효한 JSON을 반환해야 합니다.
  • 실패한 모든 API 호출은 이후에 재현할 수 있도록 적절한 컨텍스트 및 정보와 함께 기록되어야 하며 필요한 경우 디버깅해야 합니다.

또한 애플리케이션이 외부 클라이언트에 서비스를 제공해야 하므로 보안에 신경을 써야 합니다. 이를 위해:

  • 각 요청은 우리가 추적하는 소수의 개발자로 제한되어야 합니다.
  • 모든 요청(로그인/가입 제외)은 인증이 필요합니다.

테스트 주도 개발 및 RSpec

우리는 API의 결정적 동작을 보장하기 위해 소프트웨어 개발 접근 방식으로 테스트 주도 개발(TDD)을 사용할 것입니다.

테스트 목적으로 RubyOnRails 커뮤니티에서 잘 알려진 테스트 프레임워크인 RSpec을 사용합니다. 따라서 이 기사에서는 "테스트"가 아닌 "사양"으로 언급하겠습니다.

포괄적인 테스트 방법론은 "양성" 및 "음성" 테스트로 구성됩니다. 네거티브 사양은 예를 들어 일부 매개변수가 누락되거나 올바르지 않은 경우 API 엔드포인트가 작동하는 방식을 지정합니다. 긍정적인 사양은 API가 올바르게 호출되는 경우를 다룹니다.

시작하기

백엔드 API의 기초를 세워봅시다. 먼저 새로운 레일 애플리케이션을 생성해야 합니다.

 rails new toptal_grape_blog

다음으로 gemfile에 rspec-rails 를 추가하여 RSpec을 설치합니다.

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

그런 다음 명령줄에서 다음을 실행해야 합니다.

 rails generate rspec:install

또한 테스트 프레임워크를 위해 기존 오픈 소스 소프트웨어를 활용할 수도 있습니다. 구체적으로 특별히:

  • Devise - Warden 기반의 Rails용 유연한 인증 솔루션
  • factory_girl_rails - Ruby 객체를 테스트 데이터로 설정하기 위한 라이브러리인 factory_girl에 대한 Rails 통합을 제공합니다.

1단계: 다음을 gemfile에 추가합니다.

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

2단계: 사용자 모델을 생성하고, devise gem을 초기화하고, 사용자 모델에 추가합니다(이렇게 하면 사용자 클래스를 인증에 사용할 수 있음).

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

3단계: 우리 사양에서 사용자 생성의 축약된 버전을 사용하기 위해 우리의 rails_helper.rb 파일에 factory_girl 구문 메서드를 포함합니다:

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

4단계: 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 블록 뒤에 이 코드를 넣을 것입니다:

 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 }

그런 다음 3개의 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 메서드를 호출하여 사양에서 API 끝점을 한 번만 정의할 수 있도록 합니다(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 엔드포인트를 구현하지 않았기 때문에 이 시점에서 사양은 여전히 ​​실패할 것이라는 점을 명심하십시오. 다음입니다.

로그인 API 끝점 구현

우선 로그인 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

다음으로 API 엔드포인트( app/api/api.rb )를 집계하는 집계 클래스를 작성합니다.

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

이제 경로에 API를 탑재할 수 있습니다.

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

이제 누락된 매개변수를 확인하는 코드를 추가해 보겠습니다. Grape::Exceptions::ValidationErrors 에서 구해 해당 코드를 api.rb 에 추가할 수 있습니다.

 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

이 시점에서 로그인 API에 대한 모든 부정적인 사양은 올바르게 작동하지만 로그인 API에 대한 긍정적인 사양은 여전히 ​​지원해야 합니다. 긍정적인 사양은 엔드포인트가 유효한 JSON 및 유효한 토큰과 함께 HTTP 응답 코드 200(즉, 성공)을 반환할 것으로 예상합니다.

 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에 대한 기대치를 추가해 보겠습니다.

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

로그인에 성공한 경우 다음 형식으로 사용자의 이메일과 함께 첫 번째 유효한 authentication_token을 반환합니다.

 {'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

where 문에서 Ruby 구문을 사용했습니다. 이것은 activerecord 쿼리에서 Ruby 구문을 지원하는 squeel gem을 사용하여 가능합니다.

검증된 사용자의 경우 "토큰 엔터티가 있는 사용자"라고 하는 엔터티를 생성하여 grape-entity gem의 기능을 활용할 것입니다.

엔티티에 대한 사양을 작성하고 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

이 모든 작업이 완료되면 이제 새로 작성된 UserWithTokenEntity 를 사용하여 예상 JSON 형식을 반환할 수 있습니다.

 ... 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 엔드포인트는 /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

그런 다음 새 API( app/api/api.rb )를 마운트해 보겠습니다.

 ... mount Login mount PairProgrammingSessions end

사양과 API 엔드포인트를 요구 사항에 대해 하나씩 확장해 보겠습니다.

페어 프로그래밍 세션 검토 API 끝점: 유효성 검사

우리의 가장 중요한 비기능적 보안 요구사항 중 하나는 우리가 추적하는 소규모 개발자 하위 집합으로 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_exampleshared.rb 을 만들어 등록된 개발자 중 한 명이 요청한 것인지 확인합니다.

 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

앞으로 API가 확장될 것으로 예상하므로 등록된 개발자에게만 액세스를 제한하기 위해 애플리케이션의 모든 API 끝점에서 재사용할 수 있는 authorization_helper 를 구현할 것입니다.

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

ApiHelpers::AuthenticationHerlper 모듈( app/api/api_helpers/authentication_helper.rb )에서 restrict_access_to_developers 메소드를 정의할 것입니다. 이 메서드는 헤더 아래의 키 Authorization 에 유효한 ApiKey 가 포함되어 있는지 간단히 확인합니다. (API에 액세스하려는 모든 개발자는 유효한 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_PARAMSErrorCodes 클래스에 추가해야 합니다.)

다음으로 개발자가 유효하지 않은 토큰으로 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 ...

"auditable created", "contains error code" 및 "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 ...

또한 audit_log 모델을 생성해야 합니다.

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

페어 프로그래밍 세션 검토 API 끝점: 결과 반환

인증되고 권한이 부여된 사용자의 경우 이 API 끝점에 대한 호출은 프로젝트별로 그룹화된 페어 프로그래밍 세션 검토 세트를 반환해야 합니다. 이에 따라 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 형식으로 반환되도록 지정합니다.

참가자 중 한 명이 현재 사용자인 쌍 프로그래밍 세션( app/api/pair_programming_sessions.rb )을 쿼리한 다음 JSON 형식으로 반환합니다.

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

그런 다음 has_many 연관을 포함하도록 PairProgrammingSessionReview 클래스를 수정해야 합니다.

 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 매핑을 사용합니다.

CodeSampleEntity ( api/entities/code_sample_entity.rb )에서 code 필드를 노출하는 것으로 시작합니다.

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

그런 다음 이미 정의된 UserEntity 및 UserEntity 를 재사용하여 user 및 관련 code_samples 를 노출 CodeSampleEntity .

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

또한 ProjectEntity 에서 name 필드를 노출합니다.

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

마지막으로 엔티티를 새로운 PairProgrammingSessionsEntity 로 조합하여 project , host_user , visitor_userreviews 를 노출합니다.

 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:setup 이 호출될 때 db로 생성). 여기에 포함될 수 있는 항목의 예는 다음과 같습니다.

 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를 사용하여 API에 대한 수동 브라우저 기반 테스트를 수행합니다. Swagger를 사용하려면 몇 가지 설정 단계가 필요합니다.

먼저 gemfile에 두 개의 gem을 추가해야 합니다.

 ... 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의 멋진 UI를 활용하여 http://localhost:3000/api/swagger 로 이동하여 API를 탐색할 수 있습니다.

Swagger는 API 엔드포인트를 탐색 가능한 방식으로 제공합니다. 끝점을 클릭하면 Swagger가 해당 작업을 나열합니다. 작업을 클릭하면 Swagger는 필수 및 선택적 매개변수와 해당 데이터 유형을 표시합니다.

계속 진행하기 전에 한 가지 남은 세부 사항: 유효한 api_key 로 API 개발자의 사용을 제한했기 때문에 서버가 HTTP 헤더에 유효한 api_key 를 요구하기 때문에 브라우저에서 API 엔드포인트에 직접 액세스할 수 없습니다. Google 크롬용 헤더 수정 플러그인을 사용하여 Google 크롬에서 테스트 목적으로 이 작업을 수행할 수 있습니다. 이 플러그인을 사용하면 HTTP 헤더를 편집하고 유효한 api_key 를 추가할 수 있습니다(데이터베이스 시드 파일에 포함된 더미 api_key 12345654321 사용).

자, 이제 테스트할 준비가 되었습니다!

pair_programming_sessions API 엔드포인트를 호출하려면 먼저 로그인해야 합니다. 아래와 같이 데이터베이스 시드 파일의 이메일 및 비밀번호 조합을 사용하고 Swagger를 통해 로그인 API 엔드포인트에 제출합니다.

위에서 볼 수 있듯이 해당 사용자에게 속한 토큰이 반환되어 로그인 API가 의도한 대로 제대로 작동하고 있음을 나타냅니다. 이제 해당 토큰을 사용하여 GET /api/pair_programming_sessions.json 작업을 성공적으로 수행할 수 있습니다.

표시된 대로 결과는 올바른 형식의 계층적 JSON 개체로 반환됩니다. 프로젝트에는 여러 리뷰가 있고 리뷰에는 여러 코드 샘플이 있으므로 JSON 구조는 두 개의 중첩된 일대다 연결을 반영합니다. 이러한 방식으로 구조를 반환하지 않으면 API 호출자는 API 엔드포인트에 N 쿼리를 제출해야 하는 각 프로젝트에 대한 검토를 별도로 요청해야 합니다. 따라서 이 구조를 사용하여 N+1 쿼리 성능 문제를 해결합니다.

마무리

여기에 표시된 대로 API에 대한 포괄적인 사양은 구현된 API가 의도한(그리고 의도하지 않은!) 사용 사례를 적절하고 적절하게 처리하는지 확인하는 데 도움이 됩니다.

이 튜토리얼에서 제공하는 예제 API는 매우 기본적이지만, 우리가 시연한 접근 방식과 기술은 Grape gem을 사용하여 임의의 복잡성을 지닌 보다 정교한 API의 기반이 될 수 있습니다. 이 튜토리얼은 Grape가 Rails 애플리케이션에서 JSON API의 구현을 용이하게 하는 데 도움이 될 수 있는 유용하고 유연한 보석임을 보여주길 바랍니다. 즐기다!