Bitbucket

自分用の技術メモ

Active Record associations (Active Record の関連付け)

関連付け (associations) とは各モデルのデータを定義して、容易にアクセスするための仕組み。自分でアクセスするコードを書いても良いが、関連付けを行うことで他のモデルへのアクセスを簡単に書くことができる。

1対多

あるキャラクタ (Character) が複数のスキル (Skill) を使える、つまりCharacterは複数のSkillを持ち (has many)、SkillはCharacterに属して (belongs to) いる関係を考える。

ここでモデルは次のように定義する。

app/model/character.rb
1
2
3
4
class Character < ActiveRecord::Base
  attr_accessible :name
  has_many :skills
end
app/model/skill.rb
1
2
3
4
class Skill < ActiveRecord::Base
  attr_accessible :character_id, :name
  belongs_to :character
end

has_manybelongs_to をこのように書くことでActiveRecordの規約に従い、Skill.character で、character_id を通して対応する Character インスタンスにアクセス、Character.skills で、Skillを0個以上持つ Array としてアクセスできる。(規約に従わない命名をした場合、:class_name:foreign_key で明示する必要がある。)

ここでテーブルにはそれぞれ以下のようなデータが格納されているとする。

characters:

id name
1 フェイト・テスタロッサ
2 高町なのは

skills:

id name character_id
1 サンダースマッシャー 1
2 プラズマザンバーブレイカー 1
3 サンダーレイジ 1
4 スターライトブレイカー 2
5 ディバインバスター 2
6 クロスファイアシュート 2

以下のように Character のインスタンスから Skill を、Skill のインスタンスから Character にアクセスすることができる。

1
2
3
4
5
6
7
8
9
10
11
fate = Character.find 1
# => #<Character id: 1, name: "フェイト・テスタロッサ">
fate.skills
# => [#<Skill id: 1, name: "サンダースマッシャー", character_id: 1>,
#     #<Skill id: 2, name: "プラズマザンバーブレイカー", character_id: 1>,
#     #<Skill id: 3, name: "サンダーレイジ", character_id: 1>]

slb = Skill.find 4
# => #<Skill id: 4, name: "スターライトブレイカー", character_id: 2>
slb.character
# => #<Character id: 2, name: "高町なのは">

Character.skills を変更することで、関連付けられた Skill を変更することができる。

1
2
3
4
5
6
7
8
9
10
11
tr = Skill.find 3
# => #<Skill id: 3, name: "サンダーレイジ", character_id: 1>
fate.skills.delete tr

fate = Character.find 1
fate.skills
# => [#<Skill id: 1, name: "サンダースマッシャー", character_id: 1>,
#     #<Skill id: 2, name: "プラズマザンバーブレイカー", character_id: 1>]+

Skill.find 3
# => #<Skill id: 3, name: "サンダーレイジ", character_id: nil>

多対多

ここまでは、あるスキルを使えるキャラクタが1人であるという前提で1対多の関連づけを行っていたが、”クロスファイアシュート” はティアナ・ランスターも使うことができるため、このままでは問題がおきる。(要は設計ミス。) そこで、あるCharacterが複数のSkillを持ち、Skillが複数のCharacterを持つ、多対多の関係を定義することにする。

このような場合、Character と Skill の関係を表す中間モデル (ここでは、CharacterSkillRelation) を作り、間接的に関連づける。なお、has_and_belongs_to_many を利用する方法は非推奨 (deprecated) となっているため取り上げない。

まずは、次のような CharacterSkillRelation モデルを作成する。

app/model/character_skill_relation.rb
1
2
3
4
5
class CharacterSkillRelation < ActiveRecord::Base
  attr_accessible :character_id, :skill_id
  belongs_to :character
  belongs_to :skill
end

Character と Skill を次のように修正する。

app/model/character.rb
1
2
3
4
5
class Character < ActiveRecord::Base
  attr_accessible :name
  has_many :character_skill_relations
  has_many :skills, :through => :character_skill_relations
end
app/model/skill.rb
1
2
3
4
5
class Skill < ActiveRecord::Base
  attr_accessible :name
  has_many :character_skill_relations
  has_many :characters, :through => :character_skill_relations
end

ここで、characters には以下のデータが含まれているとする。

id name
1 フェイト・テスタロッサ
2 高町なのは
3 ティアナ・ランスター

Character から Skill を更新:

1
2
3
4
5
6
7
8
9
10
11
nanoha = Character.find 2
# => #<Character id: 2, name: "高町なのは">
nanoha.skills = Skill.find [4, 5, 6]
# => [#<Skill id: 4, name: "スターライトブレイカー">,
#     #<Skill id: 5, name: "ディバインバスター">,
#     #<Skill id: 6, name: "クロスファイアシュート">]

cfs = Skill.find 6
# => #<Skill id: 6, name: "クロスファイアシュート">
cfs.characters
# => [#<Character id: 2, name: "高町なのは">]

Skill から Character を更新:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
tiana = Character.find 3
tiana.skills
# => []

cfs.characters
# => [#<Character id: 2, name: "高町なのは">]
cfs.characters << tiana
cfs.characters
# => [#<Character id: 2, name: "高町なのは">,
#     #<Character id: 3, name: "ティアナ・ランスター">]

tiana = Character.find 3
tiana.skills
# => [#<Skill id: 6, name: "クロスファイアシュート">]

参考サイト

Comments