railsで多対多のテーブルを実装する
October 16, 2014
ruby
rails
rails4
model
多対多
ER図
migrate
table
テーブル
railsでmodelを作成するとき、多対多の実装方法が分かりづらかったのでまとめてみた。 railsでの多対多の書き方はいろいろあるらしい。
1 → 3の順に情報が多い気がする。手軽さ的には 3 → 1の順かな。
個人的には1がおすすめ。理由としては、中間テーブル名を用いてDBにアクセス出来ることと、中間テーブルに属性を後で付加できるから。
例として画像にtagデータを持たせるテーブルを考える。
Toxi法で多対多のテーブルを作成する。railsの規約に沿ってDB設計をした。ER図は以下の通り。
1の場合
2, 3の場合
images
テーブルにはname
とURL
の情報を保存し、tags
テーブルにはname
の情報を保存させる。tag_id
とimage_id
は外部キー。
違いは中間テーブルの有無だけで、やってることは同じ。実際にはrailsが中間テーブルを自動生成してくれるので、2,3も1のようなテーブルになる。
ではER図を元にrailsで実装してみる。
1. 中間テーブルを作成し、has_many :through
を使った場合
rails g model
コマンドを用いて、テーブルを作成する。
$ rails g model image name:string url:string
$ rails g model tag name:string
$ rails g model image_tag image:references tag:references
テーブル名が単数形であることに注意。railsではActive Record経由で自動的に複数形にしてくれる。
中間テーブルと関連付けさせる。
class Image < ActiveRecord::Base
has_many :image_tags
has_many :tags, :through => :image_tags
end
class Tag < ActiveRecord::Base
has_many :image_tags
has_many :images, :through => :image_tags
end
class ImageTag < ActiveRecord::Base
belongs_to :image
belongs_to :tag
end
ちなみにmigrateファイルは以下のようになっている。
class CreateImages < ActiveRecord::Migration
def change
create_table :images do |t|
t.string :name
t.string :url
t.timestamps
end
end
end
class CreateTags < ActiveRecord::Migration
def change
create_table :tags do |t|
t.string :name
t.timestamps
end
end
end
class CreateImageTags < ActiveRecord::Migration
def change
create_table :image_tags do |t|
t.references :image, index: true, null: false
t.references :tag, index: true, null: false
t.timestamps
end
end
end
外部キーにnot null
制約を付加している。
最後にマイグレートする
$ rake db:migrate
2. has_and_belongs_to_many
を使った場合
rails g model
コマンドを用いて、テーブルを作成する。
$ rails g model image name:string url:string
$ rails g model tag name:string
$ rails g migration create_images_tags image:references tag:references
中間テーブルは作らない。migrationファイルで関連付けさせる。
テーブルを関連付けさせる。
class Image < ActiveRecord::Base
has_and_belongs_to_many :tags
end
class Tag < ActiveRecord::Base
has_and_belongs_to_many :images
end
migrationファイルを編集する
元のファイル
class CreateImagesTags < ActiveRecord::Migration
def change
create_table :images_tags do |t|
t.references :image, index: true
t.references :tag, index: true
end
end
end
これをhas_and_belongs_to_many
の規約で編集する
has_and_belongs_to_many アソシエーションの規約
- 中間テーブルを作成しなければならない。
- 中間テーブルのテーブル名は参照先のテーブル名を辞書順に「_」で連結しなければならない。(※)
- 中間テーブルの主キー列を無効化しなくてはならない。
- 中間テーブルの外部キー列は「参照先のモデル名_id」の形式にしなければならない。
- 中間テーブルのタイムスタンプ列を削除しなくてはならない。
変更後
class CreateImagesTags < ActiveRecord::Migration
def change
create_table :images_tags, id: false do |t|
t.references :image, index: true, null: false
t.references :tag, index: true, null: false
end
end
end
外部キーにnot null
制約を付加している。
変更前にid: false
を追加しただけ。外部キー名を明記しないのは、自動生成してくれるため。
ちなみに他のmigrateファイルは以下のようになっている。
class CreateImages < ActiveRecord::Migration
def change
create_table :images do |t|
t.string :name
t.string :url
t.timestamps
end
end
end
class CreateTags < ActiveRecord::Migration
def change
create_table :tags do |t|
t.string :name
t.timestamps
end
end
end
最後にマイグレートする
$ rake db:migrate
3. create_join_table
を使った場合
rails g model
コマンドを用いて、テーブルを作成する。
$ rails g model image name:string url:string
$ rails g model tag name:string
$ rails g migration create_join_table_images_tags image tag
中間テーブルは作らない。migrationファイルで関連付けさせる。
テーブルを関連付けさせる。
class Image < ActiveRecord::Base
has_and_belongs_to_many :tags
end
class Tag < ActiveRecord::Base
has_and_belongs_to_many :images
end
migrationファイルを編集する
元のファイル
class CreateJoinTableImagesTags < ActiveRecord::Migration
def change
create_join_table :images, :tags do |t|
# t.index [:image_id, :tag_id]
# t.index [:tag_id, :image_id]
end
end
end
コメントアウトを外す。
変更後
class CreateJoinTableImagesTags < ActiveRecord::Migration
def change
create_join_table :images, :tags do |t|
t.index [:image_id, :tag_id]
t.index [:tag_id, :image_id]
end
end
end
ちなみに他のmigrateファイルは以下のようになっている。
class CreateImages < ActiveRecord::Migration
def change
create_table :images do |t|
t.string :name
t.string :url
t.timestamps
end
end
end
class CreateTags < ActiveRecord::Migration
def change
create_table :tags do |t|
t.string :name
t.timestamps
end
end
end
最後にマイグレートする
$ rake db:migrate