はじめに
RailsアプリケーションでClass継承を使いこなすことで、DRY(Don't Repeat Yourself)な設計が実現できます。本記事では、Railsにおける継承の基本から実践的な使い方まで、具体例を交えて解説します。
基本的な継承の書き方
Rubyでは<演算子を使ってクラスを継承します。
class 子クラス < 親クラス
# 子クラスの実装
end
Railsの主要な継承パターン
1. Model(モデル)の継承
基本的なモデル継承
# app/models/user.rb
class User < ApplicationRecord
# ApplicationRecordを継承することで
# Active Recordの全機能が使える
validates :email, presence: true
end
ApplicationRecordの構造
# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true # 直接インスタンス化されないクラス
# アプリ全体で共通のメソッドを定義
def formatted_created_at
created_at.strftime("%Y年%m月%d日")
end
end
2. Controller(コントローラー)の継承
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_action :set_locale
private
def set_locale
I18n.locale = params[:locale] || I18n.default_locale
end
end
# app/controllers/users_controller.rb
class UsersController < ApplicationController
# ApplicationControllerの機能を全て継承
before_action :authenticate_user! # 追加の認証
def index
@users = User.all
end
end
3. 単一テーブル継承(STI: Single Table Inheritance)
一つのテーブルで複数のモデルクラスを管理する強力な機能です。
マイグレーション
class CreateUsers < ActiveRecord::Migration[7.0]
def change
create_table :users do |t|
t.string :type # STIで必須のカラム
t.string :name
t.string :email
t.integer :permission_level # Admin用
t.string :company_name # Customer用
t.timestamps
end
end
end
モデルの実装
# app/models/user.rb
class User < ApplicationRecord
validates :name, presence: true
validates :email, presence: true, uniqueness: true
def greeting
"こんにちは、#{name}さん"
end
end
# app/models/admin.rb
class Admin < User
validates :permission_level, presence: true
def greeting
"管理者の#{name}です"
end
def can_delete_users?
permission_level > 3
end
end
# app/models/customer.rb
class Customer < User
validates :company_name, presence: true
def greeting
"#{company_name}の#{name}です"
end
def calculate_discount
# 顧客特有の割引計算
end
end
STIの使用例
# 新規作成
admin = Admin.create(name: "田中", email: "admin@example.com", permission_level: 5)
customer = Customer.create(name: "佐藤", email: "customer@example.com", company_name: "ABC商事")
# 検索
User.all # AdminとCustomerも含む全ユーザー
Admin.all # Adminのみ
Customer.all # Customerのみ
# typeカラムは自動で設定される
admin.type # => "Admin"
customer.type # => "Customer"
継承で引き継がれる要素
引き継がれるもの
class Parent < ApplicationRecord
# 1. バリデーション
validates :name, presence: true
# 2. アソシエーション
has_many :posts
belongs_to :company
# 3. スコープ
scope :active, -> { where(active: true) }
# 4. コールバック
before_save :normalize_data
# 5. クラスメソッド
def self.featured
where(featured: true)
end
# 6. インスタンスメソッド
def display_name
name.upcase
end
private
def normalize_data
self.name = name.strip if name
end
end
class Child < Parent
# 上記すべてが使える!
# 追加のバリデーション
validates :age, numericality: { greater_than: 0 }
end
メソッドのオーバーライドとsuper
class Vehicle < ApplicationRecord
def start_engine
update(engine_status: 'running')
"エンジンを始動しました"
end
def stop_engine
update(engine_status: 'stopped')
"エンジンを停止しました"
end
end
class ElectricCar < Vehicle
# 完全にオーバーライド
def start_engine
update(battery_status: 'active', engine_status: 'running')
"モーターを起動しました"
end
# superで親メソッドを呼び出し
def stop_engine
result = super # 親クラスのstop_engineを実行
update(battery_status: 'standby')
result + "(バッテリーをスタンバイモードに)"
end
end
Concernsを使った機能の共有
継承の代わりにモジュールを使う方法です。
# app/models/concerns/timestampable.rb
module Timestampable
extend ActiveSupport::Concern
included do
scope :recent, -> { where('created_at > ?', 1.week.ago) }
scope :old, -> { where('created_at < ?', 1.year.ago) }
end
def age_in_days
((Time.current - created_at) / 1.day).to_i
end
def recently_updated?
updated_at > 1.hour.ago
end
end
# 複数のモデルで利用
class Article < ApplicationRecord
include Timestampable
end
class Comment < ApplicationRecord
include Timestampable
end
実践的なユースケース
ECサイトの商品モデル
# 基底クラス
class Product < ApplicationRecord
validates :name, presence: true
validates :price, numericality: { greater_than_or_equal_to: 0 }
has_many :reviews
has_many :cart_items
scope :available, -> { where('stock > 0') }
def discount_price(rate)
(price * (1 - rate)).round
end
end
# 物理商品
class PhysicalProduct < Product
validates :weight, presence: true
validates :dimensions, presence: true
def shipping_cost
base_cost = 500
weight_cost = weight * 100
base_cost + weight_cost
end
end
# デジタル商品
class DigitalProduct < Product
validates :file_size, presence: true
validates :download_url, presence: true
def shipping_cost
0 # デジタル商品は送料無料
end
def generate_download_link(user)
# ダウンロードリンクの生成ロジック
end
end
# サブスクリプション商品
class SubscriptionProduct < Product
validates :billing_cycle, inclusion: { in: %w[monthly yearly] }
def next_billing_date(from_date = Date.current)
case billing_cycle
when 'monthly'
from_date + 1.month
when 'yearly'
from_date + 1.year
end
end
end
アンチパターンと注意点
1. 深すぎる継承階層
# ❌ 悪い例:深すぎる継承
class ApplicationRecord < ActiveRecord::Base
class User < ApplicationRecord
class Customer < User
class PremiumCustomer < Customer
class VIPCustomer < PremiumCustomer # 5階層は深すぎる!
end
end
end
end
end
# ✅ 良い例:Concernsで機能を分離
module Premium
extend ActiveSupport::Concern
# プレミアム機能
end
module VIP
extend ActiveSupport::Concern
# VIP機能
end
class Customer < User
include Premium # 必要に応じてinclude
include VIP
end
2. 不適切なSTIの使用
# ❌ 悪い例:カラムが大きく異なる場合
class Animal < ApplicationRecord
# type, name, age, fly_speed, swim_depth, run_speed...
# 鳥、魚、哺乳類で必要なカラムが全然違う
end
# ✅ 良い例:別テーブルにする
class Bird < ApplicationRecord
# birds テーブル: name, age, fly_speed, wing_span
end
class Fish < ApplicationRecord
# fishes テーブル: name, age, swim_depth, fin_type
end
ベストプラクティス
1. abstract_classを適切に使う
class BaseModel < ApplicationRecord
self.abstract_class = true # このクラスはテーブルを持たない
# 共通のメソッドやバリデーション
def self.recent(days = 7)
where('created_at > ?', days.days.ago)
end
end
class User < BaseModel
# usersテーブルを使用
end
class Post < BaseModel
# postsテーブルを使用
end
2. 継承とCompositionの使い分け
# 継承が適切:is-a関係
class Admin < User # AdminはUserである
end
# Compositionが適切:has-a関係
class User < ApplicationRecord
has_one :profile # UserはProfileを持つ(継承ではない)
delegate :bio, :avatar, to: :profile
end
まとめ
Railsのクラス継承は、適切に使えば強力なツールです。以下のポイントを押さえておきましょう:
- 基本的な継承:Model、Controller、Viewヘルパーなど、Railsのあらゆる場所で使える
- STI:同じテーブル構造を持つ関連クラスに最適
-
メソッドオーバーライド:
superを活用して親クラスの機能を拡張 - Concerns:横断的な機能は継承よりもモジュールで
- 階層は浅く:3階層程度までに留める
継承を使いこなして、メンテナンスしやすいRailsアプリケーションを作りましょう!