次に一旦DBのMySQLの設定はスキップしたいため、一時的なDB設定として使えるnulldbのgem「activerecord-nulldb-adapter」をインストールします。
以下のようにファイル「api/Gemfile」の「group :development, :test do」の部分に「gem ‘activerecord-nulldb-adapter’」を追記します。
hello worldを出力するAPIを作成
次にhello worldを試すため、テキスト「hello world !!」を出力するAPIを作成してみます。
ファイル「api/app/controllers/application_controller.rb」、「api/config/routes.rb」それぞれ以下のように修正します。
class ApplicationController < ActionController : : API
def hello
render plain : "hello world !!"
end
end
Rails . application. routes. draw do
・・
scope "api" do
scope "v1" do
get "/hello" , to : "application#hello"
end
end
end
次にブラウザで「http://localhost:3010/api/v1/hello」にアクセスし、以下のように表示されればOKです。
テストコード用にRSpecの設定
次にテストを実施できるようにするため、RSpecを導入します。
以下のようにファイル「api/Gemfile」の「group :development, :test do」の部分にテスト関連のgemを追加します。
・・
group :development , :test do
gem "debug" , platforms : %i[ mri windows ]
gem "activerecord-nulldb-adapter"
gem "rspec-rails"
gem "factory_bot_rails"
gem "shoulda-matchers"
end
・・
次に以下のコマンドを実行し、RSpecをインストールします。
$ docker compose exec api bundle install
$ docker compose exec api rails g rspec:install
次にRSpecの初期設定として「api/.rspec」に「–color」と「–format documentation」を追加します。
--require spec_helper
--color
--format documentation
次に以下のコマンドを実行し、テスト実行用コマンドのエイリアスを設定します。
$ docker compose exec api bundle binstubs rspec-core
次に不要なspecファイルが無駄に作られないようにするため、「api/config/application.rb」にジェネレータの設定を追加します。
module Api
class Application < Rails : : Application
・・・
config. generators do | g |
g. test_framework :rspec ,
fixtures : false ,
helper_specs : false ,
view_specs : false ,
routing_specs : false
end
end
end
次に環境変数に「RAILS_ENV=development」を設定した場合はRSpecの実行時の環境変数も「development」になってしまう ため、「api/spec/rails_helper.rb」を以下のように修正します。
・・・
ENV [ 'RAILS_ENV' ] = 'test'
・・・
次に以下のコマンドを実行し、RSpecを実行して確認します。
$ docker compose exec api bin/rspec
※binstubを設定していない場合はコマンド「bundle exec rspec」を使います。
テスト実行後、以下のようにRSpecが実行できればOKです。
RSpecでhello worldを出力するAPIのテストコードを作成
次に上記で作成したAPIのテストコードを作成するため、以下のコマンドを実行してテストコード用のファイルを作成します。
$ docker compose exec api rails g rspec:request application
次にファイル「api/spec/requests/applications_spec.rb」を以下のように修正します。
require 'rails_helper'
RSpec . describe "Applications" , type : :request do
describe "GET /api/v1/hello" do
it "ステータス200で正常終了すること" do
get hello_path
expect( response) . to have_http_status( :success )
end
it "「hello world !!」を出力すること" do
get hello_path
expect( response. body) . to eq( "hello world !!" )
end
end
end
次に以下のコマンドを実行し、RSpecを実行してテスト結果を確認します。
$ docker compose exec api bin/rspec
テスト実行後、以下のような結果になればOKです。
Railsのタイムゾーンを日本に変更する
次にRailsのタイムゾーンを日本(Asia/Tokyo)に変更するため、「api/config/application.rb」に設定を追加します。
module Api
class Application < Rails : : Application
・・・
config. time_zone = "Asia/Tokyo"
config. active_record. default_timezone = :local
end
次に以下のコマンドを実行し、コンテナを再起動します。
$ docker compose down
$ docker compose up -d
RailsのAPIモードでCookieを使えるようにする
RailsのAPIモードはデフォルトではCookieを使えない ため、使えるようにしておきます。
まずはファイル「api/Gemfile」でコメントアウトされている「gem “rack-cors”」の部分をコメントを外して有効にします。
次に以下のコマンドを実行し、gemをインストールします。
$ docker compose exec api bundle install
次にファイル「api/config/initializers/cors.rb」、「api/config/application.rb」、「api/app/controllers/application_controller.rb」をそれぞれ以下のように修正します。
・・・
Rails . application. config. middleware. insert_before 0 , Rack : : Cors do
allow do
origins "http://localhost:3000"
resource "/api/v1/*" ,
headers : :any ,
methods : [ :get , :post , :put , :patch , :delete , :options , :head ] ,
credentials : true
end
end
※api/config/initializers/cors.rbはクロスオリジンに関する設定ファイルで、 originsにはフロントエンド側のドメインを設定します。
次に「Cookies」を使えるようにするため、「api/config/application.rb」に設定を追加します。
module Api
class Application < Rails : : Application
・・・
config. middleware. use ActionDispatch : : Cookies
config. action_dispatch. cookies_same_site_protection = :none
end
次にファイル「api/app/controllers/application_controller.rb」で「ActionController::Cookies」読み込み、上記で作成したAPIでCookieを設定するよう修正します。
class ApplicationController < ActionController : : API
include ActionController : : Cookies
def hello
cookies[ :hello ] = {
value : "hello" ,
expires : 90 . day. from_now,
path : '/' ,
httponly : true ,
secure : true
}
render plain : "hello world !!"
end
end
次にブラウザで「http://localhost:3010/api/v1/hello」にアクセスし、ブラウザの開発者用モードで「Application > Cookies > http://localhost:3010」を確認してCookieがあればOKです。
DBとしてMySQLを追加する
次にDBとしてMySQLを追加します。ファイル「api/.env」、「api/compose.yml」、「api/config/database.yml」をそれぞれ以下のように修正します。
・・・
MYSQL_ROOT_PASSWORD=password
※api/.envにMySQLのルートパスワードの設定を追加します。
services:
api:
container_name: rails-s-api
build:
context: .
dockerfile: ./docker/local/Dockerfile
args:
- RAILS_ENV=${RAILS_ENV}
- BUNDLER_VERSION=${BUNDLER_VERSION}
- LANG=${LANG}
- TZ=${TZ}
- PORT=${PORT}
volumes:
- .:/api
ports:
- 3010:${PORT}
env_file:
- ./.env
depends_on:
- db
command: bundle exec puma -C "config/puma.rb"
# DBに関する設定
db:
container_name: rails-s-db
image: mysql:8.0.36
env_file:
- ./.env
ports:
- 3306:3306
volumes:
- ./tmp/db:/var/lib/mysql
※api/compose.ymlにDB用のコンテナ設定を追加します。
・・・
default: &default
# adapterを初期設定に戻す
adapter: mysql2
encoding: utf8mb4
# コレクションを指定
collation: utf8mb4_bin
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
# 以下をコメントアウト
# username: root
# password:
# host: localhost
development:
<<: *default
database: api_development
# DBへの接続情報を追加
username: root
password: <%= ENV["MYSQL_ROOT_PASSWORD"] %>
host: db
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
<<: *default
database: api_test
# DBへの接続情報を追加
username: root
password: <%= ENV["MYSQL_ROOT_PASSWORD"] %>
host: db
・・・
※api/config/database.ymlにDBへの接続設定を追加修正します。
次に以下のコマンドを実行し、コンテナを再起動します。
$ docker compose down
$ docker compose up -d
次に以下のコマンドを実行し、DBを作成します。
$ docker compose exec api rails db:create
次にブラウザで「http://localhost:3010」にアクセスし、エラーにならず正常に表示されればOKです。
ユーザーテーブルとデータを操作するAPIを作成する
次はユーザーテーブルを作成し、データを操作するCRUD処理のAPIを作成してみます。
データ削除時は論理削除を行いたいため、まずはGemfileにgem「discard」を追加します。
・・・
# 論理削除用
gem "discard"
・・・
次に以下のコマンドを実行してgemをインストールします。
$ docker compose exec api bundle install
次に以下のコマンドを実行し、ユーザーテーブル用の各種ファイルを作成します。
$ docker compose exec api rails g model User uid:string member_id:integer last_name:string first_name:string email:string discarded_at:datetime
次に作成されたマイグレーションファイルとモデルファイルについて、それぞれ以下のように修正します。
class CreateUsers < ActiveRecord : : Migration [ 7.1 ]
def change
create_table :users do | t |
t. string :uid , null : false
t. integer :member_id
t. string :last_name , null : false
t. string :first_name , null : false
t. string :email , null : false
t. timestamps
t. datetime :discarded_at
end
add_index :users , :uid , unique : true
add_index :users , :member_id , unique : true
add_index :users , :email , unique : true
add_index :users , :discarded_at
add_index :users , [ :email , :discarded_at ] , unique : true , name : 'unique_email_discarded_at'
end
end
class User < ApplicationRecord
include Discard : : Model
default_scope - > { kept }
validates :uid , presence : true , uniqueness : true , length : { maximum : 255 }
validates :member_id , uniqueness : true , length : { maximum : 9 }
validates :last_name , presence : true , length : { maximum : 255 }
validates :first_name , presence : true , length : { maximum : 255 }
validates :email , presence : true , uniqueness : { scope : :discarded_at } , length : { maximum : 255 }
def f_created_at
created_at. strftime( '%Y/%m/%d %H:%M:%S' )
end
def f_updated_at
updated_at. strftime( '%Y/%m/%d %H:%M:%S' )
end
def f_discarded_at
if discarded_at
discarded_at. strftime( '%Y/%m/%d %H:%M:%S' )
else
nil
end
end
end
次に以下のコマンドを実行し、マイグレーションを実行します。
$ docker compose exec api rails db:migrate
$ docker compose exec api rails db:migrate RAILS_ENV= test
次に以下のコマンドを実行し、サービス層を構築するための各種ファイルを作成します。
$ cd app
$ mkdir services
$ cd services
$ mkdir user
$ cd user
$ touch create_user_service.rb
$ touch get_a_user_service.rb
$ touch update_user_service.rb
$ touch delete_user_service.rb
$ touch get_users_with_discarded_service.rb
$ cd .. /.. /..
※業務ロジックを全てコントローラーに書くのはよくないので、サービス層を作ってコントローラーから呼び出すようにします。ただし、全てをサービス層に寄せるのもよくないので、処理の流れやDBのやり取りなどはサービス層に寄せつつ、コアなルールはモデルに寄せるようにした方がいいです。
次に作成した各種サービスファイルをそれぞれ以下のように記述します。
class User : : CreateUserService
def self . call ( . . . )
new ( . . . ) . call
end
def initialize ( params )
@params = params
end
def call
@user = User . new ( @params )
ActiveRecord : : Base . transaction do
@user . save!
@member_id = 100000000 + @user . id
@user . member_id = @member_id
@user . save!
end
@user
rescue = > e
nil
end
end
class User : : GetAUserService
def self . call ( . . . )
new ( . . . ) . call
end
def initialize ( uid )
@uid = uid
end
def call
@user = User . where( uid : @uid ) . first
end
end
class User : : UpdateUserService
def self . call ( . . . )
new ( . . . ) . call
end
def initialize ( params , uid )
@params = params
@uid = uid
end
def call
@user = User . where( uid : @uid ) . first
ActiveRecord : : Base . transaction do
@user . update! ( @params )
end
@user
rescue = > e
nil
end
end
class User : : DeleteUserService
def self . call ( . . . )
new ( . . . ) . call
end
def initialize ( uid )
@uid = uid
end
def call
ActiveRecord : : Base . transaction do
@user = User . where( uid : @uid ) . first
@user . discard!
end
@user
rescue = > e
nil
end
end
class User : : GetUsersWithDiscardedService
def self . call ( . . . )
new ( . . . ) . call
end
def initialize ( )
end
def call
@users = User . with_discarded
end
end
次に以下のコマンドを実行し、コンテナを再起動します。
$ docker compose down
$ docker compose build --no-cache
$ docker compose up -d
次に以下のコマンドを実行し、ユーザーコントローラーを作成します。
$ docker compose exec api rails g controller users
次に作成したユーザーコントローラーを次のように記述します。
class UsersController < ApplicationController
def create
res = User : : CreateUserService . call( create_params)
if res
render json : res, status : :created
else
render json : { message : "会員登録ができませんでした。" } , status : :internal_server_error
end
end
def get_user
uid = params[ :uid ]
user = User : : GetAUserService . call( uid)
render json : user
end
def update
uid = params[ :uid ]
res = User : : UpdateUserService . call( update_params, uid)
if res
render json : res
else
render json : { message : "会員情報の更新ができませんでした。" } , status : :internal_server_error
end
end
def delete
uid = params[ :uid ]
res = User : : DeleteUserService . call( uid)
if res
render json : { message : "OK" } , status : :ok
else
render json : { message : "退会処理ができませんでした。" } , status : :internal_server_error
end
end
def get_users_with_discarded
users = User : : GetUsersWithDiscardedService . call( )
render json : users
end
private
def create_params
params. require ( :user ) . permit( :uid , :last_name , :first_name , :email )
end
def update_params
params. require ( :user ) . permit( :last_name , :first_name , :email )
end
end
※Railsでは登録や更新時には対象の項目以外を更新させないようにするため、ストロングパラメータを使います。
次にルーティングファイルにルートを追加します。
Rails . application. routes. draw do
・・
scope "api" do
scope "v1" do
get "/hello" , to : "application#hello"
post "/user" , to : "users#create"
get "/user/:uid" , to : "users#get_user"
put "/user/:uid" , to : "users#update"
delete "/user/:uid" , to : "users#delete"
get "/users/with-discarded" , to : "users#get_users_with_discarded"
end
end
end
これでCRUD処理をするAPIの準備が整ったので、Postmanを使って試してみます。(Postmanについての詳細は割愛させていただきます。)
まずはユーザー作成用の「http://localhost:3010/api/v1/user」をPOSTで実行し、下図のように正常終了すればOKです。
次にユーザー取得用の「http://localhost:3010/api/v1/user/XYZ123abc1」をGETで実行し、下図のようにユーザー情報が取得できればOKです。
次にユーザー情報更新用の「http://localhost:3010/api/v1/user/XYZ123abc1」をPUTで実行し、下図のように正常終了すればOKです。
次にもう一度ユーザー取得用の「http://localhost:3010/api/v1/user/XYZ123abc1」をGETで実行し、下図のように対象項目が更新されていればOKです。
次にユーザーを論理削除する「http://localhost:3010/api/v1/user/XYZ123abc1」をDELETEで実行し、下図のように正常終了すればOKです。
次にもう一度ユーザー取得用の「http://localhost:3010/api/v1/user/XYZ123abc1」をGETで実行し、対象データが存在せずnullが取得できればOKです。
次に削除済みデータも含めて全てのユーザーを取得する「http://localhost:3010/api/v1/users/with-discarded」をGETで実行し、下図のようにユーザー情報が取得でき、discarded_atに削除日が設定されていればOKです。
最後に
今回はRails7の最小構成でバックエンドAPIを開発する方法についてまとめました。
久しぶりに触ってみた感じ、やはりRailsは色々なところが自動化されていて、使いやすいフレームワーク だなと改めて思いました。
また私自身が実務経験を経てレベルアップしたこともあり、以前よりもいい感じでコードを書けるようになってきたのが実感 できて良かったです。
過去にもRailsの記事は書いてきましたが、今回ご紹介した方法の方がシンプルでいい感じに作り始めることができると思うので、これから試す方はぜひ参考にしてみて下さい!
各種SNSなど
各種SNSなど、チャンネル登録やフォローをしていただけると励みになるので、よければぜひお願いします!
The following two tabs change content below.
SEを5年経験後、全くの未経験ながら思い切ってブロガーに転身し、月間13万PVを達成。その後コロナの影響も受け、以前から興味があったWeb系エンジニアへのキャリアチェンジを決意。現在はWeb系エンジニアとして働きながら、プロゲーマーとしても活躍できるように活動中。
コメントを残す