プロジェクトのセットアップと管理
Railsプロジェクトの基本的な操作
新規プロジェクト作成
基本的なコマンド:
rails new my_app
データベースを指定する場合 (例: PostgreSQL):
rails new my_app -d postgresql
特定のRailsバージョンを使用する場合:
rails _6.1.4_ new my_app
API専用アプリケーションとして作成:
rails new my_api --api
デフォルトのテストフレームワークをRSpecに変更:
rails new my_app -T # Minitestを除外
# GemfileにRSpec関連を追加後、インストール
Railsサーバー起動
デフォルト (ポート3000):
rails server
# または
rails s
ポートを指定:
rails s -p 4000
環境を指定 (例: production):
rails s -e production
Railsコンソール起動
通常のコンソール:
rails console
# または
rails c
サンドボックスモード (変更がロールバックされる):
rails c --sandbox
環境を指定 (例: test):
rails c -e test
Gemの管理 (Bundler)
GemfileにGemを追加後、インストール:
bundle install
Gemをアップデート:
bundle update
# 特定のGemをアップデート
bundle update gem_name
インストールされているGemを表示:
bundle list
古いバージョンのGemを削除:
bundle clean
ルーティング (config/routes.rb)
リクエストをコントローラーのアクションに結びつける
基本ルーティング
# config/routes.rb
Rails.application.routes.draw do # HTTPメソッド 'URLパターン', to: 'コントローラー#アクション' get '/posts', to: 'posts#index' get '/posts/:id', to: 'posts#show' post '/posts', to: 'posts#create' put '/posts/:id', to: 'posts#update' # または patch patch '/posts/:id', to: 'posts#update' delete '/posts/:id', to: 'posts#destroy' # ルートURL (ホームページ) root 'welcome#index' # マッチする全てのHTTPメソッドに対応 match 'photos', to: 'photos#show', via: [:get, :post] match 'videos', to: 'videos#show', via: :all
end
リソースルーティング
RESTfulなルーティングをまとめて定義
# config/routes.rb
Rails.application.routes.draw do # 複数のリソース (posts) に対する標準的なRESTfulルートを生成 # index, show, new, create, edit, update, destroy resources :posts # 単数のリソース (profile) に対するルートを生成 (IDなし) # show, new, create, edit, update, destroy (indexなし) resource :profile # 特定のアクションのみ、または除外 resources :photos, only: [:index, :show] resources :videos, except: [:destroy] # コレクションルート (IDなしの追加アクション) resources :articles do get 'search', on: :collection # /articles/search end # メンバーールート (IDありの追加アクション) resources :images do get 'preview', on: :member # /images/:id/preview post 'publish', on: :member # POST /images/:id/publish end
end
ネストしたルーティング
親子関係のあるリソース
# config/routes.rb
Rails.application.routes.draw do resources :authors do resources :books # /authors/:author_id/books/:id end # shallow nesting: 親のIDが必要なのはindex, new, createのみ resources :magazines, shallow: true do resources :ads # index, new, create は /magazines/:magazine_id/ads, その他は /ads/:id end
end
名前付きルート
ルートに名前をつけてヘルパーメソッドを生成
# config/routes.rb
Rails.application.routes.draw do get '/help', to: 'static_pages#help', as: 'help_page'
end
# ビューやコントローラーで使用
# link_to 'Help', help_page_path # => <a href="/help">Help</a>
# redirect_to help_page_url
リソースルーティングでは自動的に名前付きルートが生成される:
posts_path
-> /postsnew_post_path
-> /posts/newedit_post_path(post)
-> /posts/:id/editpost_path(post)
-> /posts/:id*_url
バージョンは完全なURLを生成 (リダイレクトやメール内で使用)
スコープと名前空間
ルートのグループ化
# config/routes.rb
Rails.application.routes.draw do # URLパスのみをプレフィックス (/admin/users) scope '/admin' do resources :users # コントローラーは UsersController end # URLパス、コントローラー、名前付きルートをプレフィックス # URL: /dashboard/stats, コントローラー: Dashboard::StatsController, ヘルパー: dashboard_stats_path namespace :dashboard do resources :stats end # URLパス、名前付きルートのみをプレフィックス (コントローラーはプレフィックスなし) # URL: /api/v1/items, コントローラー: ItemsController, ヘルパー: api_v1_items_path scope '/api/v1', as: 'api_v1' do resources :items end # モジュール指定 (コントローラーのディレクトリを指定) # URL: /management/tasks, コントローラー: Management::TasksController, ヘルパー: tasks_path scope module: 'management' do resources :tasks end
end
制約 (constraints)
ルートがマッチする条件を指定
# config/routes.rb
Rails.application.routes.draw do # パラメータのフォーマット制約 get 'photos/:id', to: 'photos#show', constraints: { id: /[A-Z]\d{5}/ } # サブドメイン制約 constraints subdomain: 'api' do resources :users end # リクエストオブジェクトに基づく制約 (Lambda) get 'admin/reports', to: 'reports#index', constraints: ->(request) { request.remote_ip == '192.168.1.1' } # カスタム制約クラス class AdminConstraint def matches?(request) # request.session[:user_id] などで認証チェック User.find_by(id: request.session[:user_id])&.admin? end end constraints AdminConstraint.new do resources :admin_panel end
end
コントローラー (app/controllers)
リクエストを処理し、レスポンスを生成する
コントローラー生成
rails generate controller ControllerName action1 action2 --skip-routes
# 例:
rails generate controller Posts index show new create --skip-routes
アクション定義
# app/controllers/posts_controller.rb
class PostsController < ApplicationController def index @posts = Post.all # デフォルトで app/views/posts/index.html.erb がレンダリングされる end def show @post = Post.find(params[:id]) end def new @post = Post.new end def create @post = Post.new(post_params) if @post.save redirect_to @post, notice: 'Post was successfully created.' # 成功時のリダイレクト else render :new, status: :unprocessable_entity # バリデーションエラーなどでフォーム再表示 end end # edit, update, destroy アクションも同様に定義 private # Strong Parameters の設定 def post_params params.require(:post).permit(:title, :body, :published_at) end
end
リクエストパラメータの取得 (params)
params
はリクエストパラメータ (URLクエリ、フォームデータなど) を含むハッシュライクなオブジェクト。
- URLパラメータ:
/posts/:id
の場合、params[:id]
で取得。 - クエリパラメータ:
/search?query=rails
の場合、params[:query]
で取得。 - フォームデータ: フォームから送信されたデータ。Strong Parameters を通して安全に利用する。
Strong Parameters
マスアサインメント脆弱性を防ぐため、許可されたパラメータのみを受け取る仕組み。
# app/controllers/articles_controller.rb
private
def article_params # :article というキーが必要で、その中の :title と :content のみを許可 params.require(:article).permit(:title, :content) # ネストした属性 (例: 著者情報) を許可 # params.require(:book).permit(:title, author_attributes: [:name, :email]) # 配列の値を許可 # params.require(:product).permit(:name, tag_ids: [])
end
レスポンス
メソッド | 説明 | 例 |
---|---|---|
render | 指定されたビュー、テンプレート、またはテキストをレンダリングする。 | render :new render 'posts/show' render plain: 'OK' render json: @users render xml: @products render status: :not_found render :edit, status: :unprocessable_entity |
redirect_to | ブラウザに別のURLへのリダイレクトを指示する (HTTP 302)。 | redirect_to @post redirect_to posts_path redirect_to '/login' redirect_to root_url, alert: 'Access denied.' redirect_back(fallback_location: root_path) |
head | レスポンスボディなしでHTTPヘッダーのみを送信する。 | head :ok head :no_content head :forbidden head :not_found |
フィルター (Callbacks)
アクションの前後や周囲でコードを実行する。
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base before_action :require_login after_action :log_action, only: [:create, :update] around_action :benchmark_action private def require_login # ログインしていなければログインページへリダイレクト redirect_to login_path unless logged_in? end def log_action # アクションのログを記録 end def benchmark_action start = Time.current yield # ここでアクションが実行される duration = Time.current - start Rails.logger.info("Action executed in #{duration} seconds") end def logged_in? # 実際のログイン状態チェック処理 session[:user_id].present? end
end
# 特定のコントローラーでフィルターをスキップ
class PublicController < ApplicationController skip_before_action :require_login, only: [:index]
end
セッションとCookie
ブラウザとサーバー間で状態を保持する。
# セッション (サーバーサイドに保存、ブラウザにはIDのみ)
session[:user_id] = @user.id
user_id = session[:user_id]
session.delete(:user_id)
reset_session # セッション全体をリセット
# Cookie (ブラウザに保存)
cookies[:username] = "taro" # セッションCookie (ブラウザ閉じると消える)
cookies.permanent[:remember_token] = token # 永続Cookie (期限付き)
cookies.signed[:user_id] = @user.id # 署名付きCookie (改ざん防止)
cookies.encrypted[:secret_data] = "value" # 暗号化Cookie (内容秘匿)
username = cookies[:username]
cookies.delete(:username)
フラッシュメッセージ
次のリクエストでのみ表示される一時的なメッセージ。
# コントローラー
redirect_to root_path, notice: "Successfully logged in!"
redirect_to login_path, alert: "Invalid email or password."
redirect_to profile_path, flash: { success: "Profile updated!", warning: "Check your settings." }
# ビュー (例: app/views/layouts/application.html.erb)
<% flash.each do |key, value| %> <div class="notification is-<%= key == 'notice' ? 'info' : (key == 'alert' ? 'danger' : 'warning') %>"> <button class="delete"></button> <%= value %> </div>
<% end %>
モデル (app/models)
データベースとのやり取り、ビジネスロジックを担当 (Active Record)
モデル生成
rails generate model ModelName attribute_name:type attribute_name:type:index
# 例:
rails generate model Post title:string body:text published_at:datetime user:references
# user:references は user_id カラム (integer) と外部キー制約、インデックスを生成
生成されるファイル:
app/models/post.rb
(モデルクラス)db/migrate/YYYYMMDDHHMMSS_create_posts.rb
(マイグレーションファイル)test/models/post_test.rb
(ユニットテストファイル)test/fixtures/posts.yml
(フィクスチャファイル)
マイグレーション
データベーススキーマをバージョン管理する仕組み。
マイグレーション生成
# 新規テーブル作成 (モデル生成時に自動生成される)
rails generate migration CreateProducts name:string description:text price:decimal
# 既存テーブルへのカラム追加
rails generate migration AddPartNumberToProducts part_number:string:index
# カラム削除
rails generate migration RemovePartNumberFromProducts part_number:string
# テーブル名やカラム名を変更
rails generate migration RenameOldTableToNewTable
# 外部キー追加
rails generate migration AddUserRefToPosts user:references
# 単独のインデックス追加
rails generate migration AddIndexToUsersEmail email:string:uniq
# Joinテーブル作成 (has_and_belongs_to_many用)
rails generate migration CreateJoinTableCustomersProducts customer product
マイグレーション実行/ロールバック
# 保留中のマイグレーションを実行
rails db:migrate
# 1ステップ戻す
rails db:rollback
# 特定のバージョンまで戻す (バージョン番号は db:migrate:status で確認)
rails db:migrate:down VERSION=YYYYMMDDHHMMSS
# 特定のバージョンまで進める
rails db:migrate:up VERSION=YYYYMMDDHHMMSS
# データベースを再作成 (db/schema.rb または db/structure.sql から)
rails db:schema:load # 開発環境向け
rails db:setup # db:create, db:schema:load, db:seed を実行
rails db:reset # db:drop, db:setup を実行
# マイグレーションの状態確認
rails db:migrate:status
主なカラム型
型 | 説明 |
---|---|
:string | 短い文字列 (VARCHAR) |
:text | 長い文字列 (TEXT) |
:integer | 整数 |
:bigint | 大きな整数 |
:float | 浮動小数点数 |
:decimal | 精度が必要な数値 (例: 金額)。precision (全桁数) と scale (小数点以下桁数) を指定可能。例: price:decimal{10,2} |
:numeric | :decimal のエイリアス |
:datetime | 日時 (タイムゾーン考慮) |
:timestamp | :datetime のエイリアス |
:time | 時刻 (日付なし) |
:date | 日付 (時刻なし) |
:binary | バイナリデータ (BLOB) |
:boolean | 真偽値 |
:primary_key | 主キー (通常は自動生成される id ) |
:references / :belongs_to | 外部キーカラム (_id ) を追加し、インデックスも作成。polymorphic: true オプションあり。 |
:json / :jsonb | JSON形式のデータを格納 (PostgreSQLなど対応DBのみ) |
その他のマイグレーション操作
# db/migrate/YYYYMMDDHHMMSS_migration_name.rb
class MigrationName < ActiveRecord::Migration[7.0] # Railsバージョンに合わせて変更 def change # テーブル作成 create_table :items do |t| t.string :name, null: false # NOT NULL制約 t.text :description t.integer :quantity, default: 0 # デフォルト値 t.decimal :price, precision: 8, scale: 2 t.belongs_to :category, foreign_key: true # category_id カラムと外部キー制約 t.timestamps # created_at と updated_at カラムを追加 end # カラム追加 add_column :users, :phone_number, :string, limit: 15 # 文字数制限 # カラム変更 (型、オプション) change_column :products, :description, :text, limit: 500 change_column_null :products, :name, false # NOT NULL制約を追加 change_column_default :products, :status, from: nil, to: 'draft' # デフォルト値変更 # カラム名変更 rename_column :users, :email_address, :email # カラム削除 remove_column :orders, :billing_address # インデックス追加 add_index :users, :email, unique: true # ユニークインデックス add_index :orders, [:customer_id, :created_at] # 複合インデックス # インデックス削除 remove_index :users, :email # 外部キー制約追加 (referencesを使わない場合) add_foreign_key :posts, :users, column: :author_id, primary_key: :id, on_delete: :cascade # 削除時カスケード # 外部キー制約削除 remove_foreign_key :posts, :users # テーブル名変更 rename_table :old_items, :new_items # テーブル削除 drop_table :obsolete_table # SQL直接実行 execute("ALTER TABLE products ADD CONSTRAINT price_check CHECK (price > 0)") # reversible マイグレーション (up/downで異なる処理) reversible do |dir| dir.up do # up (migrate) 時の処理 change_column :users, :preferences, :jsonb, using: 'preferences::jsonb' end dir.down do # down (rollback) 時の処理 change_column :users, :preferences, :text end end # change メソッド内でロールバック不可な操作は up/down を使う # def up # # ... # end # def down # # ... # end end
end
CRUD操作 (Active Record)
Create (作成)
# インスタンス作成 (メモリ上、DB未保存)
post = Post.new(title: "Hello Rails", body: "...")
# DBに保存 (バリデーション実行)
post.save # => true or false
# バリデーションエラー時に例外発生
post.save!
# インスタンス作成と保存を同時に行う (バリデーション実行)
post = Post.create(title: "Hello Rails", body: "...") # => 保存されたインスタンス or newされたインスタンス
# バリデーションエラー時に例外発生
post = Post.create!(title: "Hello Rails", body: "...")
# 複数レコード作成 (バリデーション、コールバック実行)
Post.create([ { title: "Post 1", body: "..." }, { title: "Post 2", body: "..." }
])
# find_or_create_by: 条件に合うレコードがあればそれを返し、なければ作成して返す
user = User.find_or_create_by(email: 'test@example.com') do |u| u.name = 'Test User'
end
# find_or_initialize_by: find_or_create_by と似ているが、新規作成時は save しない
user = User.find_or_initialize_by(email: 'new@example.com')
user.name = 'New User'
user.save
Read (読み取り/検索)
メソッド | 説明 | 例 |
---|---|---|
find(id) | 主キーで検索。見つからないと ActiveRecord::RecordNotFound 例外。 | Post.find(1) Post.find([1, 2, 5]) |
find_by(attributes) | 属性で検索し、最初に見つかった1件を返す。見つからないと nil 。 | User.find_by(email: 'a@b.com') Post.find_by(title: 'Title', published: true) |
find_by!(attributes) | find_by と同様だが、見つからないと ActiveRecord::RecordNotFound 例外。 | User.find_by!(email: 'a@b.com') |
where(conditions) | 条件に合うレコードを ActiveRecord::Relation オブジェクトとして返す (遅延評価)。 | Post.where(published: true) Post.where("likes > ?", 10) Post.where(created_at: (Time.current - 1.day)..Time.current) Post.where(author_id: [1, 3, 5]) Post.where.not(status: 'archived') |
first | 最初のレコードを返す (主キー順)。where などとチェーン可能。 | Post.first Post.where(published: true).first |
last | 最後のレコードを返す (主キー順)。where などとチェーン可能。 | Post.last Post.where(published: true).last |
all | 全てのレコードを ActiveRecord::Relation として返す。 | Post.all |
order(criteria) | 結果をソートする。 | Post.order(created_at: :desc) Post.order(:title) Post.order("likes DESC, title ASC") |
limit(number) | 取得するレコード数を制限する。 | Post.limit(10) |
offset(number) | 指定した数だけレコードをスキップする (ページネーションで使用)。 | Post.offset(20).limit(10) |
select(columns) | 取得するカラムを指定する。 | User.select(:id, :name) User.select('email AS user_email') |
distinct | 重複するレコードを除外する (select と併用)。 | User.select(:city).distinct |
joins(association) | 関連テーブルを INNER JOIN する。 | Post.joins(:user).where(users: { name: 'Alice' }) Order.joins(:customer, :line_items) Post.joins("LEFT JOIN comments ON comments.post_id = posts.id") |
left_outer_joins(association) | 関連テーブルを LEFT OUTER JOIN する。 | User.left_outer_joins(:posts).where(posts: { id: nil }) # 投稿がないユーザー |
includes(association) | 関連データを事前に読み込む (N+1問題対策)。Eager Loading。 | posts = Post.includes(:comments).limit(10) # posts と comments を別々に取得 |
preload(association) | includes と似ているが、常に別々のクエリで読み込む。 | posts = Post.preload(:author).limit(10) |
eager_load(association) | includes と似ているが、常にLEFT OUTER JOINで読み込む。 | posts = Post.eager_load(:tags).where(tags: { name: 'ruby' }) |
group(columns) | 指定したカラムでグルーピングする (集計関数と併用)。 | Order.group(:status).count Post.group(:author_id).maximum(:likes) |
having(conditions) | group の結果に対して条件を指定する。 | Order.group(:customer_id).having("SUM(total) > ?", 1000) |
count | レコード数をカウントする。 | Post.count Post.where(published: true).count |
sum(column) | 指定カラムの合計値を計算する。 | LineItem.sum(:price) |
average(column) | 指定カラムの平均値を計算する。 | Product.average(:rating) |
minimum(column) | 指定カラムの最小値を計算する。 | Product.minimum(:price) |
maximum(column) | 指定カラムの最大値を計算する。 | Post.maximum(:likes) |
pluck(columns) | 指定したカラムの値の配列を取得する (軽量)。 | User.pluck(:id, :email) # => [[1, 'a@b.com'], [2, 'c@d.com']] |
ids | 条件に合うレコードの主キーの配列を取得する。 | Post.where(published: true).ids # => [1, 3, 5] |
exists?(conditions) | 条件に合うレコードが存在するかどうかを返す (true/false)。 | User.exists?(email: 'a@b.com') Post.where(likes: 0).exists? |
none | 空の ActiveRecord::Relation を返す。 | Post.none |
find_each(options) | 大量のレコードをバッチ処理する (メモリ効率が良い)。 | User.find_each(batch_size: 1000) { |user| user.update(active: true) } |
find_in_batches(options) | 大量のレコードをバッチ(配列)で処理する。 | Product.find_in_batches(batch_size: 500) { |products| ProductMailer.update_notification(products).deliver_later } |
Update (更新)
# レコードを取得してから更新
post = Post.find(1)
post.title = "Updated Title"
post.published_at = Time.current
post.save # => true or false (バリデーション実行)
post.save! # 例外発生版
# 属性ハッシュで更新 (バリデーション実行)
post.update(title: "Another Update", body: "New content.") # => true or false
post.update!(title: "Another Update") # 例外発生版
# 特定の属性のみ更新 (バリデーション、コールバックをスキップ)
# タイムスタンプは更新されない
post.update_column(:title, "Skipped Validation Title")
post.update_columns(likes: 100, status: 'popular') # 複数カラム
# 条件に合うレコードをまとめて更新 (バリデーション、コールバックをスキップ)
# タイムスタンプは更新されない
Post.where(published: false).update_all(published: true, published_at: Time.current)
# カウンターキャッシュなどの値を増減 (コールバック、バリデーションをスキップ)
user.increment!(:login_count) # デフォルトで1増やす
post.increment!(:view_count, 5) # 5増やす
post.decrement!(:stock_count) # 1減らす
Delete (削除)
# レコードを取得してから削除 (コールバック実行)
post = Post.find(1)
post.destroy # => 削除されたインスタンス or false (コールバックで中断された場合)
# 主キーで直接削除 (コールバック実行)
Post.destroy(1)
Post.destroy([1, 2, 3])
# 条件に合うレコードを削除 (コールバック実行)
Post.where(author_id: 5).destroy_all
# 条件に合うレコードを直接削除 (コールバックをスキップ!)
Post.where("created_at < ?", 1.year.ago).delete_all
# 全てのレコードを削除 (コールバックをスキップ!)
# Post.delete_all # 注意: 非常に危険
バリデーション
モデルがデータベースに保存される前に、データの整合性を検証する。
# app/models/user.rb
class User < ApplicationRecord validates :name, presence: true # 存在すること validates :email, presence: true, uniqueness: { case_sensitive: false }, format: { with: URI::MailTo::EMAIL_REGEXP } # 一意性(大文字小文字区別なし)、フォーマット validates :age, numericality: { only_integer: true, greater_than_or_equal_to: 0, allow_nil: true } # 整数、0以上、nil許可 validates :password, length: { minimum: 8 }, if: :password_required? # 条件付きバリデーション (パスワードが必要な場合のみ) validates :terms_of_service, acceptance: true # チェックボックスなどがオンになっていること (DBカラム不要) validates :role, inclusion: { in: %w(admin editor viewer), message: "%{value} is not a valid role" } # 特定の値に含まれること validates :subdomain, exclusion: { in: %w(www admin api), message: "%{value} is reserved." } # 特定の値に含まれないこと validates :legacy_code, presence: true, on: :update # 更新時のみ検証 validate :custom_validation_method # カスタムバリデーションメソッド呼び出し # アソシエーションのバリデーション (関連オブジェクトも検証) has_many :posts validates_associated :posts private def password_required? new_record? || password.present? end def custom_validation_method if name.present? && name.downcase == 'admin' errors.add(:name, "cannot be 'admin'") # エラーを追加 end end
end
# バリデーションの実行とエラー確認
user = User.new(name: '')
user.valid? # => false
user.invalid? # => true
user.errors # => #<ActiveModel::Errors ...>
user.errors.full_messages # => ["Name can't be blank", "Email can't be blank", ...]
user.errors[:email] # => ["can't be blank", "is invalid"]
アソシエーション
モデル間の関連付けを定義する。
種類 | 説明 | 例 (関連するモデルも示す) |
---|---|---|
belongs_to | 1対1 or 多対1 の関連 (子モデル側)。外部キー (モデル名_id ) を持つ。 |
|
has_one | 1対1 の関連 (親モデル側)。相手のモデルが外部キーを持つ。 |
|
has_many | 1対多 の関連 (親モデル側)。相手のモデルが外部キーを持つ。 |
|
has_many :through | 多対多 の関連 (中間テーブル経由)。 |
|
has_and_belongs_to_many (HABTM) | 多対多 の関連 (中間モデルなし、結合テーブルのみ)。シンプルだが中間テーブルに属性を持てない。 |
|
ポリモーフィック関連 | 1つのモデルが複数の異なる親モデルに属することができる関連。belongs_to 側で polymorphic: true を指定し、モデル名_id と モデル名_type カラムが必要。 |
|
アソシエーションのオプション
class Post < ApplicationRecord # クラス名が規約と異なる場合 belongs_to :author, class_name: 'User', foreign_key: 'author_id' has_many :comments, dependent: :destroy # Post削除時にCommentも削除 (:nullify, :restrict_with_error, :restrict_with_exception もあり) has_many :recent_comments, -> { where('created_at > ?', 1.week.ago) }, class_name: 'Comment' # スコープ付き関連 has_many :tags, inverse_of: :post # 双方向関連の指定 (メモリ上のオブジェクト一貫性) # counter_cache: 関連レコード数を親モデルにキャッシュ # Comment モデルに counter_cache: true を追加し、 # Post モデルに comments_count カラム (integer, default: 0) が必要 belongs_to :category, counter_cache: true
end
class Comment < ApplicationRecord belongs_to :post, counter_cache: true # touch: true - Comment保存時にPostのupdated_atも更新 belongs_to :user, touch: true
end
class User < ApplicationRecord # optional: true - belongs_to の関連先が存在しなくても良い (デフォルトは false) has_one :profile, optional: true
end
スコープ (scope)
よく使うクエリ条件を名前付きで定義する。
# app/models/post.rb
class Post < ApplicationRecord # クラスメソッドとして定義 (引数なし) scope :published, -> { where(published: true) } scope :draft, -> { where(published: false) } scope :recent, -> { order(created_at: :desc).limit(5) } # 引数ありスコープ scope :created_since, ->(time) { where("created_at > ?", time) } scope :with_long_title, ->(min_length = 20) { where("LENGTH(title) > ?", min_length) } # デフォルトスコープ (常に適用される - 注意して使う) # default_scope { where(deleted_at: nil) } # スコープのチェーン # Post.published.recent # Post.created_since(1.day.ago).published # クラスメソッドでも同様の定義が可能 # def self.published # where(published: true) # end
end
コールバック (Callbacks)
モデルのライフサイクル (作成、更新、削除など) の特定のタイミングでメソッドを実行する。
タイミング | 主なコールバック |
---|---|
オブジェクト作成時 | before_validation , after_validation , before_save , around_save , before_create , around_create , after_create , after_save , after_commit /after_rollback |
オブジェクト更新時 | before_validation , after_validation , before_save , around_save , before_update , around_update , after_update , after_save , after_commit /after_rollback |
オブジェクト削除時 | before_destroy , around_destroy , after_destroy , after_commit /after_rollback |
初期化時 | after_initialize (DBから読み込み時、new時) |
検索時 | after_find (DBから読み込み時) |
touch時 | after_touch |
# app/models/user.rb
class User < ApplicationRecord before_validation :normalize_email, on: :create # create時のみ before_save :encrypt_password, if: :password_changed? after_create :send_welcome_email after_commit :clear_cache, on: [:create, :update] # トランザクション成功後 private def normalize_email self.email = email.downcase.strip if email.present? end def encrypt_password # パスワード暗号化処理 end def send_welcome_email UserMailer.welcome(self).deliver_later end def clear_cache # キャッシュクリア処理 end def password_changed? # パスワードが変更されたかどうかの判定 password_digest_changed? # ActiveModel::Dirty を利用 end
end
注意: コールバック内でビジネスロジックを複雑にしすぎると、モデルが肥大化し、テストやデバッグが難しくなることがあります。Service Objectなどの利用を検討しましょう。
enum
特定のカラムが取りうる値をシンボルで定義し、整数としてDBに保存する。
# app/models/order.rb
class Order < ApplicationRecord # status カラムに 0: pending, 1: processing, 2: shipped, 3: cancelled が対応 enum status: { pending: 0, processing: 1, shipped: 2, cancelled: 3 } # _prefix または _suffix オプションでメソッド名の衝突を回避 enum payment_status: { unpaid: 0, paid: 1, refunded: 2 }, _prefix: :payment enum delivery_method: { standard: 0, express: 1 }, _suffix: true
end
# 使用例
order = Order.new(status: :pending, payment_status: :unpaid, delivery_method: :standard)
order.pending? # => true
order.processing! # status を processing (1) に更新して保存
order.status # => "processing"
# スコープも自動生成される
Order.pending # status が pending のレコードを取得
Order.payment_paid # payment_status が paid のレコードを取得
Order.express_delivery_method # delivery_method が express のレコードを取得
# DBの実際の値
order.read_attribute_before_type_cast(:status) # => 1
ビュー (app/views)
ユーザーインターフェースの表示を担当
ビューファイル命名規則
app/views/コントローラー名/アクション名.フォーマット.テンプレートエンジン
- 例:
app/views/posts/index.html.erb
(Postsコントローラーのindexアクション、HTML形式、ERBテンプレート) - 例:
app/views/users/show.json.jbuilder
(Usersコントローラーのshowアクション、JSON形式、Jbuilderテンプレート) - 例:
app/views/layouts/application.html.erb
(共通レイアウト) - パーシャル: ファイル名の先頭にアンダースコア (
_
) をつける (例:app/views/shared/_header.html.erb
)
ERB (Embedded Ruby)
HTML内にRubyコードを埋め込むためのテンプレートエンジン。
<%= rubyコード %>
: Rubyコードを実行し、結果を出力 (HTMLエスケープされる)。<%- rubyコード %>
: Rubyコードを実行し、結果を出力 (改行を抑制、古い記法)。<%== rubyコード %>
またはraw(rubyコード)
: Rubyコードを実行し、結果をHTMLエスケープせずに出力 (XSS脆弱性に注意)。<% rubyコード %>
: Rubyコードを実行するが、結果は出力しない (条件分岐やループなど)。<%# rubyコメント %>
: ERB内のコメント (出力されない)。
<h1><%= @post.title %></h1>
<% if user_signed_in? %> <p>Welcome, <%= current_user.name %>!</p>
<% else %> <p>Please sign in.</p>
<% end %>
<ul> <% @items.each do |item| %> <li><%= item.name %></li> <% end %>
</ul>
<%# これはコメントです %>
<!-- HTMLコメント -->
<div> <%== @post.formatted_body %> <%# formatted_bodyが安全なHTMLを返す前提 %> <%= raw @user_generated_content %> <%# 非常に危険! sanitize などで処理が必要 %>
</div>
ビューヘルパー
ビューでの共通処理を助けるメソッド群 (ActionView::Helpers)。
リンクヘルパー (link_to
)
# 基本
link_to "Profile", profile_path # => <a href="/profile">Profile</a>
link_to "Post", @post # => <a href="/posts/1">Post</a> (オブジェクトを渡すと対応するパスヘルパーが呼ばれる)
link_to "Edit", edit_post_path(@post) # => <a href="/posts/1/edit">Edit</a>
# ブロックを使う
link_to user_path(@user) do content_tag(:strong, @user.name) + " Profile"
end
# オプション
link_to "External", "https://example.com", target: "_blank", rel: "noopener"
link_to "Delete", post_path(@post), method: :delete, data: { confirm: "Are you sure?" } # data-* 属性
link_to "Home", root_path, class: "button is-primary" # CSSクラス
# 現在のページかどうかでアクティブクラスを付与 (current_page? ヘルパー)
link_to "About", about_path, class: ("active" if current_page?(about_path))
# mail_to ヘルパー
mail_to "support@example.com", "Contact Support"
フォームヘルパー (form_with
)
HTMLフォームを生成するためのヘルパー。form_tag
と form_for
は非推奨。
# モデルオブジェクトに紐づくフォーム (新規作成 or 更新)
# @post が new record なら POST /posts へ、永続化済みなら PATCH /posts/:id へ送信
form_with(model: @post, local: true) do |form| # local: true でAjaxではない通常のフォーム送信 # エラーメッセージ表示 if form.object.errors.any? # エラー表示処理 ... end div class="field" = form.label :title, class: "label" div class="control" = form.text_field :title, class: "input", placeholder: "Enter title" - if form.object.errors[:title].any? p class="help is-danger" = form.object.errors[:title].to_sentence div class="field" = form.label :body, class: "label" div class="control" = form.text_area :body, class: "textarea" div class="field" = form.label :category_id, class: "label" div class="control" div class="select" = form.collection_select :category_id, Category.all, :id, :name, prompt: "Select Category" div class="field" = form.label :published_at, class: "label" div class="control" = form.datetime_select :published_at div class="field" div class="control" = form.check_box :is_public = form.label :is_public, "Make public" div class="field is-grouped" div class="control" = form.submit "Save Post", class: "button is-link" div class="control" = link_to "Cancel", posts_path, class: "button is-light"
# URLを指定するフォーム (検索フォームなど)
form_with(url: search_path, method: :get, local: true) do |form| div class="field has-addons" div class="control" = form.text_field :query, class: "input", placeholder: "Search..." div class="control" = form.submit "Search", class: "button is-info"
# 様々なフォーム要素
# form.password_field :password
# form.number_field :quantity, in: 1..100, step: 1
# form.email_field :email
# form.url_field :website
# form.telephone_field :phone
# form.date_field :start_date
# form.time_field :start_time
# form.color_field :background_color
# form.file_field :avatar
# form.hidden_field :secret_token, value: '...'
# form.radio_button :status, 'active'
# form.label :status_active, 'Active'
# form.collection_radio_buttons :role_id, Role.all, :id, :name
# form.collection_check_boxes :tag_ids, Tag.all, :id, :name
# form.select :country, ["Japan", "USA", "Canada"]
fields_for
: ネストしたモデルのフォームを作成
# @user に紐づく @profile のフォームを生成
form_with model: @user do |user_form| # Userのフィールド... user_form.fields_for :profile do |profile_form| = profile_form.text_field :bio = profile_form.text_field :location end = user_form.submit
end
日付/時刻ヘルパー
# 現在時刻
Time.current # => Sat, 03 Apr 2025 10:08:00.000000000 JST +09:00 (例)
# フォーマット済み日付/時刻 (I18n利用)
l(Time.current, format: :long) # => "2025年04月03日 10時08分" (ロケール設定による)
l(Date.today, format: :short) # => "04/03"
# 相対時間
time_ago_in_words(3.minutes.ago) # => "3分"
time_ago_in_words(2.days.ago) # => "2日"
distance_of_time_in_words(Time.current, 1.hour.from_now) # => "約1時間"
# 日付/時刻選択タグ (form_with 内で使用)
# form.date_select :birthday
# form.time_select :meeting_time
# form.datetime_select :published_at
数値ヘルパー
# 通貨
number_to_currency(12345.67) # => "¥12,346" (デフォルトロケール)
number_to_currency(12345.67, unit: "$", precision: 2) # => "$12,345.67"
# パーセント
number_to_percentage(85.5, precision: 1) # => "85.5%"
# 区切り文字
number_with_delimiter(12345678) # => "12,345,678"
# 人が読みやすい形式 (ファイルサイズなど)
number_to_human_size(1234567) # => "1.2 MB"
# 電話番号フォーマット
number_to_phone(1235551234, area_code: true) # => "(123) 555-1234"
テキストヘルパー
# 文字列切り詰め
truncate("すごく長いテキストです。このテキストを切り詰めます。", length: 15, omission: '...') # => "すごく長いテキストです。..."
# 複数形/単数形
pluralize(1, 'person') # => "1 person"
pluralize(2, 'person') # => "2 people"
pluralize(@posts.count, 'post') # => "10 posts" (例)
# 改行を <br> に、連続改行を <p> に変換
simple_format("これは\nテキストです。\n\n新しい段落。")
# => <p>これは<br />テキストです。</p><p>新しい段落。</p>
# HTMLタグを除去
strip_tags("<b>太字</b> <a href='#'>link</a>") # => "太字 link"
# ハイライト
highlight('This is a test sentence', 'test') # => "This is a <mark>test</mark> sentence"
# cycle: ループ内で交互に値を出力 (例: テーブルの行の色分け)
# <% @items.each do |item| %>
# <tr class="<%= cycle('list-line-odd', 'list-line-even') %>"> ... </tr>
# <% end %>
# word_wrap: 長い単語を指定文字数で折り返す
word_wrap('thisisaverylongwordwithoutspaces', line_width: 10)
アセットタグヘルパー
# 画像 (app/assets/images または Webpacker/Propshaft 管理下)
image_tag("logo.png", alt: "Site Logo", size: "100x50") # => <img alt="Site Logo" width="100" height="50" src="/assets/logo-....png"> (ダイジェスト付き)
# JavaScript (app/assets/javascripts または app/javascript)
javascript_include_tag "application", "data-turbo-track": "reload" # => <script src="/assets/application-....js" data-turbo-track="reload"></script> (または Webpack/Propshaft の出力パス)
# CSS (app/assets/stylesheets または app/javascript)
stylesheet_link_tag "application", media: "all", "data-turbo-track": "reload" # => <link rel="stylesheet" media="all" href="/assets/application-....css" data-turbo-track="reload"> (または Webpack/Propshaft の出力パス)
# favicon
favicon_link_tag 'favicon.ico'
# その他アセット
audio_tag("sound.mp3")
video_tag("movie.mp4")
パーシャル (Partials)
ビューの一部を別のファイルに切り出し、再利用可能にする仕組み。ファイル名は _
で始める。
# app/views/shared/_header.html.erb など
# パーシャルの呼び出し
# app/views/layouts/application.html.erb
<body> <%= render 'shared/header' %> <div class="container"> <%= yield %> </div> <%= render 'shared/footer' %>
</body>
# ローカル変数を渡す
# app/views/posts/show.html.erb
<%= render partial: 'comments/comment', locals: { comment: @comment } %>
# または省略形
<%= render 'comments/comment', comment: @comment %>
# コレクションをレンダリング (各要素に対してパーシャルを繰り返し描画)
# app/views/posts/index.html.erb
<%= render partial: 'posts/post', collection: @posts %>
# または省略形 (変数名がパーシャル名と同じ場合)
<%= render @posts %>
# => app/views/posts/_post.html.erb が @posts の各 post に対して描画される
# パーシャル内ではローカル変数 `post` が利用可能
# スペーサーテンプレート
# <%= render partial: @posts, spacer_template: 'posts/post_divider' %>
# パーシャル内でのカウンター変数
# _post.html.erb 内で `post_counter` が使える (0から始まる)
レイアウト (Layouts)
アプリケーション全体や特定の部分で共通のHTML構造を提供する。app/views/layouts/
に配置。
yield
キーワードで、各アクションのビューの内容が挿入される場所を指定する。
<!DOCTYPE html>
<html>
<head> <title><%= content_for?(:title) ? yield(:title) : "Default Title" %></title> <meta name="viewport" content="width=device-width,initial-scale=1"> <%= csrf_meta_tags %> <%= csp_meta_tag %> <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> <%= javascript_include_tag "application", "data-turbo-track": "reload", defer: true %>
</head>
<body> <%= render 'shared/navigation' %> <%# ヘッダーなど共通部分 %> <div class="main-content"> <%= render 'shared/flash_messages' %> <%= yield %> <%# ここに各アクションのビューが入る %> </div> <% if content_for?(:sidebar) %> <aside class="sidebar"> <%= yield(:sidebar) %> <%# 名前付き yield %> </aside> <% end %> <%= render 'shared/footer' %>
</body>
</html>
コントローラーでレイアウトを指定:
class AdminController < ApplicationController layout 'admin' # app/views/layouts/admin.html.erb を使用
end
class SpecialController < ApplicationController layout false # レイアウトを使用しない
end
class PostsController < ApplicationController def show # アクションごとにレイアウトを指定 render layout: 'minimal' end
end
content_for
ビューの一部をキャプチャし、レイアウトの異なる場所 (通常は yield
で指定した場所) で出力する。
# ビュー (例: app/views/posts/show.html.erb)
<% content_for :title, @post.title %> <%# レイアウトの <title> タグ内に出力 %>
<h1><%= @post.title %></h1>
<p><%= @post.body %></p>
<% content_for :sidebar do %> <h3>Related Posts</h3> <ul> <%# ... 関連投稿リスト ... %> </ul>
<% end %>
# レイアウト (app/views/layouts/application.html.erb)
<head> <title><%= yield(:title) %></title> <%# ... %>
</head>
<body> <%= yield %> <% if content_for?(:sidebar) %> <aside><%= yield(:sidebar) %></aside> <% end %>
</body>
アセット管理
JavaScript, CSS, 画像などの静的ファイル管理
Rails 6以降、JavaScriptの管理は主に Webpacker (またはその後継・代替の Shakapacker, jsbundling-rails など)、CSSは cssbundling-rails、Rails 7からは Propshaft や Import maps (デフォルト) など、複数の選択肢があります。ここでは基本的な概念と、従来のアセットパイプライン (Sprockets) の考え方も含めます。
アセットパイプライン (Sprockets)
主にRails 6以前のデフォルト。CSSや古いJavaScriptライブラリの管理には依然として使われることもあります。
- 配置場所:
app/assets/
: アプリケーション固有のアセット (images, javascripts, stylesheets)lib/assets/
: ライブラリ固有のアセットvendor/assets/
: サードパーティのアセット (Gemで管理されないもの)
- マニフェストファイル:
app/assets/config/manifest.js
: プリコンパイル対象のファイルやディレクトリを指定。app/assets/stylesheets/application.css
(または.scss
,.sass
): CSSのマニフェスト。*= require_tree .
や*= require_self
などで読み込むファイルを指定。Sass/SCSSの場合は@import
を使用。app/assets/javascripts/application.js
(非推奨、通常はWebpacker/jsbundling管理下): JavaScriptのマニフェスト (従来)。//= require_tree .
などで読み込むファイルを指定。
- プリコンパイル: 本番環境ではアセットを結合・圧縮し、フィンガープリント (ダイジェスト) を付与して
public/assets
ディレクトリに出力します。# 本番環境へのデプロイ時に自動実行されることが多い rails assets:precompile
# プリコンパイルされたアセットを削除 rails assets:clobber
# アセットを事前にクリーンアップしてからプリコンパイル rails assets:precompile RAILS_ENV=production ASSETS_CLEAN=1
- ヘルパーメソッド: ビューで
image_tag
,stylesheet_link_tag
,javascript_include_tag
などを使用すると、開発環境では個別のファイル、本番環境ではプリコンパイルされたダイジェスト付きのファイルへのパスが生成されます。
Webpacker / Shakapacker / jsbundling-rails
モダンなJavaScript開発 (ES6+, npm/yarn パッケージ利用, バンドル) をRailsに統合します。
- 配置場所:
app/javascript/
(主にここにJSコードやCSSを配置) - エントリーポイント:
app/javascript/packs/application.js
(Webpacker/Shakapacker) またはapp/assets/builds/application.js
(jsbundling-rails + esbuild/rollup/webpack の出力先)。ここで依存関係を import します。 - パッケージ管理:
package.json
ファイルで依存ライブラリを管理し、yarn install
またはnpm install
でインストールします。 - ビルド: 開発サーバー (
./bin/webpack-dev-server
やyarn build --watch
) を起動するか、デプロイ時にrails assets:precompile
が内部的にビルドコマンド (yarn build
など) を実行します。 - ヘルパーメソッド:
javascript_pack_tag 'application'
(Webpacker/Shakapacker)stylesheet_pack_tag 'application'
(Webpacker/Shakapacker でCSSも管理する場合)javascript_include_tag 'application'
(jsbundling-rails + Sprockets連携の場合)stylesheet_link_tag "application"
(cssbundling-rails + Sprockets連携の場合)
// 例: app/javascript/packs/application.js (Webpacker/Shakapacker)
import Rails from "@rails/ujs"
import Turbolinks from "turbolinks"
import * as ActiveStorage from "@rails/activestorage"
import "channels"
import "../stylesheets/application.scss" // CSS/SCSS をインポート
import "bootstrap" // npm からインストールしたライブラリ
Rails.start()
Turbolinks.start()
ActiveStorage.start()
console.log("Hello from Webpacker!")
Import maps (Rails 7 デフォルト)
ビルドプロセスやバンドルなしで、ブラウザの ES Module 機能を利用して npm パッケージなどを使う方法。
- 設定ファイル:
config/importmap.rb
で、モジュール名とファイルパス (ローカルまたはCDN) のマッピングを定義します。./bin/importmap pin module-name
コマンドで追加できます。 - 読み込み: レイアウトで
javascript_importmap_tags
を使用します。 - 利点: シンプル、ビルド不要。
- 欠点: 古いブラウザのサポート、高度なトランスパイル (TypeScript, JSXなど) は別途設定が必要。
# config/importmap.rb
pin "application", preload: true
pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
pin_all_from "app/javascript/controllers", under: "controllers"
pin "lodash", to: "https://ga.jspm.io/npm:lodash@4.17.21/lodash.js" # CDNから
<!DOCTYPE html>
<html>
<head> <%# ... %> <%= javascript_importmap_tags %> <%# これが必要 %>
</head>
<%# ... %>
</html>
// app/javascript/application.js
import "@hotwired/turbo-rails"
import "controllers" // app/javascript/controllers 以下のファイルを読み込む
import lodash from "lodash"
console.log("Import maps are working!")
console.log(lodash.VERSION)
Propshaft (Rails 7 オプション)
Sprockets の代替となる、より高速でシンプルなアセットパイプライン実装。
- ファイルを提供することに重点を置き、プリプロセス機能は限定的。
- フィンガープリント付きのファイル名を生成し、
public/assets
に配置。 - 設定は主に
config/propshaft.yml
で行う。
テスト (test or spec)
コードの品質と動作を保証する
Rails のデフォルトは Minitest ですが、RSpec も広く使われています。
テストの種類
種類 | 目的 | 配置場所 (Minitest) |
---|---|---|
モデルテスト (Unit Test) | モデルのメソッド、バリデーション、スコープ、関連などを単体でテスト。 | test/models/ |
ヘルパーテスト (Unit Test) | ビューヘルパーメソッドを単体でテスト。 | test/helpers/ |
メーラーテスト (Unit Test) | Action Mailer のメール生成や配信内容をテスト。 | test/mailers/ |
ジョブテスト (Unit Test) | Active Job のジョブ実行ロジックをテスト。 | test/jobs/ |
コントローラーテスト (Functional Test / Integration Test) | アクションの動作、レスポンス (ステータスコード、テンプレート、リダイレクト)、セッション、フラッシュなどをテスト。リクエスト/レスポンスサイクルをシミュレート。 (※Rails 5以降はIntegration Testへの移行推奨) | test/controllers/ |
インテグレーションテスト (Integration Test) | 複数のコントローラーやモデル、ビューを含む一連のユーザー操作フローをテスト。ルーティングからレスポンスまで。 | test/integration/ |
システムテスト (System Test / End-to-End Test) | 実際のブラウザ (Headless Chromeなど) を使用して、ユーザーインターフェースを含めたアプリケーション全体の動作をテスト。JavaScriptの動作も確認可能。 | test/system/ |
ルーティングテスト | config/routes.rb の設定が期待通りに動作するかテスト。 | test/controllers/ (または test/routing/ ) |
テスト実行
# すべてのテストを実行
rails test
# 特定のファイルを実行
rails test test/models/user_test.rb
# 特定のディレクトリを実行
rails test test/system
# 特定の行番号のテストを実行
rails test test/controllers/posts_controller_test.rb:25
# 特定のテスト名で実行 (-n オプション)
rails test test/models/article_test.rb -n test_should_be_published
# テスト結果を詳細表示 (-v オプション)
rails test -v
Minitest の基本
# test/models/post_test.rb
require "test_helper"
class PostTest < ActiveSupport::TestCase # setup は各テストの前に実行される def setup @user = users(:one) # フィクスチャからデータを取得 (test/fixtures/users.yml) @post = Post.new(title: "Test Title", body: "Test body.", user: @user) end # テストメソッド名は "test_" で始める test "should be valid" do assert @post.valid? # アサーション: postが有効であること end test "title should be present" do @post.title = " " assert_not @post.valid? # アサーション: postが無効であること end test "body should be present" do @post.body = nil assert_not @post.valid? end test "user should be present" do @post.user = nil assert_not @post.valid? end # 他のバリデーションやメソッドのテスト... test "should retrieve published posts scope" do # フィクスチャ posts(:published) が公開済みである前提 published_posts = Post.published assert_includes published_posts, posts(:published) # 配列に含まれるか assert_not_includes published_posts, posts(:draft) # 配列に含まれないか end # teardown は各テストの後に実行される (通常はあまり使わない) # def teardown # # クリーンアップ処理 # end
end
# test/controllers/posts_controller_test.rb
require "test_helper"
class PostsControllerTest < ActionDispatch::IntegrationTest setup do @post = posts(:one) @user = users(:one) sign_in_as(@user) # ログインヘルパー (自作またはGem) end test "should get index" do get posts_url # posts_path ヘルパーが使える assert_response :success # レスポンスステータスが 2xx であること assert_select "h1", "Posts Index" # HTML要素の存在と内容を確認 end test "should show post" do get post_url(@post) assert_response :success assert_select "h1", @post.title end test "should get new" do get new_post_url assert_response :success end test "should create post" do # assert_difference(評価する式, 変化量) { ブロックを実行 } assert_difference("Post.count", 1) do post posts_url, params: { post: { title: "New Post", body: "Content", user_id: @user.id } } end assert_redirected_to post_url(Post.last) # リダイレクト先を確認 assert_equal "Post was successfully created.", flash[:notice] # フラッシュメッセージを確認 end # edit, update, destroy のテストも同様に記述
end
# test/system/posts_test.rb
require "application_system_test_case"
class PostsTest < ApplicationSystemTestCase setup do @post = posts(:one) @user = users(:one) login_as @user # Deviseなどのヘルパー例 end test "visiting the index" do visit posts_url # 実際のブラウザでアクセス assert_selector "h1", text: "Posts Index" # ページ内の要素を確認 assert_text @post.title # ページ内にテキストが存在するか確認 end test "creating a Post" do visit posts_url click_on "New Post" # ボタンやリンクをクリック fill_in "Title", with: "System Test Post" # フォームに入力 fill_in "Body", with: "This is created by system test." click_on "Create Post" # 送信ボタンをクリック assert_text "Post was successfully created." # 結果のテキストを確認 assert_selector "h1", text: "System Test Post" end # 更新、削除などのユーザー操作をシミュレートするテストも記述
end
主なアサーション (Minitest)
アサーション | 説明 |
---|---|
assert(test, msg = nil) | test が真であること。 |
assert_not(test, msg = nil) | test が偽であること。 |
assert_equal(expected, actual, msg = nil) | expected == actual であること。 |
assert_not_equal(expected, actual, msg = nil) | expected != actual であること。 |
assert_nil(obj, msg = nil) | obj が nil であること。 |
assert_not_nil(obj, msg = nil) | obj が nil でないこと。 |
assert_empty(collection, msg = nil) | コレクションが空であること (empty? )。 |
assert_not_empty(collection, msg = nil) | コレクションが空でないこと。 |
assert_includes(collection, obj, msg = nil) | コレクションが obj を含むこと (include? )。 |
assert_not_includes(collection, obj, msg = nil) | コレクションが obj を含まないこと。 |
assert_instance_of(klass, obj, msg = nil) | obj が klass のインスタンスであること。 |
assert_kind_of(klass, obj, msg = nil) | obj が klass またはそのサブクラスのインスタンスであること。 |
assert_match(regexp, string, msg = nil) | string が正規表現 regexp にマッチすること。 |
assert_no_match(regexp, string, msg = nil) | string が正規表現 regexp にマッチしないこと。 |
assert_raises(exception1, exception2, ...) { ... } | ブロック内で指定された例外が発生すること。 |
assert_difference(expression, difference = 1, msg = nil) { ... } | ブロック実行前後で expression の評価結果が difference だけ変化すること。 |
assert_no_difference(expression, msg = nil) { ... } | ブロック実行前後で expression の評価結果が変化しないこと。 |
assert_redirected_to(expected_url_options, msg = nil) | 指定されたURLにリダイレクトされたこと。 |
assert_response(type, msg = nil) | レスポンスのステータスコードが指定されたタイプ (:success, :redirect, :missing, :error, 数値) であること。 |
assert_select(selector, equality?, message?) { ... } | CSSセレクタにマッチする要素が存在するか、内容が正しいかなどをテスト。 |
フィクスチャ (Fixtures) と FactoryBot
- フィクスチャ: YAMLファイル (
test/fixtures/
) にテストデータを定義。シンプルだが、データ間の依存関係や大量のデータ管理が煩雑になりやすい。 - FactoryBot (旧 FactoryGirl): Rubyコードでテストデータ生成の「ファクトリ」を定義するGem。より柔軟で管理しやすく、関連データの生成も容易。RSpecと組み合わせて使われることが多いが、Minitestでも利用可能。
# test/fixtures/users.yml
one: name: User One email: one@example.com # password_digest: <%= BCrypt::Password.create('password') %>
two: name: User Two email: two@example.com
# test/factories/users.rb (FactoryBot の例)
FactoryBot.define do factory :user do name { "Test User" } sequence(:email) { |n| "tester#{n}@example.com" } password { "password" } password_confirmation { "password" } # Trait (属性のバリエーション) trait :admin do admin { true } end # 関連データの生成 factory :user_with_posts do transient do posts_count { 5 } end after(:create) do |user, evaluator| create_list(:post, evaluator.posts_count, user: user) end end end
end
# テストコードでの FactoryBot 利用例
# user = build(:user) # メモリ上にインスタンス作成 (保存しない)
# user = create(:user) # DBに保存
# admin_user = create(:user, :admin) # Trait を使う
# user_with_posts = create(:user_with_posts, posts_count: 3)
国際化 (I18n)
アプリケーションを多言語対応させる
ロケールファイル
翻訳テキストを言語ごとにYAMLファイルで管理。config/locales/
ディレクトリに配置。
# config/locales/en.yml
en: hello: "Hello world" messages: welcome: "Welcome, %{user_name}!" activerecord: models: post: "Article" attributes: post: title: "Subject" body: "Content" errors: messages: blank: "can't be blank" models: post: attributes: title: too_short: "is too short (minimum is %{count} characters)" date: formats: default: "%Y-%m-%d" short: "%b %d" time: formats: default: "%a, %d %b %Y %H:%M:%S %z" short: "%d %b %H:%M"
# config/locales/ja.yml
ja: hello: "こんにちは、世界" messages: welcome: "ようこそ、%{user_name}さん!" activerecord: models: post: "記事" attributes: post: title: "件名" body: "本文" errors: messages: blank: "を入力してください" models: post: attributes: title: too_short: "は%{count}文字以上で入力してください" date: formats: default: "%Y年%m月%d日" short: "%m/%d" time: formats: default: "%Y年%m月%d日(%a) %H時%M分%S秒 %z" short: "%H:%M"
翻訳ヘルパー (t
/ translate
)
ビューやコントローラーで翻訳テキストを取得。
# ビュー
<h1><%= t('hello') %></h1>
<p><%= t('messages.welcome', user_name: current_user.name) %></p> <%# 変数を渡す %>
# コントローラー
flash[:notice] = t('.success_message') # '.' は現在のコントローラー/アクションに対応するキーを示す (遅延探索)
# 例: PostsController#create なら t('.success_message') は t('posts.create.success_message') を探す
# モデル名や属性名の翻訳
Post.model_name.human # => "記事" (jaロケール時)
Post.human_attribute_name(:title) # => "件名" (jaロケール時)
# バリデーションエラーメッセージも自動で翻訳される
# デフォルト値
t('messages.non_existent_key', default: 'Default message')
t('optional_feature.title', default: :'.alternative_title') # シンボルで別のキーを指定
# 複数形 (キーに count を渡し、YAMLで one/other を定義)
t('inbox.messages', count: 1) # => "1 message" (en)
t('inbox.messages', count: 5) # => "5 messages" (en)
# en.yml:
# inbox:
# messages:
# one: "1 message"
# other: "%{count} messages"
ローカライズヘルパー (l
/ localize
)
日付や時刻、数値をロケールに合わせてフォーマット。
# ビュー
<p>作成日: <%= l(@post.created_at, format: :long) %></p>
<p>最終更新: <%= l(@post.updated_at, format: '%Y/%m/%d') %></p> <%# カスタムフォーマット %>
<p>価格: <%= number_to_currency(@product.price) %></p> <%# number_ helpersもI18nを利用 %>
ロケールの設定
# config/application.rb
module MyApp class Application < Rails::Application # ... # 利用可能なロケールをホワイトリストで指定 config.i18n.available_locales = [:en, :ja] # デフォルトロケール config.i18n.default_locale = :ja # YAMLのネストしたキーを'.'で連結して参照できるようにする (デフォルトtrue) # config.i18n.fallbacks = true # 指定ロケールに翻訳がない場合、デフォルトロケール等にフォールバック end
end
# リクエストごとにロケールを設定 (例: ApplicationController)
class ApplicationController < ActionController::Base before_action :set_locale private def set_locale # パラメータ ( /?locale=en )、サブドメイン、HTTPヘッダー、ユーザー設定などから決定 I18n.locale = params[:locale] || user_preferred_locale || http_accept_language.compatible_language_from(I18n.available_locales) || I18n.default_locale end # URLにロケール情報を含める場合 (ヘルパーで自動付与) # config/routes.rb # scope "(:locale)", locale: /#{I18n.available_locales.join("|")}/ do # # ... ルーティング定義 ... # root to: "welcome#index" # end # ApplicationController に以下を追加 # def default_url_options # { locale: I18n.locale } # end
end
メール (Action Mailer)
アプリケーションからメールを送信する
メーラー生成
rails generate mailer UserMailer welcome notification
# 生成されるファイル:
# app/mailers/user_mailer.rb
# app/views/user_mailer/welcome.html.erb
# app/views/user_mailer/welcome.text.erb (テキスト形式メール用)
# app/views/user_mailer/notification.html.erb
# app/views/user_mailer/notification.text.erb
# test/mailers/user_mailer_test.rb
# test/mailers/previews/user_mailer_preview.rb
メーラークラス定義
# app/mailers/user_mailer.rb
class UserMailer < ApplicationMailer # デフォルトの送信元アドレスなどを設定可能 default from: 'notifications@example.com' # メーラーアクション def welcome(user) @user = user # ビューで使うインスタンス変数 @url = 'http://example.com/login' # ビューで使う変数 # ヘッダー設定とメール送信 (mailメソッド) mail( to: @user.email, subject: 'Welcome to My Awesome Site!', # cc: '...', bcc: '...', reply_to: '...' なども指定可能 # template_path: 'notifications', # ビューのパスを変更 # template_name: 'another_welcome' # ビューのファイル名を変更 ) do |format| format.html { render layout: 'mailer' } # HTMLメールのレイアウト指定 format.text end end def notification(user, message) @user = user @message = message # 添付ファイル attachments['invoice.pdf'] = File.read('/path/to/invoice.pdf') attachments.inline['logo.png'] = File.read('app/assets/images/logo.png') # インライン添付 (HTML内で表示) mail to: @user.email, subject: 'Important Notification' end
end
# app/mailers/application_mailer.rb (ベースクラス)
class ApplicationMailer < ActionMailer::Base default from: 'from@example.com' layout 'mailer' # デフォルトレイアウト (app/views/layouts/mailer.html.erb)
end
メールビューの作成
HTML形式 (.html.erb
) とテキスト形式 (.text.erb
) の両方を用意することが推奨されます。
<%# app/views/user_mailer/welcome.html.erb %>
<!DOCTYPE html>
<html>
<head> <meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
</head>
<body> <h1>Welcome, <%= @user.name %>!</h1> <p> You have successfully signed up to example.com, your username is: <%= @user.email %>.<br> </p> <p> To login to the site, just follow this link: <%= link_to 'Login', @url %>. <%# インライン画像 %> <%= image_tag attachments['logo.png'].url, alt: 'Logo', style: 'width: 50px;' if attachments['logo.png'] %> </p> <p>Thanks for joining and have a great day!</p>
</body>
</html>
<%# app/views/user_mailer/welcome.text.erb %>
Welcome, <%= @user.name %>!
You have successfully signed up to example.com,
your username is: <%= @user.email %>.
To login to the site, just follow this link: <%= @url %>.
Thanks for joining and have a great day!
メール送信
メーラーアクションはメールオブジェクト (Mail::Message
) を返します。deliver_now
または deliver_later
で実際に送信します。
# 同期的に送信 (現在のプロセスで実行、完了を待つ)
UserMailer.welcome(@user).deliver_now
# 非同期的に送信 (バックグラウンドジョブキューに追加、推奨)
# Active Job の設定が必要 (Sidekiq, Resque など)
UserMailer.welcome(@user).deliver_later
# 配信日時を指定
UserMailer.notification(@user, msg).deliver_later(wait: 1.hour)
# 特定のキューを指定
UserMailer.welcome(@user).deliver_later(queue: 'urgent_emails')
設定
config/environments/
(development.rb, production.rb など) でメール配信方法を設定します。
# config/environments/development.rb (開発環境)
config.action_mailer.raise_delivery_errors = false # エラーを発生させない
config.action_mailer.perform_caching = false
# Letter Opener Gem: 送信するメールをブラウザでプレビュー (送信しない)
config.action_mailer.delivery_method = :letter_opener
config.action_mailer.perform_deliveries = true
# SMTP設定 (例: Mailtrap, Gmailなど)
# config.action_mailer.delivery_method = :smtp
# config.action_mailer.smtp_settings = {
# address: 'smtp.mailtrap.io',
# port: 2525,
# user_name: ENV['MAILTRAP_USERNAME'],
# password: ENV['MAILTRAP_PASSWORD'],
# authentication: :cram_md5
# }
# デフォルトURLオプション (メール内のリンク生成用)
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
# config/environments/production.rb (本番環境)
config.action_mailer.raise_delivery_errors = true # エラーを発生させる
config.action_mailer.perform_caching = false
config.action_mailer.delivery_method = :smtp # または :sendgrid, :postmark などサービス用アダプタ
config.action_mailer.smtp_settings = { address: ENV['SMTP_ADDRESS'], port: ENV['SMTP_PORT'], domain: ENV['SMTP_DOMAIN'], user_name: ENV['SMTP_USERNAME'], password: ENV['SMTP_PASSWORD'], authentication: 'plain', enable_starttls_auto: true
}
# デフォルトURLオプション
config.action_mailer.default_url_options = { host: 'yourdomain.com', protocol: 'https' }
# アセットホスト (メール内の画像パス用)
config.asset_host = 'https://yourcdn.com' # または 'https://yourdomain.com'
プレビュー
test/mailers/previews/
以下のファイルで、ブラウザ上でメールの見た目を確認できます。http://localhost:3000/rails/mailers
にアクセスします。
# test/mailers/previews/user_mailer_preview.rb
class UserMailerPreview < ActionMailer::Preview # http://localhost:3000/rails/mailers/user_mailer/welcome def welcome user = User.first || FactoryBot.create(:user) # テストデータを使用 UserMailer.welcome(user) end # http://localhost:3000/rails/mailers/user_mailer/notification def notification user = User.last || FactoryBot.create(:user) message = "This is a test notification message." UserMailer.notification(user, message) end
end
バックグラウンドジョブ (Active Job)
時間のかかる処理をバックグラウンドで非同期に実行する
ジョブ生成
rails generate job ProcessCsvFile --queue critical
# 生成されるファイル:
# app/jobs/process_csv_file_job.rb
# test/jobs/process_csv_file_job_test.rb
ジョブクラス定義
perform
メソッド内に実行したい処理を記述します。
# app/jobs/process_csv_file_job.rb
class ProcessCsvFileJob < ApplicationJob # デフォルトのキューを指定 (生成時オプションまたはここで指定) queue_as :default # リトライ設定 (例: 5秒後、最大3回リトライ) retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3 # 特定のエラーでリトライしない discard_on ActiveJob::DeserializationError # perform メソッドに実行する処理を書く # 引数にはシリアライズ可能なオブジェクト (単純な型, String, Integer, Float, NilClass, TrueClass, FalseClass, BigDecimal, Symbol, Date, Time, DateTime, ActiveSupport::TimeWithZone, ActiveSupport::Duration, Hash, ActiveSupport::HashWithIndifferentAccess, Array) または Global ID を持つ Active Record オブジェクトを渡す def perform(csv_data, user_id) user = User.find(user_id) # 時間のかかるCSV処理... results = CsvProcessor.process(csv_data) # 結果を通知するなど NotificationMailer.csv_processed(user, results).deliver_later rescue StandardError => e # エラーハンドリング Rails.logger.error("CSV processing failed for user #{user_id}: #{e.message}") # 必要なら再試行や通知など end # コールバックも利用可能 # before_perform :log_start # after_perform :log_end # around_perform :benchmark_job
end
ジョブの実行 (エンキュー)
perform_later
でジョブをキューに追加します。perform_now
は同期的に実行します (テストやデバッグ用)。
# 非同期実行 (バックエンドキューアダプタ経由)
ProcessCsvFileJob.perform_later(csv_content, current_user.id)
# 実行日時を指定
ProcessCsvFileJob.set(wait: 10.minutes).perform_later(data, user.id)
ProcessCsvFileJob.set(wait_until: Date.tomorrow.noon).perform_later(data, user.id)
# 優先度やキューを指定 (アダプタが対応していれば)
ProcessCsvFileJob.set(queue: :low_priority, priority: 10).perform_later(data, user.id)
# 同期実行 (現在のプロセスで即時実行)
ProcessCsvFileJob.perform_now(csv_content, current_user.id)
キューアダプタの設定
Active Job は様々なバックエンドキューイングシステム (Sidekiq, Resque, Delayed Job, Sucker Punch, Queue Classic, Sneakers など) のラッパーです。アダプタを設定する必要があります。
# config/application.rb
module MyApp class Application < Rails::Application # ... # デフォルトは :async (インメモリ、プロセス再起動で失われる、開発向き) # config.active_job.queue_adapter = :async # Sidekiq を使う場合 (Gem 'sidekiq' を追加) config.active_job.queue_adapter = :sidekiq # Delayed Job を使う場合 (Gem 'delayed_job_active_record' を追加、マイグレーション必要) # config.active_job.queue_adapter = :delayed_job end
end
# 環境ごとにアダプタを変更する場合
# config/environments/production.rb
config.active_job.queue_adapter = :sidekiq
# config/environments/test.rb
# テスト中は :test アダプタが便利 (ジョブはキューに入るが実行されない)
config.active_job.queue_adapter = :test
# または :inline で同期実行
# config.active_job.queue_adapter = :inline
各アダプタには固有の設定ファイル (例: config/sidekiq.yml
) や、ワーカープロセスの起動が必要です。
# Sidekiq ワーカー起動例
bundle exec sidekiq -q default -q critical -C config/sidekiq.yml
ヒント: Sidekiq は Redis を利用し、高パフォーマンスで多機能なため、本番環境で広く使われています。
テスト
:test
アダプタや Active Job のテストヘルパーを使います。
# test/jobs/process_csv_file_job_test.rb
require 'test_helper'
class ProcessCsvFileJobTest < ActiveJob::TestCase include ActiveJob::TestHelper # ヘルパーをインクルード setup do @user = users(:one) @csv_data = "header1,header2\nvalue1,value2" end test 'job is enqueued' do # ジョブがキューに追加されることを確認 assert_enqueued_jobs 1 do ProcessCsvFileJob.perform_later(@csv_data, @user.id) end end test 'job is enqueued in the correct queue' do # 正しいキューに追加されることを確認 assert_enqueued_with(job: ProcessCsvFileJob, queue: 'default') do ProcessCsvFileJob.perform_later(@csv_data, @user.id) end end test 'job performs processing and sends email' do # perform_enqueued_jobs ブロック内でキューのジョブを実行 perform_enqueued_jobs do ProcessCsvFileJob.perform_later(@csv_data, @user.id) end # ジョブの結果を確認 (例: メールが送信されたか) assert_equal 1, ActionMailer::Base.deliveries.size mail = ActionMailer::Base.deliveries.last assert_equal [@user.email], mail.to assert_equal 'CSV Processed Notification', mail.subject # 仮の件名 end test 'job handles errors' do # perform_enqueued_jobs でジョブを実行し、エラー処理を確認 # 例: CsvProcessor が例外を発生させるようにスタブ/モックを設定 CsvProcessor.stubs(:process).raises(StandardError, "Processing error") # ログ出力を確認するなど assert_logged "CSV processing failed for user #{@user.id}: Processing error" do perform_enqueued_jobs do ProcessCsvFileJob.perform_later(@csv_data, @user.id) end end # メールが送信されていないことなどを確認 assert_equal 0, ActionMailer::Base.deliveries.size end
end
その他
設定、初期化、コマンドなど
環境設定
config/environments/
ディレクトリに、各環境 (development, test, production) の設定ファイルがあります。
development.rb
: 開発環境向け設定。コードの変更をリロード、詳細なログ、キャッシュ無効化など。test.rb
: テスト環境向け設定。キャッシュクラス、テスト用設定など。production.rb
: 本番環境向け設定。パフォーマンス最適化、キャッシュ有効化、ログレベル、アセットホスト設定など。- 共通設定は
config/application.rb
やconfig/environment.rb
に記述します。
# 例: config/environments/production.rb
Rails.application.configure do config.cache_classes = true # クラスをキャッシュ (リクエストごとにリロードしない) config.eager_load = true # アプリ起動時に全クラスを読み込む config.consider_all_requests_local = false # エラー発生時に詳細なデバッグ情報を表示しない config.action_controller.perform_caching = true # Action Controller のキャッシュを有効化 # config.cache_store = :memory_store # キャッシュストアの指定 (メモリ、Redis、Memcachedなど) # config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? # 静的ファイル配信 (通常はWebサーバーが行う) config.assets.compile = false # アセットの動的コンパイルを無効化 (要 precompile) config.log_level = :info # ログレベル config.log_tags = [:request_id] # ログのタグ付け config.i18n.fallbacks = true # I18n フォールバック有効化 config.active_support.report_deprecations = false # 非推奨警告を抑制 # ... 他多数の設定 ...
end
設定値は Rails.configuration
や Rails.application.config
で参照できます。
初期化子 (Initializers)
config/initializers/
ディレクトリにある .rb
ファイルは、RailsフレームワークとアプリケーションのGemがロードされた後に実行されます。
- Gemの設定 (例: Devise, CarrierWave, Kaminari など)
- 外部APIクライアントの初期化
- モンキーパッチ (注意して使用)
- カスタム設定の読み込み
# config/initializers/kaminari_config.rb (例)
Kaminari.configure do |config| config.default_per_page = 10 # config.max_per_page = nil # config.window = 4 # config.outer_window = 0 # config.left = 0 # config.right = 0 # config.page_method_name = :page # config.param_name = :page
end
Rails コマンド (rails
または bin/rails
)
よく使うRailsコマンドの一覧です。
コマンド | 説明 |
---|---|
rails new [app_name] [options] | 新規Railsアプリケーションを作成します。 |
rails server (s ) | 開発用Webサーバー (Puma) を起動します。 |
rails console (c ) | アプリケーションのコンテキストでIRBセッションを開始します。 |
rails generate (g ) | Scaffold, Model, Controller, Migration などのコードを生成します。例: rails g scaffold Post title:string body:text |
rails destroy (d ) | generate で作成したファイルを削除します。 |
rails db:create | config/database.yml に基づいてデータベースを作成します。 |
rails db:drop | データベースを削除します。 |
rails db:migrate | 保留中のマイグレーションを実行します。 |
rails db:rollback | 最後のマイグレーションを取り消します。 |
rails db:migrate:status | マイグレーションの状態を表示します。 |
rails db:schema:load | db/schema.rb からデータベーススキーマをロードします (既存データは削除)。 |
rails db:structure:load | db/structure.sql からデータベーススキーマをロードします (config.active_record.schema_format = :sql の場合)。 |
rails db:seed | db/seeds.rb ファイルを実行して初期データを投入します。 |
rails db:setup | db:create , db:schema:load , db:seed を実行します。 |
rails db:reset | db:drop , db:setup を実行します。 |
rails db:prepare | データベースが存在しなければ作成し、保留中のマイグレーションを実行します。 |
rails db:migrate:reset | db:drop , db:create , db:migrate を実行します。 |
rails routes | アプリケーションのルーティング一覧を表示します。-c オプションでコントローラー名を指定、-g オプションでgrep検索。 |
rails test | テストスイートを実行します。 |
rails assets:precompile | 本番環境向けにアセットをプリコンパイルします。 |
rails assets:clobber | プリコンパイルされたアセットを削除します。 |
rails runner (r ) | 指定したRubyコードをアプリケーションのコンテキストで実行します。例: rails r 'puts User.count' |
rails log | log/development.log (または現在の環境のログ) を表示します (tail -f)。 |
rails initializers | 実行される初期化子の一覧を表示します。 |
rails middleware | アプリケーションのミドルウェアスタックを表示します。 |
rails restart | tmp/restart.txt ファイルを更新して、Passengerなどのアプリケーションサーバーを再起動させます。 |
rails notes | コード中の特定の注釈 (FIXME, OPTIMIZE, TODO) を検索して表示します。 |
rails about | Rails, Ruby, Rack などのバージョン情報を表示します。 |
rails -T (--tasks ) | 利用可能なRakeタスク (railsコマンド) の一覧を表示します。 |
rails credentials:edit | 暗号化された資格情報ファイル (config/credentials.yml.enc ) を編集します。 |
多くのコマンドにはヘルプオプション (-h
または --help
) があります。例: rails generate --help
秘密情報管理 (Credentials)
Rails 5.2 以降、APIキーやパスワードなどの秘密情報を安全に管理するために Credentials 機能が導入されました。
- 秘密情報は
config/credentials.yml.enc
に暗号化されて保存されます。 - 復号キーは
config/master.key
に保存されます (このキーは絶対にバージョン管理に含めないでください!.gitignore
で除外されます)。 - 編集:
rails credentials:edit
コマンドを使用します (環境変数EDITOR
で指定されたエディタが開きます)。 - アクセス: アプリケーションコード内では
Rails.application.credentials
でアクセスできます。# config/credentials.yml.enc の内容 (編集時): # aws: # access_key_id: YOUR_ACCESS_KEY_ID # secret_access_key: YOUR_SECRET_ACCESS_KEY # stripe: # secret_key: sk_test_... # コード内でのアクセス: aws_key = Rails.application.credentials.aws[:access_key_id] stripe_secret = Rails.application.credentials.dig(:stripe, :secret_key)
- 環境ごとの秘密情報: Rails 6 以降では、
config/credentials/production.yml.enc
のように環境別のファイルも利用できます。rails credentials:edit --environment production
で編集します。 master.key
(または環境別のキー) は、デプロイ先のサーバーに安全な方法で配置する必要があります (環境変数RAILS_MASTER_KEY
で渡すのが一般的)。