はじめに
こんにちは!
見習いエンジニアの 古内 と申します。
今回は以前作成したものに実装した、モデルの自己参照の記事となります。
対象にしているのは、progate などで勉強を終えたが「モデルのアソシエーションがわからない!」といった方々です。
実際に私自身も「基礎的なことはなんとなくわかったけど、モデル同士のやり取りとかその先が全然わからない!」と苦手意識を持っていました。苦手意識はそれに関する知識が乏しいためにできてしまったので、繰り返し学習することで払拭することができると思います(私もこの記事を書きながら復習しました!)。
ここで必要なのはアソシエーションに関する知識です。
アソシエーションとは日本語に訳すと「関連」という意味で、Rails ではモデル同士の関連付けに使われます。
始めはわかりにくいものだと思いますが、使う場面も多いのでこの記事で少しでも理解してもらえたら幸いです。
環境
- Ruby 2.5.1
- Ruby on Rails 5.2.2
- Docker
- Docker Compose
準備
では環境の準備から始めましょう。
私はこちらを参考に環境構築をしました。
簡単に Rails 開発環境の構築ができるので、みなさんも活用してみてください!(こちらを使うときは最後の Docker Compose の起動まで進めてください)
実践
じゃんけんを例にとって説明させてもらいます。
グーはチョキに強く、チョキはパーに強く、パーはグーに強い。
この三竦みの関係をモデルで実装していきたいと思います。
モデルの作成
まずじゃんけんモデルを作成します。
カラムは name のみで OK です。
$ rails g model janken name:string
次に中間テーブルを作成します。
中間テーブルとはモデル間で多対多のアソシエーションをするとき、その間に挟むものです。今回はじゃんけんなので勝つ相手と負ける相手はそれぞれ1つずつだけなのですが、実際はここまで単純なことは滅多にないと思うので、多対多で実装します。
$ rails g model win to_janken_id:integer from_janken_id:integer
どれ ( from_janken_id ) が、どれ ( to_janken_id ) に対して勝てるか、という関係を表しています。
モデルの作成が終わったらマイグレーションを実行しましょう。
$ rails db:migrate
それではモデルの状態を確認してみましょう。
db ディレクトリの中に schema.rb というファイルがあります。
これには現在のモデルの状態が書かれているので、
ActiveRecord::Schema.define(version: 2019_03_16_092939) do
create_table "jankens", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "wins", force: :cascade do |t|
t.integer "to_janken_id"
t.integer "from_janken_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
こうなっていれば成功です。
モデル間のアソシエーション
続いてモデル間のアソシエーションを追加します。
先程作成した Janken モデルと Win モデルにそれぞれ次のように追加してください。
class Janken < ApplicationRecord
has_many :wins, foreign_key: 'to_janken_id' #追加
has_many :wins, foreign_key: 'from_janken_id' #追加
has_many :win, through: :wins, source: :to_janken #追加
end
class Win < ApplicationRecord
belongs_to :to_janken_id, class_name: 'Janken' #追加
belongs_to :from_janken_id, class_name: 'Janken' #追加
end
has_many とは一対多のアソシエーションする際、そのアソシエーション元に定義するものです。他にも has_one というものがありますが、ここでは説明を省かせてもらいます。
1 つ目の has_many は誰の ( wins ) 何を ( to_janken_id ) 関連付けるかを定義しています。
2 つ目も同じですね。
3 つ目は最後の確認の際に必要なものです。
belongs_to は has_many で定義したアソシエーション先に定義するものです。
こちらも has_many と一緒で誰の ( Janken ) 何を ( to_janken_id ) 関連付けるか定義しています。
このように中間テーブルを挟むことで一対多の実装しかできない has_many で多対多を実装することができます。
インスタンスの生成
ではインスタンス ( グー、チョキ、パー ) を生成してみましょう。
まずはコンソールを開き、
$ rails c
インスタンスを生成します。
$ rock = Janken.new(id: 1, name: 'rock')
$ rock.save
$ scissors = Janken.new(id: 2, name: 'scissors')
$ scissors.save
$ paper = Janken.new(id: 3, name: 'paper')
$ paper.save
次にじゃんけんの相性を作ります。
$ rock_win = Win.new(to_janken_id: scissors.id, from_janken_id: rock.id).save
$ scissors_win = Win.new(to_janken_id: paper.id, from_janken_id: scissors.id).save
$ paper_win = Win.new(to_janken_id: rock.id, from_janken_id: paper.id).save
検証
では実際に確認してみましょう。
$ janken_win = Janken.find(1).win
Janken モデルの id が 1 のものは rock ( グー ) なので scissors ( チョキ ) が返ってきます。
#<ActiveRecord::Associations::CollectionProxy [#<Janken id: 2, name: "scissors", created_at: "2019-03-16 15:25:32", updated_at: "2019-03-16 15:25:32">]>
以上で完成です!
最後に
自己参照はユーザー同士のフォローなど、一つのモデルの中でアソシエーションしなければいけないので、わかりくいですが様々なところで役に立つ知識です。
この解説で少しでも理解に近づいてもらえたら幸いです。
参考
RAILS GUIDES - Active Record の関連付けこちらに has_one や has_many 等の詳しい説明がされています。