Ruby on Railsの基礎知識
フレームワークの特徴と強み
Ruby on Railsは、Web開発の効率性と生産性を最大限に高めるためのフレームワークです。以下の主要な特徴と強みにより、多くの開発者から支持されています:
- Convention over Configuration(CoC)
- 設定より規約を重視する思想
- 標準的な命名規則や設定に従うことで、最小限のコードで開発可能
- 例:モデル名が単数形なら、対応するテーブル名は複数形
- Don’t Repeat Yourself(DRY)
- コードの重複を避け、保守性を向上
- 共通機能の再利用を促進
- 変更箇所を最小限に抑える
- ActiveRecordによる直感的なデータベース操作
# データの取得と作成が直感的 user = User.find(1) new_user = User.create(name: "John", email: "john@example.com") # 関連付けも簡潔に記述可能 class User < ApplicationRecord has_many :posts has_one :profile end
- 豊富なGem(ライブラリ)エコシステム
- 認証(Devise)
- 管理画面(ActiveAdmin)
- ファイルアップロード(CarrierWave)
- API開発(Grape)
開発環境のセットアップ手順
1. 必要なツールのインストール
# Rubyのインストール(rbenvを使用) brew install rbenv rbenv init rbenv install 3.2.2 rbenv global 3.2.2 # Railsのインストール gem install rails -v 7.1.2 # Node.jsとYarnのインストール brew install node npm install -g yarn
2. 新規プロジェクトの作成
# PostgreSQLを使用する新規Railsプロジェクトの作成 rails new myapp --database=postgresql # プロジェクトディレクトリへ移動 cd myapp # 依存関係のインストール bundle install
3. 開発サーバーの起動
# データベースの作成と初期化 rails db:create rails db:migrate # 開発サーバーの起動 rails server
MVCアーキテクチャの実践的な理解
Model(モデル)
- ビジネスロジックとデータの管理を担当
- データベースとのやり取りを行う
- バリデーションやアソシエーションを定義
# app/models/article.rb class Article < ApplicationRecord belongs_to :user has_many :comments validates :title, presence: true validates :content, length: { minimum: 10 } # カスタムメソッドの例 def self.published where(status: 'published') end end
View(ビュー)
- ユーザーインターフェースの表示を担当
- ERBテンプレートを使用してHTML生成
- パーシャルを活用して再利用性を高める
<!-- app/views/articles/index.html.erb --> <h1>記事一覧</h1> <% @articles.each do |article| %> <div class="article"> <h2><%= article.title %></h2> <p><%= truncate(article.content, length: 100) %></p> <%= link_to '詳細を見る', article_path(article) %> </div> <% end %> <%= render 'shared/pagination' %>
Controller(コントローラー)
- モデルとビューの橋渡し役
- リクエストの処理とレスポンスの生成
- ビジネスロジックの組み立て
# app/controllers/articles_controller.rb class ArticlesController < ApplicationController before_action :set_article, only: [:show, :edit, :update, :destroy] def index @articles = Article.published.page(params[:page]) end def show @comments = @article.comments.includes(:user) end def create @article = current_user.articles.build(article_params) if @article.save redirect_to @article, notice: '記事が作成されました' else render :new end end private def set_article @article = Article.find(params[:id]) end def article_params params.require(:article).permit(:title, :content) end end
MVCの相互作用
- リクエストの流れ
- ブラウザからのリクエストがRouterで解析される
- 適切なControllerアクションにルーティング
- ControllerがModelからデータを取得
- ViewでHTMLを生成してレスポンス
- データの流れ
- ModelがDBからデータを取得・保存
- ControllerがModelのデータをViewに受け渡し
- ViewがデータをHTMLとして描画
この基礎的な構造を理解することで、Railsアプリケーションの開発効率が大きく向上します。また、適切な責務分離により、保守性の高いコードベースを維持することが可能になります。
実践的なRailsアプリケーション開発ガイド
モデル設計のベストプラクティス
1. 適切なバリデーションの実装
class User < ApplicationRecord # 必須項目の検証 validates :email, presence: true, uniqueness: { case_sensitive: false } validates :username, presence: true, length: { in: 3..20 } # メールアドレスのフォーマット検証 validates :email, format: { with: URI::MailTo::EMAIL_REGEXP, message: "は有効なメールアドレスではありません" } # カスタムバリデーション validate :password_complexity private def password_complexity return if password.blank? unless password.match?(/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/) errors.add :password, 'は少なくとも8文字で、文字と数字を含む必要があります' end end end
2. アソシエーションの適切な設計
class Post < ApplicationRecord # 基本的な関連付け belongs_to :user has_many :comments, dependent: :destroy has_many :likes has_many :liking_users, through: :likes, source: :user # ポリモーフィック関連付け has_many :attachments, as: :attachable # スコープを使用した関連付けの制御 has_many :approved_comments, -> { where(status: 'approved') }, class_name: 'Comment' end
3. コールバックの効果的な使用
class Order < ApplicationRecord before_validation :normalize_phone_number after_create :send_confirmation_email before_save :calculate_total private def normalize_phone_number self.phone = phone.gsub(/[^\d]/, '') if phone.present? end def send_confirmation_email OrderMailer.confirmation(self).deliver_later end def calculate_total self.total = order_items.sum { |item| item.price * item.quantity } end end
効率的なルーティング設定方法
1. RESTfulルーティングの基本
Rails.application.routes.draw do # 基本的なリソースルーティング resources :posts do resources :comments, shallow: true end # カスタムアクションの追加 resources :users do member do patch :activate patch :deactivate end collection do get :search end end # 名前付きルート get 'dashboard', to: 'dashboard#index', as: :dashboard # APIルーティング namespace :api do namespace :v1 do resources :posts, only: [:index, :show, :create] end end end
2. ルーティングの最適化
Rails.application.routes.draw do # パフォーマンスを考慮したルーティング resources :posts, only: [:index, :show] do resources :comments, only: [:create, :destroy] end # 制約付きルーティング constraints(SubdomainRouteConstraint.new) do resources :organizations end # カスタムパラメータ制約 get 'users/:id', to: 'users#show', constraints: { id: /[A-Za-z0-9\.]+/ } end
コントローラーでのビジネスロジック実装
1. サービスオブジェクトの活用
# app/services/user_registration_service.rb class UserRegistrationService def initialize(params) @params = params end def execute user = User.new(@params) if user.save setup_user_profile(user) send_welcome_email(user) { success: true, user: user } else { success: false, errors: user.errors } end end private def setup_user_profile(user) user.create_profile!(default_profile_params) end def send_welcome_email(user) UserMailer.welcome(user).deliver_later end def default_profile_params { visibility: 'public', theme: 'light' } end end # app/controllers/users_controller.rb class UsersController < ApplicationController def create result = UserRegistrationService.new(user_params).execute if result[:success] redirect_to user_path(result[:user]), notice: '登録が完了しました' else @user = User.new @user.errors.merge!(result[:errors]) render :new end end end
2. 効率的なコントローラー設計
class ArticlesController < ApplicationController before_action :set_article, only: [:show, :edit, :update, :destroy] before_action :authorize_article, only: [:edit, :update, :destroy] def index @articles = Article.includes(:author, :categories) .published .page(params[:page]) end def create @article = current_user.articles.build(article_params) respond_to do |format| if @article.save format.html { redirect_to @article, notice: '記事が作成されました' } format.json { render :show, status: :created } else format.html { render :new } format.json { render json: @article.errors, status: :unprocessable_entity } end end end private def set_article @article = Article.find(params[:id]) end def authorize_article authorize @article end def article_params params.require(:article) .permit(:title, :content, :status, category_ids: []) end end
3. 共通機能のモジュール化
# app/controllers/concerns/error_handling.rb module ErrorHandling extend ActiveSupport::Concern included do rescue_from ActiveRecord::RecordNotFound, with: :not_found rescue_from ActionController::ParameterMissing, with: :bad_request rescue_from Pundit::NotAuthorizedError, with: :forbidden end private def not_found respond_to do |format| format.html { render 'errors/not_found', status: :not_found } format.json { render json: { error: 'Resource not found' }, status: :not_found } end end def bad_request respond_to do |format| format.html { render 'errors/bad_request', status: :bad_request } format.json { render json: { error: 'Invalid parameters' }, status: :bad_request } end end def forbidden respond_to do |format| format.html { render 'errors/forbidden', status: :forbidden } format.json { render json: { error: 'Access denied' }, status: :forbidden } end end end # 使用例 class ApplicationController < ActionController::Base include ErrorHandling end
これらのベストプラクティスを実践することで、保守性が高く、スケーラブルなRailsアプリケーションを開発することができます。各コンポーネントの責務を明確に分離し、適切な設計パターンを採用することで、長期的なメンテナンス性も向上します。
データベース設計と操作
ActiveRecordを使いこなすテクニック
1. 高度なクエリメソッド
class User < ApplicationRecord # スコープを使用した複雑なクエリの定義 scope :active_this_month, -> { where('last_login_at >= ?', Time.current.beginning_of_month) } scope :with_complete_profile, -> { joins(:profile) .where.not(profiles: { bio: nil }) .where.not(profiles: { avatar_url: nil }) } # 複雑な条件を組み合わせた検索 def self.search(query) where('email LIKE :query OR username LIKE :query', query: "%#{query}%") .or( where(id: Profile.where('bio LIKE :query', query: "%#{query}%").select(:user_id)) ) end end
2. 関連テーブルの効率的な結合
class Post < ApplicationRecord # EAGERローディングの活用 scope :with_details, -> { includes(:author, :categories, comments: :user) } # 複雑な結合クエリ scope :popular_with_comments, -> { joins(:comments) .group('posts.id') .select('posts.*, COUNT(comments.id) as comments_count') .having('COUNT(comments.id) > ?', 5) .order('comments_count DESC') } end
3. カスタムSQLの活用
class Order < ApplicationRecord # 売上集計のための複雑なクエリ def self.monthly_sales_report find_by_sql(<<-SQL) SELECT DATE_TRUNC('month', created_at) as month, COUNT(*) as total_orders, SUM(total_amount) as revenue, AVG(total_amount) as average_order_value FROM orders WHERE status = 'completed' GROUP BY DATE_TRUNC('month', created_at) ORDER BY month DESC SQL end end
マイグレーションの効果的な管理方法
1. 安全なマイグレーション設計
class AddUserSettingsToUsers < ActiveRecord::Migration[7.0] def up # 新しいカラムの追加 add_column :users, :settings, :jsonb, null: false, default: {} # 既存データの移行 User.find_each do |user| user.update_column(:settings, { notification_preferences: user.read_attribute(:notification_preferences) || {}, theme: 'light', language: 'ja' }) end # 古いカラムの削除 remove_column :users, :notification_preferences end def down add_column :users, :notification_preferences, :jsonb User.find_each do |user| user.update_column(:notification_preferences, user.settings['notification_preferences']) end remove_column :users, :settings end end
2. インデックス管理
class OptimizeDatabaseIndexes < ActiveRecord::Migration[7.0] def change # 複合インデックスの追加 add_index :orders, [:user_id, :created_at] # ユニークインデックスの追加 add_index :users, :email, unique: true, where: "deleted_at IS NULL" # 部分インデックスの追加 add_index :posts, :published_at, where: "status = 'published'" # 不要なインデックスの削除 remove_index :comments, :updated_at end end
3. データベース制約の管理
class AddConstraintsToOrders < ActiveRecord::Migration[7.0] def change # CHECK制約の追加 add_check_constraint :orders, "total_amount >= 0", name: "check_positive_amount" # 外部キー制約の追加 add_foreign_key :orders, :users, on_delete: :restrict # NOT NULL制約の追加 change_column_null :orders, :status, false # デフォルト値の設定 change_column_default :orders, :status, from: nil, to: 'pending' end end
パフォーマンスを考慮したクエリの書き方
1. N+1問題の解決
# 悪い例 def index @posts = Post.all # N+1問題: 各投稿に対してユーザーとコメントのクエリが実行される @posts.each do |post| puts "#{post.user.name}: #{post.comments.count} comments" end end # 良い例 def index @posts = Post.includes(:user, :comments) # 必要なデータを1度に取得 @posts.each do |post| puts "#{post.user.name}: #{post.comments.size} comments" end end
2. バッチ処理の最適化
class BatchProcessor def self.process_large_dataset # find_eachを使用して少しずつ処理 User.find_each(batch_size: 1000) do |user| user.calculate_statistics end end def self.bulk_update_records # bulk_insertを使用して一括挿入 users_data = generate_users_data(10000) User.insert_all(users_data) # bulk_updateを使用して一括更新 updates = User.where(status: 'pending').select(:id).map do |user| { id: user.id, status: 'active', updated_at: Time.current } end User.upsert_all(updates) end end
3. クエリキャッシュの活用
class CacheOptimizedQueries def self.cached_popular_posts Rails.cache.fetch('popular_posts', expires_in: 1.hour) do Post.popular_with_comments.limit(10).to_a end end def self.cached_user_statistics(user_id) Rails.cache.fetch("user_stats/#{user_id}", expires_in: 30.minutes) do User.find(user_id).calculate_statistics end end end
これらのテクニックを適切に組み合わせることで、効率的で保守性の高いデータベース操作を実現できます。パフォーマンスを意識しながら、適切なインデックスとキャッシュ戦略を採用することで、アプリケーションの応答性を向上させることができます。
テスト駆動開発の実践
RSpecによる効率的なテスト設計
1. テストの基本構造
# spec/models/user_spec.rb require 'rails_helper' RSpec.describe User, type: :model do # letを使用したテストデータの定義 let(:user) { build(:user) } let(:admin) { build(:user, :admin) } # コンテキストによるテストのグループ化 context 'バリデーション' do it 'メールアドレスがない場合は無効' do user.email = nil expect(user).not_to be_valid end it 'パスワードが短すぎる場合は無効' do user.password = '123' expect(user).not_to be_valid expect(user.errors[:password]).to include('は6文字以上で入力してください') end end # 共有コンテキストの使用 context '管理者権限' do it '管理者は特別な権限を持つ' do expect(admin).to be_admin expect(admin.can_manage_users?).to be true end end end
2. ファクトリの効果的な設定
# spec/factories/users.rb FactoryBot.define do factory :user do sequence(:email) { |n| "user#{n}@example.com" } password { 'password123' } username { Faker::Internet.username } # トレイトを使用した柔軟なデータ作成 trait :admin do admin { true } role { 'administrator' } end trait :with_posts do after(:create) do |user| create_list(:post, 3, user: user) end end # コールバックの活用 after(:build) do |user| user.build_profile if user.profile.nil? end end end
テストケースの作成と実行方法
1. コントローラーテスト
# spec/controllers/posts_controller_spec.rb RSpec.describe PostsController, type: :controller do let(:user) { create(:user) } let(:post_item) { create(:post, user: user) } describe 'GET #index' do context '認証済みユーザー' do before { sign_in user } it '正常にレスポンスを返す' do get :index expect(response).to have_http_status(:success) end it 'すべての投稿を取得する' do posts = create_list(:post, 3) get :index expect(assigns(:posts)).to match_array(posts) end end end describe 'POST #create' do context '有効なパラメータの場合' do it '新しい投稿を作成する' do sign_in user post_params = attributes_for(:post) expect { post :create, params: { post: post_params } }.to change(Post, :count).by(1) end end end end
2. システムテスト
# spec/system/user_registration_spec.rb RSpec.describe 'ユーザー登録', type: :system do before do driven_by(:rack_test) end scenario 'ユーザーが新規登録する' do visit new_user_registration_path fill_in 'メールアドレス', with: 'test@example.com' fill_in 'パスワード', with: 'password123' fill_in 'パスワード(確認)', with: 'password123' expect { click_button '登録' }.to change(User, :count).by(1) expect(page).to have_content('アカウント登録が完了しました') end end
モックとスタブの活用テクニック
1. サービスのモック化
# spec/services/payment_service_spec.rb RSpec.describe PaymentService do let(:user) { create(:user) } let(:order) { create(:order, user: user) } describe '#process_payment' do context '外部決済サービスとの連携' do it '支払いが成功する場合' do payment_client = instance_double('PaymentClient') allow(payment_client).to receive(:charge).and_return( success: true, transaction_id: 'tx_123' ) service = PaymentService.new(order, payment_client) result = service.process_payment expect(result).to be_successful expect(order.reload.status).to eq('paid') end it '支払いが失敗する場合' do payment_client = instance_double('PaymentClient') allow(payment_client).to receive(:charge).and_raise( PaymentError.new('カードが拒否されました') ) service = PaymentService.new(order, payment_client) result = service.process_payment expect(result).not_to be_successful expect(order.reload.status).to eq('payment_failed') end end end end
2. 時間依存のテスト
# spec/models/subscription_spec.rb RSpec.describe Subscription do describe '#active?' do let(:subscription) { create(:subscription) } it '有効期限内の場合はtrueを返す' do travel_to Time.zone.local(2024, 1, 1, 12, 0, 0) do subscription.expires_at = 1.month.from_now expect(subscription).to be_active end end it '有効期限切れの場合はfalseを返す' do travel_to Time.zone.local(2024, 1, 1, 12, 0, 0) do subscription.expires_at = 1.day.ago expect(subscription).not_to be_active end end end end
3. メール送信のテスト
# spec/mailers/notification_mailer_spec.rb RSpec.describe NotificationMailer do let(:user) { create(:user) } describe '#welcome_email' do subject(:mail) { described_class.welcome_email(user) } it '正しい宛先にメールが送信される' do expect(mail.to).to eq([user.email]) end it '正しい件名が設定される' do expect(mail.subject).to eq('ようこそ!') end it 'メール本文にユーザー名が含まれる' do expect(mail.body.encoded).to include(user.username) end end end
これらのテストプラクティスを採用することで、信頼性の高いコードベースを維持し、リグレッションを防ぐことができます。また、テスト駆動開発を実践することで、設計の品質向上とメンテナンス性の向上を図ることができます。
セキュリティ対策の実装
一般的な脆弱性への対処方法
1. XSS(クロスサイトスクリプティング)対策
# config/initializers/content_security_policy.rb Rails.application.config.content_security_policy do |policy| policy.default_src :self policy.font_src :self, :https, :data policy.img_src :self, :https, :data policy.object_src :none policy.script_src :self policy.style_src :self, :https policy.frame_ancestors :none policy.base_uri :self policy.form_action :self end # app/helpers/application_helper.rb module ApplicationHelper def safe_user_content(content) sanitize content, tags: %w[p b i u ul li ol], attributes: %w[class id] end end # app/views/posts/show.html.erb <div class="post-content"> <%= safe_user_content(@post.content) %> </div>
2. CSRF(クロスサイトリクエストフォージェリ)対策
# app/controllers/application_controller.rb class ApplicationController < ActionController::Base protect_from_forgery with: :exception before_action :verify_authenticity_token # APIリクエストの場合はCSRFチェックをスキップ skip_before_action :verify_authenticity_token, if: :json_request? private def json_request? request.format.json? end end # app/views/forms/_secure_form.html.erb <%= form_with(model: @resource, local: true) do |f| %> <%= csrf_meta_tags %> <!-- フォームの内容 --> <% end %>
3. SQLインジェクション対策
class User < ApplicationRecord # 悪い例 def self.search_unsafe(query) where("name LIKE '%#{query}%'") # SQLインジェクションの危険あり end # 良い例 def self.search_safe(query) where("name LIKE ?", "%#{sanitize_sql_like(query)}%") end # さらに良い例:スコープを使用 scope :search_by_name, ->(query) { where("name ILIKE :query", query: "%#{sanitize_sql_like(query)}%") } end # 配列条件を使用した安全なクエリ def find_users_by_status(statuses) User.where(status: statuses) end
認証・認可の実装ベストプラクティス
1. Deviseを使用した堅牢な認証
# app/models/user.rb class User < ApplicationRecord devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable, :confirmable, :lockable, :timeoutable, :trackable, :jwt_authenticatable, jwt_revocation_strategy: JwtDenylist # パスワードの複雑性要件 validate :password_complexity private def password_complexity return if password.blank? unless password.match?(/^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/) errors.add :password, 'には文字、数字、特殊文字を含める必要があります' end end end # config/initializers/devise.rb Devise.setup do |config| config.password_length = 8..128 config.unlock_strategy = :time config.maximum_attempts = 5 config.unlock_in = 1.hour config.timeout_in = 30.minutes config.remember_for = 2.weeks end
2. Punditを使用した細かな認可制御
# app/policies/application_policy.rb class ApplicationPolicy attr_reader :user, :record def initialize(user, record) @user = user @record = record end def index? false end def show? scope.where(id: record.id).exists? end private def scope Pundit.policy_scope!(user, record.class) end end # app/policies/post_policy.rb class PostPolicy < ApplicationPolicy def update? user.admin? || record.user_id == user.id end def destroy? user.admin? || record.user_id == user.id end class Scope < Scope def resolve if user.admin? scope.all else scope.where(published: true).or(scope.where(user_id: user.id)) end end end end # app/controllers/posts_controller.rb class PostsController < ApplicationController before_action :authenticate_user! after_action :verify_authorized, except: :index after_action :verify_policy_scoped, only: :index def index @posts = policy_scope(Post) end def update @post = Post.find(params[:id]) authorize @post if @post.update(post_params) redirect_to @post, notice: '更新しました' else render :edit end end end
セキュアなAPI開発の手法
1. JWTを使用した認証
# app/controllers/api/v1/base_controller.rb module Api module V1 class BaseController < ApplicationController include JWTAuthentication before_action :authenticate_api_request! private def authenticate_api_request! token = extract_token_from_header payload = decode_jwt_token(token) @current_user = User.find(payload['sub']) rescue JWT::DecodeError, ActiveRecord::RecordNotFound render json: { error: '認証に失敗しました' }, status: :unauthorized end def extract_token_from_header header = request.headers['Authorization'] header&.split(' ')&.last end end end end
2. レート制限の実装
# config/initializers/rack_attack.rb class Rack::Attack # IPベースの制限 throttle('req/ip', limit: 300, period: 5.minutes) do |req| req.ip unless req.path.start_with?('/assets') end # ユーザーベースの制限 throttle('api/ip', limit: 100, period: 1.minute) do |req| if req.path.start_with?('/api/') req.ip end end # ログイン試行の制限 throttle('login/email', limit: 5, period: 20.minutes) do |req| if req.path == '/login' && req.post? req.params['email'].to_s.downcase end end end # 制限超過時のレスポンス設定 Rack::Attack.throttled_response = lambda do |env| now = Time.now match_data = env['rack.attack.match_data'] headers = { 'Content-Type' => 'application/json', 'Retry-After' => (match_data[:period] - (now.to_i % match_data[:period])).to_s } [429, headers, [{ error: 'リクエスト制限を超過しました' }.to_json]] end
3. セキュアなヘッダーの設定
# config/initializers/secure_headers.rb SecureHeaders::Configuration.default do |config| config.x_frame_options = "DENY" config.x_content_type_options = "nosniff" config.x_xss_protection = "1; mode=block" config.x_download_options = "noopen" config.x_permitted_cross_domain_policies = "none" config.referrer_policy = %w(strict-origin-when-cross-origin) config.hsts = { max_age: 2.years.to_i, include_subdomains: true, preload: true } end
これらのセキュリティ対策を実装することで、アプリケーションを様々な脆弱性から保護することができます。定期的なセキュリティ監査と更新を行い、新しい脆弱性に対しても適切に対応することが重要です。
デプロイメントとメンテナンス
本番環境へのデプロイ手順
1. デプロイ準備
# config/environments/production.rb Rails.application.configure do # キャッシュの設定 config.cache_classes = true config.eager_load = true config.cache_store = :redis_cache_store, { url: ENV['REDIS_URL'], pool_size: Integer(ENV.fetch('RAILS_MAX_THREADS', 5)) } # アセットの設定 config.assets.js_compressor = :terser config.assets.css_compressor = :sass config.assets.compile = false # ログの設定 config.log_level = :info config.log_tags = [:request_id] # メール配信の設定 config.action_mailer.perform_caching = false config.action_mailer.delivery_method = :smtp config.action_mailer.smtp_settings = { address: ENV['SMTP_SERVER'], port: ENV['SMTP_PORT'], user_name: ENV['SMTP_USERNAME'], password: ENV['SMTP_PASSWORD'], authentication: 'plain', enable_starttls_auto: true } end
2. Capfileによるデプロイ設定
# Capfile require 'capistrano/setup' require 'capistrano/deploy' require 'capistrano/rbenv' require 'capistrano/bundler' require 'capistrano/rails/assets' require 'capistrano/rails/migrations' require 'capistrano/puma' # config/deploy.rb set :application, 'myapp' set :repo_url, 'git@github.com:username/myapp.git' set :deploy_to, '/var/www/myapp' set :linked_files, %w{config/database.yml config/master.key} set :linked_dirs, %w{log tmp/pids tmp/cache tmp/sockets vendor/bundle} namespace :deploy do desc 'Restart application' task :restart do on roles(:app), in: :sequence, wait: 5 do execute :touch, release_path.join('tmp/restart.txt') end end after :publishing, :restart end
3. Dockerを使用したデプロイ
# Dockerfile FROM ruby:3.2.2 RUN apt-get update -qq && apt-get install -y nodejs postgresql-client WORKDIR /myapp COPY Gemfile /myapp/Gemfile COPY Gemfile.lock /myapp/Gemfile.lock RUN bundle install COPY . /myapp # docker-compose.yml version: '3' services: db: image: postgres:13 volumes: - postgres_data:/var/lib/postgresql/data environment: POSTGRES_PASSWORD: password web: build: . command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/myapp ports: - "3000:3000" depends_on: - db environment: DATABASE_URL: postgres://postgres:password@db:5432/myapp_production volumes: postgres_data:
継続的インテグレーションの構築方法
1. GitHub Actionsの設定
# .github/workflows/ci.yml name: CI on: [push, pull_request] jobs: test: runs-on: ubuntu-latest services: postgres: image: postgres:13 env: POSTGRES_PASSWORD: postgres 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.2 bundler-cache: true - name: Install dependencies run: | bundle install yarn install - name: Setup database env: RAILS_ENV: test POSTGRES_HOST: localhost POSTGRES_PORT: 5432 run: | bundle exec rails db:create bundle exec rails db:schema:load - name: Run tests run: bundle exec rspec - name: Run security checks run: | bundle exec brakeman bundle exec bundle-audit check --update
効率的なデバッグとトラブルシューティング
1. ログ解析とモニタリング
# config/initializers/lograge.rb Rails.application.configure do config.lograge.enabled = true config.lograge.custom_options = lambda do |event| { params: event.payload[:params].except(*%w(controller action format id)), time: Time.current, user_id: event.payload[:user_id], request_id: event.payload[:request_id] } end end # app/controllers/application_controller.rb class ApplicationController < ActionController::Base include LoggingConcern before_action :set_request_details private def set_request_details RequestStore.store[:request_id] = request.uuid RequestStore.store[:user_id] = current_user&.id end end
2. エラーハンドリングとレポーティング
# config/initializers/error_reporting.rb Sentry.init do |config| config.dsn = ENV['SENTRY_DSN'] config.breadcrumbs_logger = [:active_support_logger, :http_logger] config.traces_sample_rate = 0.1 end # app/controllers/application_controller.rb class ApplicationController < ActionController::Base rescue_from StandardError do |exception| Sentry.capture_exception(exception) respond_to do |format| format.html { render 'errors/internal_server_error', status: :internal_server_error } format.json { render json: { error: '内部サーバーエラーが発生しました' }, status: :internal_server_error } end end end
3. パフォーマンス監視
# config/initializers/scout_apm.rb ScoutApm.config do |config| config.name = "MyApp" config.monitor = true end # カスタムメトリクスの追加 class ApplicationController < ActionController::Base before_action :track_request_metrics private def track_request_metrics ScoutApm::Context.add_tag(:user_id, current_user&.id) ScoutApm::Context.add_tag(:request_source, request.headers['X-Request-Source']) end end
4. メンテナンスタスクの自動化
# lib/tasks/maintenance.rake namespace :maintenance do desc "古いセッションデータの削除" task cleanup_sessions: :environment do ActiveRecord::SessionStore::Session.where('updated_at < ?', 2.weeks.ago).delete_all end desc "一時ファイルの削除" task cleanup_temp_files: :environment do TempFile.where('created_at < ?', 1.day.ago).find_each do |file| file.delete_from_storage file.destroy end end desc "バックアップの作成" task create_backup: :environment do timestamp = Time.current.strftime('%Y%m%d_%H%M%S') system "pg_dump -Fc #{Rails.configuration.database_configuration[Rails.env]['database']} > backup_#{timestamp}.dump" system "aws s3 cp backup_#{timestamp}.dump s3://myapp-backups/" end end
これらの設定とツールを適切に組み合わせることで、安定した本番環境の運用とメンテナンスが可能になります。定期的なモニタリングとメンテナンスを行い、問題の早期発見と解決を心がけることが重要です。