Contents
開発環境のセットアップ
このセクションでは、Rails 7+ の API アプリを ローカル と コンテナ の両方で快適に動かすために必要なツール群と、そのインストール手順・設定方法を解説します。バージョン管理が不十分だと「自分の環境では動く」の罠にはまりやすく、チーム全体で同一の Ruby/Gems 環境を保つことが重要です。
Ruby とバージョン管理ツールのインストール
rbenv(macOS)または asdf(Linux/macOS)を用いて Ruby のバージョンと gemset を固定します。以下は macOS で rbenv + rbenv-gemset プラグインを使う例です。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# Homebrew で rbenv と ruby-build をインストール brew install rbenv ruby-build # シェルの初期化ファイルに rbenv のロード設定を追記(~/.zshrc 等) echo 'if command -v rbenv >/dev/null; then eval "$(rbenv init -)"; fi' >> ~/.zshrc source ~/.zshrc # gemset 管理プラグインを導入 git clone https://github.com/jf/rbenv-gemset.git $(rbenv root)/plugins/rbenv-gemset # 任意の Ruby バージョンをインストールし、global に設定 rbenv install 3.2.2 rbenv global 3.2.2 # プロジェクトルートで .ruby-version と .ruby-gemset を作成 echo "3.2.2" > .ruby-version # Ruby バージョンを固定 echo "my_api_gemset" > .ruby-gemset # gemset 名を固定 # 作成した gemset を有効化(以降の bundle install はこの環境に入る) rbenv gemset create 3.2.2 my_api_gemset rbenv rehash |
.ruby-version と .ruby-gemset をリポジトリへコミットしておけば、git clone 後に rbenv install と rbenv gemset create だけで同一環境が再現できます。
Bundler のバージョン確認と互換性対応
Bundler 2.1 以降では bundle add <gem> が使えますが、古い CI 環境や社内サーバーでは 2.0 系がインストールされているケースがあります。そのため 必ずバージョンを確認し、必要に応じて従来の手順へフォールバック できるようにします。
|
1 2 3 4 5 6 7 8 9 10 11 |
# Bundler のバージョン表示 bundle -v # e.g. Bundler version 2.3.26 # バージョンが 2.1 未満の場合は Gemfile に追記して bundle install を実行 if [[ $(bundle -v | awk '{print $3}') < "2.1" ]]; then echo "# bundle add が使えない環境向けの代替手順" else # Bundler ≥2.1 のときは便利な bundle add を使用 bundle add pg rspec-rails factory_bot_rails fi |
ポイント
- Gemfile に直接記述する方法(gem 'pg' 等)はすべての環境で動作します。
- bundle install --without development test といった除外オプションは 本番イメージ構築時だけ 用いるようにし、ローカル開発では全 gem をインストールしておきます。
API モードでプロジェクトを作成
この章では Rails の --api フラッグとテストフレームワーク RSpec のセットアップ手順を示します。API アプリはビュー系ミドルウェアが省かれるため軽量ですが、開発時に必要な gem が欠如するとすぐにビルドエラーになる ので注意が必要です。
新規プロジェクトの生成と RSpec の導入
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# API モードで新規作成(テストフレームワークは除外) rails new my_api --api -T --database=postgresql cd my_api # Bundler が 2.1 以上の場合は bundle add、そうでなければ Gemfile に追記 if [[ $(bundle -v | awk '{print $3}') < "2.1" ]]; then # 古い環境向けの手動追加例 echo "gem 'rspec-rails', '~> 6'" >> Gemfile echo "gem 'factory_bot_rails'" >> Gemfile else bundle add rspec-rails factory_bot_rails database_cleaner-active_record fi # RSpec の初期化 bundle install rails generate rspec:install |
ポイント
- -T オプションでデフォルトの MiniTest を除外し、RSpec に完全に置き換えます。
- bundle add が使えない環境でも手動で Gemfile に追記すれば同等の結果が得られます。
基本的な RESTful エンドポイントの実装
ここでは 記事 (Article) リソースを例に、名前空間付き API のルーティング・コントローラ・モデル・バリデーションまで一通り実装します。RESTful 設計は外部クライアントとの合意形成をシンプルにし、将来の拡張やテスト自動化にも好影響を与えます。
ルーティングの定義
|
1 2 3 4 5 6 7 8 9 |
# config/routes.rb の冒頭で API バージョン付けを行います。 Rails.application.routes.draw do namespace :api, defaults: { format: :json } do namespace :v1 do resources :articles, only: [:index, :show, :create, :update, :destroy] end end end |
コントローラ実装
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# app/controllers/api/v1/articles_controller.rb の冒頭で目的を説明します。 module Api module V1 class ArticlesController < ApplicationController before_action :set_article, only: %i[show update destroy] # GET /api/v1/articles def index render json: Article.all end # GET /api/v1/articles/:id def show render json: @article end # POST /api/v1/articles def create article = Article.new(article_params) if article.save render json: article, status: :created else render json: { errors: article.errors.full_messages }, status: :unprocessable_entity end end # PATCH/PUT /api/v1/articles/:id def update if @article.update(article_params) render json: @article else render json: { errors: @article.errors.full_messages }, status: :unprocessable_entity end end # DELETE /api/v1/articles/:id def destroy @article.destroy head :no_content end private def set_article @article = Article.find(params[:id]) end def article_params params.require(:article).permit(:title, :content) end end end end |
モデルとバリデーション
|
1 2 3 4 |
# マイグレーション作成と実行 rails generate model Article title:string content:text rails db:migrate |
|
1 2 3 4 5 6 |
# app/models/article.rb の冒頭でモデルの責務を示します。 class Article < ApplicationRecord validates :title, presence: true, length: { maximum: 150 } validates :content, presence: true, length: { minimum: 10 } end |
ポイント
- 名前空間 (Api::V1) と defaults: { format: :json } により、全エンドポイントが JSON を返すことを保証します。
- バリデーションはモデル側に集中させ、コントローラは「成功/失敗」の分岐だけに留めます。
シリアライザ・CORS・JWT 認証の設定
API の実運用では 出力フォーマット統一 と 外部ドメインからのアクセス許可、そして ステートレス認証 が必須です。このセクションでそれぞれの導入手順を示します。
JSON シリアライザ(ActiveModelSerializers)の導入
|
1 2 3 4 5 6 7 8 9 |
# Bundler のバージョンに合わせてインストール方法を切り替えます。 if [[ $(bundle -v | awk '{print $3}') < "2.1" ]]; then echo "gem 'active_model_serializers', '~> 0.10'" >> Gemfile else bundle add active_model_serializers fi bundle install rails g serializer article |
|
1 2 3 4 5 |
# app/serializers/article_serializer.rb の冒頭でシリアライズ対象を明示します。 class ArticleSerializer < ActiveModel::Serializer attributes :id, :title, :content, :created_at, :updated_at end |
render json: @article と書くだけで AMS が自動的に上記属性だけを返します。
rack‑cors による CORS 設定
|
1 2 3 4 5 6 7 8 |
# CORS 用ミドルウェアのインストール(同様にバージョン対策) if [[ $(bundle -v | awk '{print $3}') < "2.1" ]]; then echo "gem 'rack-cors'" >> Gemfile else bundle add rack-cors fi bundle install |
|
1 2 3 4 5 6 7 8 9 10 11 |
# config/initializers/cors.rb の冒頭で許可ポリシーを説明します。 Rails.application.config.middleware.insert_before 0, Rack::Cors do allow do origins 'https://frontend.example.com', 'http://localhost:3000' resource '*', headers: :any, methods: %i[get post put patch delete options head], expose: ['Authorization'] end end |
devise と devise‑jwt によるトークン認証
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# 認証系 gem のインストール(Bundler バージョンに応じて) if [[ $(bundle -v | awk '{print $3}') < "2.1" ]]; then cat <<EOS >> Gemfile gem 'devise' gem 'devise-jwt' EOS else bundle add devise devise-jwt fi bundle install rails generate devise:install rails generate devise User |
|
1 2 3 4 5 6 7 8 9 |
# app/models/user.rb の冒頭で JWT 用設定をコメント付きで示します。 class User < ApplicationRecord # データベース認証 + JWT 発行の組み合わせ devise :database_authenticatable, :registerable, :jwt_authenticatable, jwt_revocation_strategy: Devise::JWT::RevocationStrategies::Null end |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# config/initializers/devise.rb の冒頭で JWT シークレット取得先を明示します。 Devise.setup do |config| # ...省略... config.jwt do |jwt| jwt.secret = Rails.application.credentials.dig(:jwt_secret) || ENV['JWT_SECRET'] jwt.dispatch_requests = [ ['POST', %r{^/api/v1/login$}] ] jwt.revocation_requests = [ ['DELETE', %r{^/api/v1/logout$}] ] jwt.expiration_time = 2.hours.to_i end end |
ポイント
- before_action :authenticate_user! を API コントローラに追加すれば、JWT が無いリクエストは自動的に 401 エラーになります。
テスト・バージョニング・Docker コンテナ化・デプロイ
この最終章では 安全なリリースフロー を構築するための3つの柱(テスト、コンテナ、CI/CD)を具体的に示します。各工程が自動化されていれば、コード変更時に人為的ミスが入りにくく、運用コストも大幅に削減できます。
API バージョニングのベストプラクティス
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# config/routes.rb の冒頭でバージョニング方針をコメントします。 Rails.application.routes.draw do namespace :api, defaults: { format: :json } do # 現行バージョンは v1。将来的に非互換変更が必要になったら v2 を追加してください。 namespace :v1 do resources :articles end # 将来の例: # namespace :v2 do # resources :articles # end end end |
- URL に
api/v1/を必ず含め、コントローラも同様に名前空間で分離します。 - 非互換変更は新しい名前空間を作成し、旧バージョンはそのまま残すことでクライアント側の移行期間を確保できます。
RSpec と factory_bot によるテスト基盤
|
1 2 3 4 5 6 7 8 |
# spec/factories/articles.rb の冒頭でファクトリの目的を説明します。 FactoryBot.define do factory :article do title { "サンプル記事タイトル" } content { "この記事は RSpec のテスト用に作成されたものです。" } end end |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# spec/models/article_spec.rb の冒頭でモデルバリデーションのチェックを行う旨を書きます。 require 'rails_helper' RSpec.describe Article, type: :model do it "valid with all required attributes" do expect(build(:article)).to be_valid end it "invalid without a title" do article = build(:article, title: nil) expect(article).not_to be_valid expect(article.errors[:title]).to include("can't be blank") end end |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# spec/requests/api/v1/articles_spec.rb の冒頭でエンドポイントテストの概要を記載します。 require 'rails_helper' RSpec.describe "Articles API", type: :request do let!(:articles) { create_list(:article, 3) } describe "GET /api/v1/articles" do before { get '/api/v1/articles' } it "returns all articles" do expect(JSON.parse(response.body).size).to eq(3) end it "responds with HTTP 200" do expect(response).to have_http_status(:ok) end end end |
テストは bundle exec rspec でローカル実行でき、GitHub Actions 等の CI に組み込めば プッシュごとに自動検証 が走ります。
Dockerfile の改善(開発用 vs 本番用)
Docker イメージをビルドするときは、開発時に必要な gem は除外しない ことが重要です。以下のように ビルドステージを分割 すれば、本番イメージは軽量化しつつローカル開発コンテナはフルセットで動作します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# Dockerfile (マルチステージ構成) ## ---------- ビルドステージ ---------- FROM ruby:3.2-slim AS builder # 必要なシステムパッケージをインストール RUN apt-get update -qq && \ apt-get install -y build-essential libpq-dev nodejs yarn && \ rm -rf /var/lib/apt/lists/* WORKDIR /app # Gemfile と lock ファイルだけを先にコピーしてキャッシュを活用 COPY Gemfile* ./ # 開発・テスト gem もインストール(本番イメージでは除外される) RUN bundle config set --local without '' && \ bundle install --jobs 4 ## ---------- 本番ステージ ---------- FROM ruby:3.2-slim AS production WORKDIR /app COPY --from=builder /usr/local/bundle /usr/local/bundle COPY . . # 本番環境では development と test を除外してインストール済みの bundle を流用 RUN bundle config set --local without 'development test' && \ bundle install EXPOSE 3000 CMD ["rails", "server", "-b", "0.0.0.0"] |
docker-compose.yml(開発向け)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
version: '3.9' services: db: image: postgres:15-alpine environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: password POSTGRES_DB: my_api_development volumes: - pgdata:/var/lib/postgresql/data web: build: context: . target: builder # 開発時は builder ステージを使用 command: bundle exec rails s -p 3000 -b '0.0.0.0' depends_on: - db ports: - "3000:3000" environment: DATABASE_URL: postgres://postgres:password@db:5432/my_api_development volumes: - .:/app # ソースコードのリアルタイム反映 volumes: pgdata: |
- 開発 コンテナは
builderステージをそのまま使用し、全 gem が利用可能です。 - 本番デプロイ の際は
docker build --target productionを実行すれば、不要な開発依存が除外された軽量イメージが生成されます。
CI/CD とデプロイ(Heroku / Render)
|
1 2 3 4 5 6 |
# Heroku へのコンテナデプロイ例 heroku create my-api-demo --stack=container git push heroku main # Dockerfile が自動的にビルドされる heroku addons:create heroku-postgresql:hobby-dev heroku run rails db:migrate |
Render でも同様に Dockerfile と 環境変数(RAILS_MASTER_KEY, DATABASE_URL, JWT_SECRET)を設定すれば、プッシュだけで自動ビルド・テストが走ります。GitHub Actions のサンプルワークフローは以下の通りです。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# .github/workflows/ci.yml name: CI on: push: branches: [ main ] pull_request: jobs: test: runs-on: ubuntu-latest services: postgres: image: postgres:15-alpine env: POSTGRES_USER: postgres POSTGRES_PASSWORD: password POSTGRES_DB: my_api_test ports: ["5432:5432"] options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - uses: actions/checkout@v3 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: '3.2' bundler-cache: true - name: Install dependencies run: bundle install --jobs 4 - name: Prepare DB env: RAILS_ENV: test DATABASE_URL: postgres://postgres:password@localhost:5432/my_api_test run: | bundle exec rails db:create db:migrate - name: Run RSpec run: bundle exec rspec |
ポイント
- CI では RAILS_ENV=test と同一の PostgreSQL コンテナを立ち上げ、ローカルと同様にテストが実行できます。
- 本番デプロイは Dockerfile の production ステージを利用することで、最小サイズかつセキュリティリスクの少ないイメージが生成されます。
まとめ
本稿では .ruby-gemset の作成方法、Bundler バージョン依存のインストール手順、そして Dockerfile が開発環境で gem を除外しないようにする対策 を含め、Rails 7+ API アプリを構築・テスト・デプロイするまでのフルパイプラインを解説しました。
.ruby-versionと.ruby-gemsetによりチーム全員が同一 Ruby 環境を再現できる- Bundler のバージョンチェックで
bundle addが使えない環境でも安全に gem を追加可能 - マルチステージ Dockerfile で 開発時はフルセット、リリース時は軽量化 を実現
これらを順守すれば、ローカルと本番・CI の差異がほぼなくなり、変更に強い API サービスの運用が可能になります。ぜひ自プロジェクトで試してみてください。