Tutorial Permata Anggur: Cara Membangun API Seperti REST Di Ruby
Diterbitkan: 2022-03-11Sebagai pengembang Ruby On Rails, kami sering kali perlu memperluas aplikasi kami dengan titik akhir API untuk mendukung klien Internet Kaya JavaScript atau aplikasi iPhone dan Android asli. Ada juga beberapa kasus di mana satu-satunya tujuan aplikasi adalah untuk melayani aplikasi iPhone/Android melalui JSON API.
Dalam tutorial ini, saya mendemonstrasikan cara menggunakan Grape – kerangka mikro API seperti REST untuk Ruby – untuk membangun dukungan backend di Rails untuk API JSON. Grape dirancang untuk berjalan sebagai mesin rak yang dapat dipasang untuk melengkapi aplikasi web kami, tanpa mengganggu mereka.
Gunakan Kasus
Kasus penggunaan yang akan kita fokuskan untuk tutorial ini adalah aplikasi yang mampu menangkap dan meninjau sesi pemrograman berpasangan. Aplikasi itu sendiri akan ditulis untuk iOS di ObjectiveC dan perlu berkomunikasi dengan layanan backend untuk menyimpan dan mengambil data. Fokus kami dalam tutorial ini adalah pada pembuatan layanan backend yang kuat dan aman yang mendukung JSON API.
API akan mendukung metode untuk:
- Masuk ke sistem
- Meminta ulasan sesi pemrograman pasangan
CATATAN: Selain menyediakan kemampuan untuk menanyakan ulasan sesi pemrograman pasangan, API sebenarnya juga perlu menyediakan fasilitas untuk mengirimkan ulasan pemrograman pasangan untuk dimasukkan dalam database. Karena mendukung bahwa melalui API berada di luar cakupan tutorial ini, kami hanya akan berasumsi bahwa database telah diisi dengan kumpulan sampel ulasan pemrograman berpasangan.
Persyaratan teknis utama meliputi:
- Setiap panggilan API harus mengembalikan JSON yang valid
- Setiap panggilan API yang gagal harus direkam dengan konteks dan informasi yang memadai untuk selanjutnya dapat direproduksi, dan di-debug jika perlu
Juga, karena aplikasi kita perlu melayani klien eksternal, kita perlu memperhatikan keamanan. Menuju tujuan itu:
- Setiap permintaan harus dibatasi untuk sebagian kecil pengembang yang kami lacak
- Semua permintaan (selain login/pendaftaran) harus diautentikasi
Pengembangan Berbasis Uji Dan RSpec
Kami akan menggunakan Test Driven Development (TDD) sebagai pendekatan pengembangan perangkat lunak kami untuk membantu memastikan perilaku deterministik API kami.
Untuk tujuan pengujian, kami akan menggunakan RSpec, kerangka kerja pengujian terkenal di komunitas RubyOnRails. Karena itu saya akan merujuk dalam artikel ini ke "spesifikasi" daripada "tes".
Metodologi pengujian yang komprehensif terdiri dari tes "positif" dan "negatif". Spesifikasi negatif akan menentukan, misalnya, bagaimana perilaku endpoint API jika beberapa parameter tidak ada atau salah. Spesifikasi positif mencakup kasus di mana API dipanggil dengan benar.
Mulai
Mari kita meletakkan dasar untuk API backend kita. Pertama, kita perlu membuat aplikasi Rails baru:
rails new toptal_grape_blog
Selanjutnya, kita akan menginstal RSpec dengan menambahkan rspec-rails
ke dalam gemfile kita:
group :development, :test do gem 'rspec-rails', '~> 3.2' end
Kemudian dari baris perintah kami, kami perlu menjalankan:
rails generate rspec:install
Kami juga dapat memanfaatkan beberapa perangkat lunak sumber terbuka yang ada untuk kerangka pengujian kami. Secara khusus:
- Rancang - solusi otentikasi fleksibel untuk Rails berdasarkan Warden
- factory_girl_rails - menyediakan integrasi Rails untuk factory_girl, perpustakaan untuk menyiapkan objek Ruby sebagai data pengujian
Langkah 1: Tambahkan ini ke gemfile kami:
... gem 'devise' ... group :development, :test do ... gem 'factory_girl_rails', '~> 4.5' ... end
Langkah 2: Buat model pengguna, inisialisasi permata yang devise
, dan tambahkan ke model pengguna (ini memungkinkan kelas pengguna digunakan untuk otentikasi).
rails g model user rails generate devise:install rails generate devise user
Langkah 3: Sertakan metode sintaks factory_girl
di file rails_helper.rb
kami untuk menggunakan versi singkat dari pembuatan pengguna dalam spesifikasi kami:
RSpec.configure do |config| config.include FactoryGirl::Syntax::Methods
Langkah 4: Tambahkan permata anggur ke DSL kami dan instal:
gem 'grape' bundle
Kasus Penggunaan dan Spesifikasi Login Pengguna
Backend kami perlu mendukung kemampuan login dasar. Mari kita buat kerangka untuk login_spec
, dengan asumsi bahwa permintaan login yang valid terdiri dari alamat email terdaftar dan pasangan kata sandi:
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
Jika salah satu parameter tidak ada, klien akan menerima kode status pengembalian HTTP 400 (yaitu, Permintaan Buruk), bersama dengan pesan kesalahan 'email hilang' atau 'kata sandi tidak ada'.
Untuk pengujian kami, kami akan membuat pengguna yang valid dan menetapkan email dan kata sandi pengguna sebagai parameter asli untuk rangkaian pengujian ini. Kemudian kami akan menyesuaikan hash parameter ini untuk setiap spesifikasi tertentu dengan menghilangkan kata sandi/email atau menggantinya.
Mari buat pengguna dan hash parameter di awal spesifikasi. Kami akan menempatkan kode ini setelah blok deskripsi:
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 } ...
Kami kemudian dapat memperluas konteks 'params yang hilang'/'kata sandi' sebagai berikut:
let(:params) { original_params.except(:password) } it_behaves_like '400' it_behaves_like 'json result' it_behaves_like 'contains error msg', 'password is missing'
Namun alih-alih mengulangi harapan di seluruh konteks 'email' dan 'sandi', kita dapat menggunakan contoh bersama yang sama sebagai harapan. Untuk ini, kita perlu menghapus komentar pada baris ini di file rails_helper.rb
kita:
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
Kami kemudian perlu menambahkan 3 contoh bersama RSpec ke dalam 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
Contoh bersama ini memanggil metode api_call
yang memungkinkan kita untuk mendefinisikan titik akhir API hanya sekali dalam spesifikasi kita (sesuai dengan prinsip KERING). Kami mendefinisikan metode ini sebagai berikut:
describe '/api/login' do ... def api_call *params post "/api/login", *params end ...
Kami juga perlu menyesuaikan pabrik untuk pengguna kami:
FactoryGirl.define do factory :user do password "Passw0rd" password_confirmation { |u| u.password } sequence(:email) { |n| "test#{n}@example.com" } end end
Dan akhirnya, sebelum menjalankan spesifikasi, kita perlu menjalankan migrasi:
rake db:migrate
Namun, ingatlah bahwa spesifikasi masih akan gagal pada saat ini, karena kami belum mengimplementasikan titik akhir API kami. Itu berikutnya.
Menerapkan Titik Akhir API Masuk
Sebagai permulaan, kami akan menulis kerangka kosong untuk API login kami ( 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
Selanjutnya, kita akan menulis kelas agregator yang menggabungkan titik akhir API ( app/api/api.rb
):
class API < Grape::API prefix 'api' mount Login end
OK, sekarang kita bisa memasang API kita di rute:
Rails.application.routes.draw do ... mount API => '/' ... end
Sekarang mari tambahkan kode untuk memeriksa parameter yang hilang. Kita dapat menambahkan kode itu ke api.rb
dengan menyelamatkan dari Grape::Exceptions::ValidationErrors
.
rescue_from Grape::Exceptions::ValidationErrors do |e| rack_response({ status: e.status, error_msg: e.message, }.to_json, 400) end
Untuk kata sandi yang tidak valid, kami akan memeriksa apakah kode respons http adalah 401 yang berarti akses tidak sah. Mari tambahkan ini ke konteks 'sandi salah':
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'
Logika yang sama kemudian ditambahkan ke konteks 'dengan login yang tidak ada' juga.
Kami kemudian menerapkan logika yang menangani upaya otentikasi yang tidak valid ke login.rb
kami sebagai berikut:
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
Pada titik ini semua spesifikasi negatif untuk api login akan berfungsi dengan baik, tetapi kita masih perlu mendukung spesifikasi positif untuk api login kita. Spesifikasi positif kami akan mengharapkan titik akhir untuk mengembalikan kode respons HTTP 200 (yaitu, sukses) dengan JSON yang valid dan token yang valid:
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
Mari tambahkan juga ekspektasi untuk kode respons 200 ke spec/support/shared.rb
:
RSpec.shared_examples '200' do specify 'returns 200' do api_call params expect(response.status).to eq(200) end end
Jika login berhasil, kami akan mengembalikan otentikasi_token valid pertama bersama dengan email pengguna dalam format ini:
{'email':<the_email_of_the_user>, 'token':<the users first valid token>}
Jika belum ada token seperti itu, kami akan membuatnya untuk pengguna saat ini:
... if user.present? && user.valid_password?(params[:password]) token = user.authentication_tokens.valid.first || AuthenticationToken.generate(user) status 200 else ...
Agar ini berfungsi, kita memerlukan kelas AuthenticationToken
yang dimiliki oleh pengguna. Kami akan membuat model ini, lalu menjalankan migrasi yang sesuai:
rails g model authentication_token token user:references expires_at:datetime rake db:migrate
Kami juga perlu menambahkan asosiasi yang sesuai ke model pengguna kami:
class User < ActiveRecord::Base has_many :authentication_tokens end
Kemudian kita akan menambahkan ruang lingkup yang valid ke kelas AuthenticationToken
:
class AuthenticationToken < ActiveRecord::Base belongs_to :user validates :token, presence: true scope :valid, -> { where{ (expires_at == nil) | (expires_at > Time.zone.now) } } end
Perhatikan bahwa kami menggunakan sintaks Ruby dalam pernyataan where
. Ini diaktifkan oleh penggunaan permata squeel
kami yang memungkinkan dukungan untuk sintaks Ruby dalam kueri rekaman aktif.
Untuk pengguna yang divalidasi, kami akan membuat entitas yang akan kami rujuk sebagai "pengguna dengan entitas token", memanfaatkan fitur permata grape-entity
.
Mari kita tulis spesifikasi untuk entitas kita dan letakkan di file 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
Selanjutnya mari tambahkan entitas ke user_entity.rb
:
module Entities class UserEntity < Grape::Entity expose :email end end
Dan akhirnya, tambahkan kelas lain ke user_with_token_entity.rb
:
module Entities class UserWithTokenEntity < UserEntity expose :token do |user, options| user.authentication_tokens.valid.first.token end end end
Karena kami tidak ingin token tetap valid tanpa batas waktu, kami akan membuat token tersebut kedaluwarsa setelah satu hari:
FactoryGirl.define do factory :authentication_token do token "MyString" expires_at 1.day.from_now user end end
Setelah semua ini selesai, sekarang kita dapat mengembalikan format JSON yang diharapkan dengan UserWithTokenEntity
yang baru ditulis:
... 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 ...
Dingin. Sekarang semua spesifikasi kami lulus dan persyaratan fungsional titik akhir api masuk dasar didukung.
Titik Akhir API Tinjauan Sesi Pemrograman Pair: Memulai
Backend kami harus mengizinkan pengembang resmi yang telah masuk untuk menanyakan ulasan sesi pemrograman pasangan.
Titik akhir API baru kami akan dipasang ke /api/pair_programming_session
dan akan mengembalikan ulasan milik sebuah proyek. Mari kita mulai dengan menulis kerangka dasar untuk spesifikasi ini:
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
Kami akan menulis titik akhir API kosong yang sesuai ( app/api/pair_programming_sessions.rb
) juga:
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
Kemudian mari kita mount api baru kita ( app/api/api.rb
):
... mount Login mount PairProgrammingSessions end
Mari kita perluas spesifikasi, dan titik akhir API, terhadap persyaratan satu per satu.
Titik Akhir API Tinjauan Sesi Pemrograman Pair: Validasi
Salah satu persyaratan keamanan non-fungsional terpenting kami adalah membatasi akses API ke sebagian kecil pengembang yang kami lacak, jadi mari kita tentukan bahwa:
... 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 ...
Kemudian kami akan membuat shared_example
di shared.rb
kami untuk mengonfirmasi bahwa permintaan tersebut berasal dari salah satu pengembang terdaftar kami:

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
Kita juga perlu membuat kelas ErrorCodes
(di app/models/error_codes.rb
):
module ErrorCodes DEVELOPER_KEY_MISSING = 1001 end
Karena kami mengharapkan API kami untuk berkembang di masa mendatang, kami akan menerapkan authorization_helper
yang dapat digunakan kembali di semua titik akhir API dalam aplikasi untuk membatasi akses ke pengembang terdaftar saja:
class PairProgrammingSessions < Grape::API helpers ApiHelpers::AuthenticationHelper before { restrict_access_to_developers }
Kita akan mendefinisikan metode restrict_access_to_developers
dalam modul ApiHelpers::AuthenticationHerlper
( app/api/api_helpers/authentication_helper.rb
). Metode ini hanya akan memeriksa apakah Authorization
kunci di bawah header berisi ApiKey
yang valid. (Setiap pengembang yang menginginkan akses ke API akan memerlukan ApiKey
yang valid. Ini dapat disediakan oleh administrator sistem atau melalui beberapa proses pendaftaran otomatis, tetapi mekanisme tersebut berada di luar cakupan artikel ini.)
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
Kita kemudian perlu membuat model ApiKey dan menjalankan migrasi: rails g model api_key token rake db:migrate
Setelah ini selesai, di spec/api/pair_programming_spec.rb
kami, kami dapat memeriksa apakah pengguna diautentikasi:
... it_behaves_like 'restricted for developers' it_behaves_like 'unauthenticated' ...
Mari kita juga mendefinisikan contoh bersama yang unauthenticated
yang dapat digunakan kembali di semua spesifikasi ( 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
Contoh yang dibagikan ini memerlukan token di tajuk pengembang jadi mari tambahkan itu ke spesifikasi kami ( spec/api/pair_programming_spec.rb
):
... describe '/api' do let(:api_key) { create :api_key } let(:developer_header) { {'Authorization' => api_key.token} } ...
Sekarang, di app/api/pair_programming_session.rb
, mari coba mengautentikasi pengguna:
... class PairProgrammingSessions < Grape::API helpers ApiHelpers::AuthenticationHelper before { restrict_access_to_developers } before { authenticate! } ...
Mari kita terapkan authenticate!
metode di 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 ...
(Perhatikan bahwa kita perlu menambahkan kode kesalahan BAD_AUTHENTICATION_PARAMS
ke kelas ErrorCodes
kita.)
Selanjutnya, mari kita tentukan apa yang terjadi jika pengembang memanggil API dengan token yang tidak valid. Dalam hal ini kode pengembalian akan menjadi 401 yang menandakan 'akses tidak sah'. Hasilnya harus JSON dan auditable harus dibuat. Jadi kami menambahkan ini ke 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 ...
Kami akan menambahkan contoh yang dibagikan "dapat diaudit", "berisi kode kesalahan", dan "berisi pesan kesalahan" ke 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 ...
Kita juga perlu membuat model audit_log:
rails g model audit_log backtrace data user:references rake db:migrate
Titik Akhir API Ulasan Sesi Pemrograman Pair: Mengembalikan Hasil
Untuk pengguna yang diautentikasi dan diotorisasi, panggilan ke titik akhir API ini harus mengembalikan serangkaian tinjauan sesi pemrograman berpasangan yang dikelompokkan berdasarkan proyek. Mari kita ubah spec/api/pair_programming_spec.rb
sesuai dengan itu:
... context 'valid params' do it_behaves_like '200' it_behaves_like 'json result' end ...
Ini menetapkan bahwa permintaan yang dikirimkan dengan api_key
valid dan parameter yang valid mengembalikan kode HTTP 200 (yaitu, sukses) dan bahwa hasilnya dikembalikan dalam bentuk JSON yang valid.
Kami akan menanyakan dan kemudian kembali dalam format JSON sesi pemrograman pasangan di mana salah satu peserta adalah pengguna saat ini ( 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 ...
Sesi pemrograman pasangan dimodelkan sebagai berikut dalam database:
- Hubungan 1-ke-banyak antara proyek dan sesi pemrograman berpasangan
- Hubungan 1-ke-banyak antara sesi pemrograman pasangan dan ulasan
- Hubungan 1-ke-banyak antara ulasan dan contoh kode
Mari buat model yang sesuai dan kemudian jalankan migrasi:
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
Kemudian kita perlu memodifikasi kelas PairProgrammingSession
dan Review
untuk memuat asosiasi 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
CATATAN: Dalam keadaan normal, saya akan membuat kelas-kelas ini dengan menulis spesifikasi untuk mereka terlebih dahulu, tetapi karena itu di luar cakupan artikel ini, saya akan melewatkan langkah itu.
Sekarang kita perlu menulis kelas-kelas yang akan mengubah model kita menjadi representasi JSON mereka (disebut sebagai entitas anggur dalam terminologi anggur). Untuk mempermudah, kita akan menggunakan pemetaan 1-ke-1 antara model dan entitas anggur.
Kita mulai dengan mengekspos bidang code
dari CodeSampleEntity
(dalam api/entities/code_sample_entity.rb
):
module Entities class CodeSampleEntity < Grape::Entity expose :code end end
Kemudian kami mengekspos user
dan code_samples
terkait dengan menggunakan kembali UserEntity
yang sudah ditentukan dan CodeSampleEntity
:
module Entities class ReviewEntity < Grape::Entity expose :user, using: UserEntity expose :code_samples, using: CodeSampleEntity end end
Kami juga mengekspos bidang name
dari ProjectEntity
:
module Entities class ProjectEntity < Grape::Entity expose :name end end
Terakhir, kami merakit entitas menjadi PairProgrammingSessionsEntity
baru di mana kami mengekspos project
, host_user
, visitor_user
dan 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
Dan dengan itu, API kami diimplementasikan sepenuhnya!
Menghasilkan Data Uji
Untuk tujuan pengujian, kami akan membuat beberapa contoh data di db/seeds.rb
. File ini harus berisi semua pembuatan record yang diperlukan untuk seed database dengan nilai defaultnya. Data kemudian dapat dimuat dengan rake db:seed
(atau dibuat dengan db saat db:setup
dipanggil). Berikut adalah contoh dari apa yang dapat mencakup:
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'
Sekarang aplikasi kami siap digunakan dan kami dapat meluncurkan server Rails kami.
Menguji API
Kami akan menggunakan Swagger untuk melakukan beberapa pengujian berbasis browser manual terhadap API kami. Beberapa langkah pengaturan diperlukan agar kita dapat menggunakan Swagger.
Pertama, kita perlu menambahkan beberapa permata ke gemfile kita:
... gem 'grape-swagger' gem 'grape-swagger-ui' ...
Kami kemudian menjalankan bundle
untuk menginstal permata ini.
Kami juga perlu menambahkan ini ke aset ke saluran aset kami (di config/initializers/assets.rb
):
Rails.application.config.assets.precompile += %w( swagger_ui.js ) Rails.application.config.assets.precompile += %w( swagger_ui.css )
Terakhir, di app/api/api.rb
kita perlu memasang generator swagger:
... add_swagger_documentation end ...
Sekarang kita dapat memanfaatkan UI bagus Swagger untuk menjelajahi API kita hanya dengan membuka http://localhost:3000/api/swagger
.
Swagger menyajikan titik akhir API kami dengan cara yang dapat dijelajahi dengan baik. Jika kita mengklik titik akhir, Swagger mencantumkan operasinya. Jika kita mengklik sebuah operasi, Swagger menunjukkan parameter yang diperlukan dan opsional serta tipe datanya.
Satu detail yang tersisa sebelum melanjutkan: Karena kami membatasi penggunaan pengembang API dengan api_key
yang valid, kami tidak akan dapat mengakses titik akhir API langsung dari browser karena server akan memerlukan api_key
yang valid di header HTTP. Kami dapat melakukannya untuk tujuan pengujian di Google Chrome dengan menggunakan plugin Modifikasi Header untuk Google Chrome. Plugin ini akan memungkinkan kita untuk mengedit header HTTP dan menambahkan api_key
yang valid (kita akan menggunakan dummy api_key
dari 12345654321
yang kita sertakan dalam file seed database kita).
Oke, sekarang kita siap untuk menguji!
Untuk memanggil titik akhir API pair_programming_sessions
, pertama-tama kita harus masuk. Kita hanya akan menggunakan kombinasi email dan kata sandi dari file benih database kita dan mengirimkannya melalui Swagger ke titik akhir API login, seperti yang ditunjukkan di bawah ini.
Seperti yang Anda lihat di atas, token milik pengguna tersebut dikembalikan, yang menunjukkan bahwa API login berfungsi sebagaimana mestinya. Kita sekarang dapat menggunakan token itu untuk berhasil melakukan operasi GET /api/pair_programming_sessions.json
.
Seperti yang ditunjukkan, hasilnya dikembalikan sebagai objek JSON hierarkis yang diformat dengan benar. Perhatikan bahwa struktur JSON mencerminkan dua asosiasi 1-ke-banyak bersarang, karena proyek memiliki beberapa tinjauan, dan tinjauan memiliki beberapa contoh kode. Jika kami tidak mengembalikan struktur dengan cara ini, maka pemanggil API kami perlu meminta tinjauan secara terpisah untuk setiap proyek yang akan memerlukan pengiriman N kueri ke titik akhir API kami. Dengan struktur ini, kami memecahkan masalah kinerja kueri N+1.
Bungkus
Seperti yang ditunjukkan di sini, spesifikasi komprehensif untuk API Anda membantu memastikan bahwa API yang diterapkan dengan benar dan memadai menangani kasus penggunaan yang dimaksudkan (dan tidak diinginkan!).
Meskipun contoh API yang disajikan dalam tutorial ini cukup mendasar, pendekatan dan teknik yang telah kami tunjukkan dapat berfungsi sebagai dasar untuk API yang lebih canggih dengan kompleksitas arbitrer menggunakan permata Grape. Tutorial ini diharapkan menunjukkan bahwa Grape adalah permata yang berguna dan fleksibel yang dapat membantu memfasilitasi implementasi JSON API di aplikasi Rails Anda. Menikmati!