railsでSTIの使い方

自分のサイトを作ろうとしていて、STI(Single Table Inheritance: 単一継承テーブル)を使ってみました。

STIは一つのテーブルを継承したクラスを作成することができます。

  • modelにtype(string)を入れる
  • modelから継承したクラスを作成する。

簡単な図で表すとこんな感じです。

https://gyazo.com/d98ce740e5e34c2bf210fb8066cd484b

継承しているのでどのモデルでも、カラムは全部使えます。

状況

「アニメ」「スポーツ」など、カテゴリーによる発言者を分けようとしとしました。

  • アニメ→キャラクター
  • スポーツ→選手

こういう状況で何個もモデル(キャラクター・選手)を作るのがめんどくさかったので、STIを行いました。

使い方

発言者をSpeakerモデルとします。

bundle exec rails g model Speaker name:string type:string

app/db/migrate/xxxx..._create_speakers.rb

class CreateSpeakers < ActiveRecord::Migration
  def change
    create_table :speakers do |t|
      t.string :name, null: false ←validationを先に追加(好みで)
      t.string :type

      t.timestamps null: false
    end
  end
end

bundle exec rails g model player --parent speaker

app/models/player.rb

class Player < Speaker
  has_many :sports
end

bundle exec rails g migration Sport body:string

app/db/migrate/xxxx..._create_speakers.rb

class CreateSports < ActiveRecord::Migration
  def change
    create_table :sports do |t|
      t.text :body, null: false ←validationを先に追加(好みで)

      t.timestamps null: false
    end
  end
end

bundle exec rails g model Sport body:string

app/db/migrate/xxxx..._create_speakers.rb

class CreateSports < ActiveRecord::Migration
  def change
    create_table :sports do |t|
      t.text :body, null: false ←validationを先に追加(好みで)

      t.timestamps null: false
    end
  end
end

ID追加

bundle exec rails g migration AddColumnToSport player_id:integer

app/db/migrate/xxxx..._add_column_to_sport.rb

class AddColumnToSport < ActiveRecord::Migration
  def change
    add_column :sports, :player_id, :integer
    add_index :sports, :player_id←indexを先に追加(好みで)
  end
end

これでPlayerモデルとSportモデルの関連づけができました。

irb(main):001:0> Player.create(name: 'ダルビッシュ')
=> #<Player id: 1, name: "ダルビッシュ", type: "Player", created_at: "2016-06-05 06:15:39", updated_at: "2016-06-05 06:15:39">
irb(main):002:0> Sport.create(body: '健康大事', player_id: 1)
=> #<Sport id: 1, body: "健康大事", created_at: "2016-06-05 06:16:07", updated_at: "2016-06-05 06:16:07", player_id: 1>
irb(main):003:0> player = Player.find(1)
=> #<Player id: 1, name: "ダルビッシュ", type: "Player", created_at: "2016-06-05 06:15:39", updated_at: "2016-06-05 06:15:39">
irb(main):004:0> player.sports
=> #<ActiveRecord::Associations::CollectionProxy [#<Sport id: 1, body: "健康大事", created_at: "2016-06-05 06:16:07", updated_at: "2016-06-05 06:16:07", player_id: 1>]>

自分的にはあまりクラス独自のカラムを作りたくないので、そういう場合は、新しくtableを作って分岐さすのがいいのかもしれません。

以上、すごく簡単な使い方になります。