RubyやRailsを学び始めてひと月とちょっとが経ち、色々見よう見まねで作ってはみたものの、未だに いいね機能 や フォロー、フォロワー機能 などの仕組みがわからず。
というか、よくよく考えてみるとどういう仕組みでSNSを使っているユーザーが投稿していて、その 投稿とユーザーが結びついているのがわかっていない 、という状況にありました。
こういったモデル同士、つまりユーザーやその投稿、そして投稿につけられる"いいね"やユーザーに対するフォローなどの
関連付け の事を アソシエーション と言うのですが、そのような概念がプログラミング初心者の方にはとても難しく感じるのではないか?という動機からこのような記事を執筆するに至りました。
正直、アソシエーションやリレーションの記事などは、SQLから学ぶのなどもう既にたくさん出ています。
ですが、意外とテキストベースの解説が多く個人的には初心者がとっつきにくいような気がしており、なるべく図などを使ってわかりやすく説明できたらなと思っております(画像はほとんど @mopiemon が作ってくれました🙇♂️感謝)。
目次
目次 |
---|
アソシエーションとは |
データベース設計とは |
UserとTweetの実装(1対多) |
Favoriteの実装(多対多) |
Follow機能の実装(自己結合多対多) |
完成版のソースコード |
この記事を読むにあたっての前提
・CRUDを理解している
・MVCをざっくり理解している
・特に、Model, DataBase, Table周りの意味も簡単に知っている。
もちろん、それ以外の方も是非是非挑戦してみてください。理解なんて後から帳尻合わせていきましょう。
できるようになること
- これが作れるようになる(お粗末ですいません) - https://rails-association-tutorial.herokuapp.com
丁寧すぎるherokuへのデプロイの方法はこちら - 【初心者向け】railsアプリをherokuを使って確実にデプロイする方法【決定版】
- 簡単な設計ができるようになる。
- ユーザー、投稿、イイネ、コメント、フォロー機能のアソシエーションが理解でき、実装できるようになる。
注意してほしいこと
- validationは説明しません(ごめんなさい)
- dependent系のオプションも説明しません(この記事読んだあと調べればすぐわかります。)
- 例外処理もしません
- indexとかも貼りません
本当にアソシエーション周りだけの理解をするための記事です。ご了承ください。
validation, dependent, viewのパーシャル化だけ実装したコードは最後に上げておきます。
アソシエーションとは
目次 |
---|
アソシエーションとは ←今ココ |
データベース設計とは |
UserとTweetの実装(1対多) |
Favoriteの実装(多対多) |
Follow機能の実装(自己結合多対多) |
完成版のソースコード |
https://railsguides.jp/association_basics.html
モデル間の関連付け に使われる概念です。
モデル間の関連付けと言われてもピンとこない人が多いと思うので(自分がそうでした)、図を駆使しながら具体例で考えてみましょう。
プログラミング初心者のAさんがあるブログアプリを作ったとします。そのブログは最初はAさん一人で使うものだったため、記事投稿機能のみがつけられていて、モデルも Articleモデル 一つのみ実装されていました。
ですが、そのサイトをたまたま見つけてしまったBさんが、勝手に自分の投稿をし始めてしまいました。
これでは どれが誰の投稿かわからなくなってしまう 、とAさんはブログに ユーザーログイン機能 を付け足しました。これでアプリには Articleモデル と Userモデル の二つモデルが実装されていることになります。
そして、この時にまさしく どれが誰の投稿なのか を関連付けるものが アソシエーション なのです。
そして、アソシエーションを行う際は、その モデル同士の親子関係 がどのようになっているかがとても大事になってきます。
モデル同士の親子関係とは
図で考えてみましょう。
Aさん(User)は、自分が作ったブログサイトでたくさんの記事(Article)を投稿します。
Bさんも少しは投稿します。
つまり、 User一人一人は沢山のArticleを持っている(User has many articles.) 、と考えることができます。(この突然出てきた英文は伏線ですよ!!!!)
逆の立場(Article)も考えてみましょう。
ある日投稿された記事(Article)は、Aさん(User)によって書かれました。
次の日投稿された記事はBさんによって書かれました。
他の日に投稿された記事も、それぞれAさん、Bさんどちらかによって書かれた記事です。 Aさん、Bさんが共作で書いた記事というのはありえません 。
つまり、 Articleは誰か一人のUserに所属している(Article belongs to a user.) と考えることができます。
UserがいなくてはArticleは生まれないし、Articleは必ず誰か一人のUserから生まれます。そう、 Userが親でArticleが子 となっているわけなのです。これが親子関係です。
このような関係をUserとArticleは 一対多の関係 または 1:Nの関係 といいます。もちろんUserが1でArticleが多です。(これがめちゃめちゃ大事です!!!!!!!!!!)
他にも 一対一(1:1) の関係や 多対多(M:N) の関係などもあります。
一対一は簡単なので割愛します。多対多は少し難しいですが、これからやっていくチュートリアルで登場&解説するので、ご安心ください。
さあ、それではいざアプリ制作に取り掛かっていきたいのですが、その前に データベース設計 を行わなくてはなりません。
そう、 アソシエーションはデータベース設計と密接に関わっております 。
データベース設計とは?(少し難しい話です)
目次 |
---|
アソシエーションとは |
データベース設計とは ←今ココ |
UserとTweetの実装(1対多) |
Favoriteの実装(多対多) |
Follow機能の実装(自己結合多対多) |
完成版のソースコード |
情報システムにおいて、どのような情報をデータベースに格納すべきかを検討し、その「格納すべき情報」を「どのような形で保持するか?」を設計すること です。
一般化した定義のためわかりづらいかもしれませんが、
- 情報システム は Twitterアプリそのもの
-
どのような情報 は
- ユーザー (のemailやpassword)
- tweet (本文やどのユーザーが投稿したのか?など)
- お気に入り など
-
「格納すべき情報」を「どのような形で保持するか?」 というのは
-
情報システムをより細かく考えたデータ
- つまりユーザーでいうと「emailは 文字列型 、その識別idは 整数型 」など
-
情報システムをより細かく考えたデータ
といったアプリの動的な部分の設計のことを言います。データのことをちゃんと詳しく決めよう!!ということです。
そして、データベース設計において大事な概念が4つあります。
- エンティティ
- 属性
- 関連(リレーション)
- 関連の多重度
順番に説明して行きますが、完全に理解しなくて大丈夫ですので軽〜く読んでみてください。
エンティティ
エンティティとは、モデリングの対象となるアプリの中で管理する必要がある情報です。ツイッターでいうとUser, Tweet, Favorite, Followなどがこれにあたります。RailsのModelに近いですね。(実際は違います!)
そして項目を見るとわかると思いますが、 エンティティはアプリの要件定義と表裏一体 なのです。ここら辺は調べるといくらでも深い記事が出てくるので割愛します。
関連(リレーション)
関連(リレーション)とは、結び付きのあるエンティティ同士をリンクさせるものです。先ほどの例だとUserとArticle(ツイッターでいうとUserとTweet)が簡単な関連となります。
ここで、「あれ、関連付けって、アソシエーションっていうじゃないの?」と思う方もいらっしゃると思いますが、アソシエーションとリレーションは同じものと思って大丈夫です。
属性(プロパティ)
属性とは、あるエンティティ(Model)に従属する項目のことで、 エンティティを1つに定めたときに、一緒に分かる情報 だと思ってください。例えば、あるユーザーを指定したら、そのユーザーのemailやアカウント名などがこれにあたります。 テーブルのカラム と同じですね。
関連の多重度
関連のあるエンティティAとBについて、片方から他方を見たときに 相手が1つなのか、複数なのか ということを明らかにすることです。
先ほどの例にすると、 Userから見るとArticleは複数 、 Articleから見るとUserは1つ ということになります。
すなわち、 1つのAからBを見たとき および 1つのBからAを見たとき の相手の数を明らかにすることが、 多重度を決定する ということになります。
・・・・はい、ここまでがデータベースの設計についての基本の概念であり、その中の 関連(リレーション) 、 関連の多重度 がまさにアソシエーションにあたります。
アソシエーションとデータベース設計が密接に関わっていること、お分りいただけたでしょうか。
早速難しい話からスタートしてしまいましたが、最初はわからないのは当たり前です。
実際の開発では、設計を完璧に仕上げることがほとんどの場合においてマストですが、とりあえず今回は小さく設計して実装 -> また小さく設計&実装という形で一つ一つ確認していきましょう。
では、チュートリアルに入ります!
(ありがちだけど)twitterクローンを作ってみよう
Rails tutorialやその他シラバスなどでおなじみの、twitterクローンでアソシエーションを学んでいきます。
今回学びたいのはアソシエーションの部分のみ なので、見栄えやその他細かい機能は必要最低限で行こうと思います。ご了承ください。
それではまずは設計です。設計の時によく使われるのが、 ER図 と いうものです。
ER図とは
データベース設計(データモデリング)で使う設計手法です。 お絵かきの方法 です。
ER図は、データベースが必要なWEBサイトやシステムの設計では必ずと言ってよいほど作成されます。逆に言うと、ER図なしにはデータベースを構築できません。データベース設計の基本中の基本と言える設計手法です。
様々な記法があったりするのですがここでは IE記法 というものを採用します。(とても簡単です)
IE記法
データベース設計のうち、 関連(リレーション)、関連の多重度 の二つを決めるものです。要するに アソシエーションを決める記号 となります。
上記のデータベース設計で出て来た画像も、ER図であり、IE記法でかかれています。
以下三つの記号から成り立っています。
今回はこれのうち 鳥の足(多) を使ってツイッタークローンをシンプルに設計します。
ログイン機能とツイート機能をER図を使って設計しよう
目次 |
---|
アソシエーションとは |
データベース設計とは |
UserとTweetの実装(1対多) ←今ココ |
Favoriteの実装(多対多) |
Follow機能の実装(自己結合多対多) |
完成版のソースコード |
(設計図の作成には Cacooというのを使用していますが、皆さんは手書きで問題ありません。)
最初に言った通り、まずは小さく設計しましょう。UserとTweetについて決めて行きます。
上述の通りエンティティ(Model)はUserとTweetの二つになります。
次にプロパティです。
Userはdeviseというgemを使う予定なので、デフォルトのカラムであるemailとpassword(deviseではencrypted_password
という名前になっています。)を使って行きましょう。
Tweetは本文(body)のみで大丈夫でしょう。
とりあえずここまでを図に落とし込んでみましょう。
次にアソシエーション部分である、 関連 と 関連の多重度 についてです。
UserとTweetは最初に出した例であるUserとArticleの関係と同じであり、 一つのUserはTweetを複数持っています。
つまり、UserとTweetは一対多(1:N)の関係というわけです。
なので、ER図はこのようになります。
ただ、ここで注意するべきことがあります。このままだと TweetがどのUserに所属しているのか という情報がありません。
それを設定するために、foreign_keyを設定する必要があります。
foreign_keyとは
まず、データには一つ一つ識別するためのidがあります。これを Primary Key(主キー) と言い、Railsでは id というカラム名でテーブル作成時に標準搭載されています。
そして、 foreign key というのはその 親のid(Primary key)を保存するカラム というわけです。
これにより どの親に所属しているのか? というのを識別することができます。
(赤丸のuser_id
がforeign_key
)
なのでプロパティにforeign_keyを追加しましょう。 親のモデル名_id という名前とするとRailsの命名規則により色々便利になるので、 user_id としましょう。
ER図はこのようになります。
これでUser,Tweet部分の設計は完了しました!!では実装して行きましょう。
ようやくプログラミング!
設計の通りに下準備
それではアプリを作成し、ER図の通りにUserとTweetについて準備してきましょう。
※1 $ マークはターミナルのコマンドという意味です。実際には打たないでください。
※2 #~~ はコメントアウトです。打たなくて大丈夫です。
$rails new association_tutorial
$cd association_tutorial/
Userはログイン機能をつけるため、deviseというgemを使用します。(deviseの詳しい説明は割愛します。こちらの記事がおすすめです。)
Gemfileに以下の記述をしましょう。(色々書いてあると思いますが消さずに追加してください。)
gem 'devise' # 一番下に追加
ターミナルに戻り、以下のコマンドを打ってください。deviseの機能をインストールし、 その機能を取り込んだ Userモデルを作成します。
$bundle install # gemのダウンロード
$rails g devise:install # deviseの機能のインストール
$rails g devise User # deviseの機能を取り込んだUserモデルの作成
$rails g controller users index show #(deviseの機能ではない)usersコントローラの作成。今回はindexとshowアクションを用意
カラム(プロパティ)は、deviseのデフォルトのカラムがemail
とpassword
となっており、設計と同じなのでこのままで問題ありません。
次にTweetについてです。(foreign_key
であるuser_id
もこのタイミングで追加しています。)
$rails g model Tweet body:text user_id:integer # 要件通りにカラムとその型も一緒に定義。
$rails g controller tweets new index show create
忘れずにテーブルの確定をしましょう。
$rails db:migrate
そしてルーティングの設定です。以下のように記述してください。
Rails.application.routes.draw do
root 'tweets#index' # 追加
# ===========ここはいらないので削除orコメントアウト==========
#get 'tweets/new'
#get 'tweets/index'
#get 'tweets/show'
#get 'tweets/create'
#get 'users/index'
#get 'users/show'
# ==================================================
devise_for :users
resources :tweets # 追加
resources :users # 追加
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
tweets, usersコントローラもサクッと表示部分を書いておきます。
class TweetsController < ApplicationController
before_action :authenticate_user!, except: [:index] # deviseのメソッドで「ログインしていないユーザーをログイン画面に送る」メソッド
def new
@tweet = Tweet.new # 新規投稿用の空のインスタンス
end
def create
# 後で書きます。
end
def index
@tweets = Tweet.all
end
def show
@tweet = Tweet.find(params[:id])
end
end
class UsersController < ApplicationController
before_action :authenticate_user!
def index
@users = User.all
end
def show
@user = User.find(params[:id])
end
end
最後に、レイアウトなどの各ページも作っておきましょう。(フロントがぐちゃぐちゃなのは本当にごめんなさい!パーシャル化はあえてしておりません。)
<!DOCTYPE html>
<html>
<head>
<title>AssociationTutorial</title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>
<body>
<!-- 追加 -->
<% if user_signed_in? %> <!-- deviseのメソッドで、「ログインしているかしていないかでtrue or falseを返す」メソッド -->
<%= link_to "新規投稿", new_tweet_path %>
<%= link_to "ログアウト", destroy_user_session_path, method: :delete %>
<%= link_to "マイページ", user_path(current_user.id) %>
<% else %>
<%= link_to "新規登録", new_user_registration_path %>
<%= link_to "ログイン", new_user_session_path %>
<% end %>
<%= link_to "ツイート一覧", tweets_path %>
<%= link_to "ユーザー一覧", users_path %>
<!-- ここまで -->
<%= yield %>
</body>
</html>
<h1>Tweets#new</h1>
<p>Find me in app/views/tweets/new.html.erb</p>
<%= form_for @tweet do |f| %>
<p>
<%= f.label :body,"ツイート" %>
<%= f.text_field :body %>
</p>
<%= f.submit %>
<% end %>
<h1>Tweets#index</h1>
<p>Find me in app/views/tweets/index.html.erb</p>
<% @tweets.each do |tweet| %>
<hr>
<p><span>ツイート内容: </span><%=link_to tweet.body, tweet_path(tweet.id) %></p>
<% end %>
<h1>Tweets#show</h1>
<p>Find me in app/views/tweets/show.html.erb</p>
<p><span>ツイート内容: </span><%= @tweet.body %></p>
<h1>Users#index</h1>
<p>Find me in app/views/users/index.html.erb</p>
<% @users.each do |user| %>
<hr>
<p><span>email: </span><%=link_to user.email, user_path(user.id) %></p>
<% end %>
<h1>Users#show</h1>
<p>Find me in app/views/users/show.html.erb</p>
<hr>
<p><span>email: </span><%= @user.email %></p>
いよいよアソシエーションを記述しよう
さあ、いよいよアソシエーションの記述になります。ER図で言うと棒線の部分がこれにあたります。
・・・と、いってもRailsは命名規則によってものすごくよしなにやってくれるので、記述することはとても少ないです。
has_many
UserはたくさんのTweetを持っています(User has many tweets.) 。なので、 Userモデル に以下の記述をしてください。
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :tweets # これを追加
end
このように 子の方のモデル名の複数形 を書きます。has_manyの方が 複数形 なことは Railsの命名規則でマスト ですのでお気をつけください。(英文と同じですね!)
belongs_to
Tweetは一つのUserに所属しています(Tweet belongs to a User.) 。
もうおわかりですね。tweetモデルに以下のように記述します。
class Tweet < ApplicationRecord
belongs_to :user # これを追加
end
はい、こちらは 一つ なので 単数形 です。
そして、これだけの記述だけでアソシエーションをすることができています。tweetsテーブルに追加してある foreign_key の user_id も、 モデル名_id としたおかげで勝手に認識してくれています。railsはこういったところがとてもすごいですね。
コントローラの編集
さて、userとtweetをアソシエーションさせた状態で保存させたいです。なので、tweetを保存するタイミング、要するにtweets#create
とストロングパラメータ(tweets_params
)を以下のように記述しましょう。
class TweetsController < ApplicationController
before_action :authenticate_user!, except: [:index] # deviseのメソッドで「ログインしていないユーザーをログイン画面に送る」メソッド
def new
@tweet = Tweet.new # 新規投稿用の空のインスタンス
end
def create
# ============追加================
@tweet = Tweet.new(tweet_params) # フォームから送られてきたデータ(body)をストロングパラメータを経由して@tweetに代入
@tweet.user_id = current_user.id # user_idの情報はフォームからはきていないので、deviseのメソッドを使って「ログインしている自分のid」を代入
@tweet.save
redirect_to tweets_path
# =================================
end
def index
@tweets = Tweet.all
end
def show
@tweet = Tweet.find(params[:id])
end
# ===============追加=============
private
def tweet_params
params.require(:tweet).permit(:body) # tweetモデルのカラムのみを許可
end
# =================================
end
ここで注意したいポイントは、 user_idの情報はフォームから送られていないので、追加で代入してあげないといけない と言うことです。
この場合はdeviseのメソッドであるcurrent_user
で ログイン中のユーザーの情報 が取得できるため(つまりツイートした本人)、current_user.id
を@tweet.user_id
に代入しています。
これで、アソシエーションをした状態でデータの保存をすることができました!
とりあえず何人か新規登録して、投稿してみましょう。
deviseの新規登録画面 - http://localhost:3000/users/sign_up
新規投稿画面 - http://localhost:3000/tweets/new
ログをみても、きちんとuser_idに代入されているのがわかると思います。
アソシエーションしているデータの受け取り方
ここまででアソシエーションした状態でデータ保存をすることができました。
次は「 ユーザーがツイートしたデータ 」や、「 このツイートをしたユーザー 」などといった 表示の方法 です。
Userがtweetしたデータ
結論から言うと、
@user.tweets
(@user
は一つのユーザーのデータです。)
これで「ユーザーに関連したツイート」を取得することができます。
tweets
と複数形になっていることに注意してください。この記述はUserモデルに記述したhas_many :tweets
によって決まっています。
そして、複数形になっていることからもわかりますが、 @user.tweetsは複数のツイートが入った配列 となっております。
これらを用いて、Users
のコントローラとビューを以下のように変更しましょう。
class UsersController < ApplicationController
before_action :authenticate_user!
def index
@users = User.all
end
def show
@user = User.find(params[:id])
# ===============追加==============
@tweets = @user.tweets
# ================================
end
end
<h1>Users#show</h1>
<p>Find me in app/views/users/show.html.erb</p>
<hr>
<p><span>email: </span><%= @user.email %></p>
<!-- 追加 -->
<% @tweets.each do |tweet| %>
<hr>
<p><span>ツイート内容: </span><%=link_to tweet.body, tweet_path(tweet.id) %></p>
<% end %>
http://localhost:3000/users/1
このようになっていればうまくいっています。
このtweetをしたUser
こちらも同じように
@tweet.user
このようにすれば「このツイートをしたユーザー」を取得することができます。
こちらもTweetモデルに記述したbelongs_to :user
より、 単数 にすることで取得します。
そして、こちらは 単数 なので、 取得したデータも一つ のみです。
なので、Tweetsコントローラとビューは以下のようになります。
class TweetsController < ApplicationController
before_action :authenticate_user!, except: [:index] # deviseのメソッドで「ログインしていないユーザーをログイン画面に送る」メソッド
def new
@tweet = Tweet.new # 新規投稿用の空のインスタンス
end
def create
@tweet = Tweet.new(tweet_params) # フォームから送られてきたデータ(body)をストロングパラメータを経由して@tweetに代入
@tweet.user_id = current_user.id # user_idの情報はフォームからはきていないので、deviseのメソッドを使って「ログインしている自分のid」を代入
@tweet.save
redirect_to tweets_path
end
def index
@tweets = Tweet.all
end
def show
@tweet = Tweet.find(params[:id])
# ===============追加==============
@user = @tweet.user
# ================================
end
private
def tweet_params
params.require(:tweet).permit(:body) # tweetモデルのカラムのみを許可
end
end
<h1>Tweets#show</h1>
<p>Find me in app/views/tweets/show.html.erb</p>
<p><span>email: </span><%= @user.email %></p> <!-- 追加 -->
<p><span>ツイート内容: </span><%= @tweet.body %></p>
indexもemailを表示させちゃいましょう。
<h1>Tweets#index</h1>
<p>Find me in app/views/tweets/index.html.erb</p>
<% @tweets.each do |tweet| %>
<hr>
<p><span>email: </span><%=link_to tweet.user.email, user_path(tweet.user.id) %></p> <!-- 追加 -->
<p><span>ツイート内容: </span><%=link_to tweet.body, tweet_path(tweet.id) %></p>
<% end %>
ちょっと見慣れないかもしれないですが、このようにtweet.user.email
など、メソッドチェーンで関連先を一気に取得することも可能です。(あまり長くなりすぎるとプログラムの可読性が悪くなるので注意!)
これで一対多の実装ができました!!!ひとまずお疲れ様です。
一対多まとめ
- 親子関係を考え、 子供に親のid(foreign_key)が入るカラム を追加
- 親側モデルに has_many :子供のモデル名の複数形 、子供側モデルに belongs_to :親のモデル名(単数形) と記述
- 関連したモデルの情報は、
関連元のモデルのインスタンス.関連先のモデル名
で取得可能。関連先のモデル名
は、 関連元のモデルに記述した関連先の名前 が入る。(has_manyなら複数、belongs_toなら単数。)
お気に入り機能をER図を使って設計しよう
目次 |
---|
アソシエーションとは |
データベース設計とは |
UserとTweetの実装(1対多) |
Favoriteの実装(多対多) ←今ココ |
Follow機能の実装(自己結合多対多) |
完成版のソースコード |
さて、次は お気に入り機能 の実装です。ここまででめちゃめちゃ長くなってしまいましたが、気合いで頑張っていきましょう!
お気に入り機能とはどういう機能なのか?
お気に入りとは、 ユーザーがツイートに対してつける印 だとみなすことができます。
また片方ずつ考えていきましょう。
ユーザーは「たくさんのツイートをお気に入り」することができます。
逆も考えてみます。
「ツイートはたくさんのユーザーにお気に入り」されます。
このように 「ユーザーもツイートもたくさん持っている」関係を多対多(M:N)の関係 といいます。
多対多(M:N)を設計しよう
それではER図に落とし込んでいきたいのですが、Railsでは多対多の関係をUserモデル、Tweetモデルのみでは実装することができません。
実装するためには、中間テーブルというものが必要です。
中間テーブルとは
多対多をプログラムで実装するためには、お互いがお互いのforeign_key
を知らなくてはなりません。
ですが「複数のuserによってどんどんファボが増えていった」場合、画像のように配列にどんどん追加していくか、カラムをtweetsテーブルにどんどん追加しなくてはならず、非常に面倒です。
そのような状態を避けるために、 お互いのidだけを保存する テーブルが必要となります。
それを、 中間テーブル といいます。
これで「どうやってお気に入り数や、誰のお気に入りなのかを判断するの?」と思うかもしれませんが、それは 中間テーブルに保存されるtweet_id
とuser_id
で判断 しています。
例えば、「あるツイートについたお気に入り数」をみたいときは、 そのtweet_id
と同じレコードに保存されているuser_id
の数 を見ればよく、
「あるユーザーのお気に入りしたツイート」がみたいときは、 そのuser_id
と同じレコードに保存されているtweet_id
から、ツイートを検索 すれば良いことになります。
中間テーブルを使ってER図の作成
それでは中間テーブル込みのER図を作っていきましょう。
通常、中間テーブルはテーブル1_テーブル2の複数系
(user_tweets
)などとつけることが多いですが、今回中間テーブルの意味が「お気に入り登録」とはっきりわかっているため、favorites
テーブルとしてしまいましょう。
中間テーブルは たくさん持たれる側(どちらにもbelongs_to) なのに注意してください。
それでは、これを実装していきましょう!
設計の通りに下準備
設計の通り、Favorite関連のコントローラ、モデル、テーブルを作成します。
$rails g model Favorite user_id:integer tweet_id:integer
$rails g controller favorites create destroy
$rails db:migrate
あとはルーティングですが、後々のことを考えて、tweets
にネストしたルーティングを作成しましょう。
下記のように do ~ endの中に入れる形で書く ことを、 ネスト(入れ子)する と言います。
Rails.application.routes.draw do
#==================削除orコメントアウト================
# get 'favorites/create'
# get 'favorites/destroy'
#=====================================
root 'tweets#index'
devise_for :users
# ================ここをネスト(入れ子)した形に変更
resources :tweets do
resource :favorites, only: [:create, :destroy]
end
#======================================
resources :users
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
理由としては、 お気に入り時に、foreign_key
の一つであるtweet_id
を取得するのが楽になるから です。(一対多の時もそうでしたが、foreign_key
を取得しないとリレーションができません。)
詳しくはこちら - Railsのルーティングを極める(後編)
また、favoriteの詳細ページは作らない、つまりfavoriteのidは要らず省略したいため、resource
と、単数形のメソッドを利用しています。
rails routes
でルーティングを確認すると、以下のようにfavoritesのcreateとdestroyに:tweet_id
が含まれ、かつ:id(:favorite_id)
が省略された形で生成されているのがわかると思います。
これで下準備は完了です。
###アソシエーションの記述
中間テーブルfavorites
は たくさん持たれる側 なので、このようになります。
class Favorite < ApplicationRecord
belongs_to :user
belongs_to :tweet
end
したがって、User, Tweetモデルも以下のようになります。
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :tweets
has_many :favorites # 追加
end
class Tweet < ApplicationRecord
belongs_to :user
has_many :favorites # 追加
end
これでアソシエーションが組めたので、
@user.favorites
や@tweet.favorites
などで それぞれに関連したお気に入り(実際にはuser,tweetのidの2つ組の情報) が取得できるようになりました。
・・・が、viewを作る前に、一つ注意しなくてはならないことがあります。
お気に入り機能は 登録だけでなく解除 もできなくてはならないことです。
そのため、Tweetモデルに「このツイートをユーザーがファボしているかどうか」を判定するメソッドを用意しましょう。
ユーザーがツイートをお気に入りしたかどうかの判定メソッド
「ツイートがファボしてあるかどうか」を判定したいので、 Tweetモデル に以下のように記述しましょう。
class Tweet < ApplicationRecord
belongs_to :user
has_many :favorites
# 追加
def favorited_by?(user)
favorites.where(user_id: user.id).exists?
end
end
これは、Tweetのインスタンスメソッドなので、ビューなどで
if @tweet.favorited_by?(current_user)
という風に使うことができます。これはつまり
if @tweet.favorites.where(user_id: current_user.id).exists?
ということと同じなので、 アソシエーションの記述である@tweet.favorites
の中に、引数で送られたuser
のidがあるかどうか? ということを判定していることになります。
インスタンスメソッドについてはこちら - Rubyのクラスメソッドとインスタンスメソッドの例
それではお気に入りボタンを作っていきましょう。
お気に入りボタンの実装
tweetsのindexに実装します。先ほど定義したfavorited_by?
を早速使います。
count
メソッドで、 配列の中の要素数 を取得しており、そしてそれがそのまま お気に入りの数 となります。
<h1>Tweets#index</h1>
<p>Find me in app/views/tweets/index.html.erb</p>
<% @tweets.each do |tweet| %>
<hr>
<p><span>email: </span><%=link_to tweet.user.email, user_path(tweet.user.id) %></p>
<p><span>ツイート内容: </span><%=link_to tweet.body, tweet_path(tweet.id) %></p>
<!-- 追加 -->
<% if user_signed_in? %>
<% if tweet.favorited_by?(current_user) %> <!-- ログインしているユーザーがファボしたかどうかで分岐 -->
<p><span>お気に入り解除: </span><%=link_to tweet.favorites.count, tweet_favorites_path(tweet.id), method: :delete %></p>
<% else %>
<p><span>お気に入り登録: </span><%=link_to tweet.favorites.count, tweet_favorites_path(tweet.id), method: :post %></p>
<% end %>
<% else %>
<p><span>お気に入り数: </span><%= tweet.favorites.count %></p>
<% end %>
<!-- ここまで -->
<% end %>
お気に入り登録の時はmethod: :post
お気に入り解除の時はmethod: :delete
にすることに注意してください。rails routes
でVerb
のところをきちんと確認しましょう。
あとはコントローラです。
FavoritesControllerの実装
繰り返しますが URIに:tweet_id が含まれており、tweet_favorites_path(tweet.id)
と引数でtweetのidを送ってあるので、 params[:tweet_id]
とすれば お気に入り登録しようとしているツイートのidが取得できます 。
class FavoritesController < ApplicationController
def create
# こう記述することで、「current_userに関連したFavoriteクラスの新しいインスタンス」が作成可能。
# つまり、favorite.user_id = current_user.idが済んだ状態で生成されている。
# buildはnewと同じ意味で、アソシエーションしながらインスタンスをnewする時に形式的に使われる。
favorite = current_user.favorites.build(tweet_id: params[:tweet_id])
favorite.save
redirect_to tweets_path
end
def destroy
favorite = Favorite.find_by(tweet_id: params[:tweet_id], user_id: current_user.id)
favorite.destroy
redirect_to tweets_path
end
end
ポイントは
current_user.favorites.build(tweet_id: params[:tweet_id])
です。これは、 current_userに関連したFavoriteインスタンスを生成 しています。なので、Favoriteのプロパティはすでに user_id: current_user.id が代入されております。
あとは残りのtweet_id
にviewから送られてきたparams[:tweet_id]
を代入するだけです。(params[:id]
ではないことに注意!ルーティングで生成されるURIを見ればparams[:tweet_id]
であることを確認できます。)
これにより、お気に入り登録&解除を行うことができるようになりました!
http://localhost:3000/tweets
has many through
中間テーブルに保存されている情報は それぞれの親id でしかないので、直接取得しviewに表示しようと思うと、少しコツがいります。
# コントローラ
def show
@user = User.find(params[:id])
@tweets = @user.tweets
# mapメソッドを使いfavoriteをtweetの情報に変換
@favorite_tweets = @user.favorites.map{|favorite| favorite.tweet}
end
end
<!-- ビュー -->
<% @favorite_tweets.each do |tweet| %>
<hr>
<p><span>ファボツイート内容: </span><%=link_to tweet.body, tweet_path(tweet.id) %></p>
<% end %>
このように、mapメソッドでfavoritesの情報をtweetの集まりに変換してあげなくてはいけません。
ですが、has many through
を使えば、 ユーザーがファボしたツイート を直接アソシエーションで取得することができます。
このように実装します。
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :tweets
has_many :favorites
has_many :favorite_tweets, through: :favorites, source: :tweet # 追加
end
source
source
は 「参照元のモデル」 をさすオプションです。
これを指定することで アソシエーションでメソッドチェーンする時の名称を変更 することができます。
本当はhas_many :tweets, through: :favorites
と記述したいのですが、上のhas_many :tweets
と重複してしまうため、favorite_tweets
と名称を変更しています。
has many throughを使った「ユーザーがファボしたツイートの表示」
これで、 @user.favorite_tweets
とやることで、「ユーザーがファボしたツイート」を取得することができるようになりました。
それでは、Usersコントローラ、ビューも以下のように変更してください。
class UsersController < ApplicationController
def index
@users = User.all
end
def show
@user = User.find(params[:id])
@tweets = @user.tweets
@favorite_tweets = @user.favorite_tweets # 追加
end
end
<h1>Users#show</h1>
<p>Find me in app/views/users/show.html.erb</p>
<hr>
<p><span>email: </span><%= @user.email %></p>
<% @tweets.each do |tweet| %>
<hr>
<p><span>ツイート内容: </span><%=link_to tweet.body, tweet_path(tweet.id) %></p>
<% end %>
<!-- 追加 -->
<% @favorite_tweets.each do |tweet| %>
<hr>
<p><span>ファボツイート内容: </span><%=link_to tweet.body, tweet_path(tweet.id) %></p>
<% end %>
これでファボ機能が実装できました、確認してみましょう!!
http://localhost:3000
コメント機能も作ってみよう
コメント機能も多対多となります。
ここまでの知識があればきっと実装することができると思うので、是非ともやってみてください!
最後にソースコードを置いておきます。
- ヒント
- ・中間テーブルに文章を入れれるカラムを追加
- ・「一回コメントした人も何度もコメントできるようにするか」はあなたの自由(favorited_by?の話です)
- ・form_for の引数に渡すインスタンスに注意
ネストしてあるコントローラへのルーティングは、form_forの場合
<%= form_for([@tweet, @comment]) do |f| %>
のように、 配列で二つ([関連元のインスタンス, 関連先のインスタンス]) 渡す必要があります。
多対多まとめ
- Railsでは多対多を実装するために 中間テーブル が必要。
- 中間テーブル側が子供(たくさん持たれる方)となるので、
belongs_to :親1
belongs_to :親2
と書こう. - 親側はもちろん
has_many :中間テーブルの名前の複数形
-
has_many_through
を使うと記述が省略できるので積極的に使おう -
has_many_through
を使うときに指定するsource
は 参照元 のモデル名なのに注意 - 「ファボしてるかしていないか」、という判定のメソッド(今回は
favorited_by?
)を用意することに注意(コメントなど、用意しなくてもいい時もある)
フォロー、フォロワー機能をER図を使って設計しよう
目次 |
---|
アソシエーションとは |
データベース設計とは |
UserとTweetの実装(1対多) |
Favoriteの実装(多対多) |
Follow機能の実装(自己結合多対多) ←今ココ |
完成版のソースコード |
いよいよラストです!(本当に長かった)最後に、ユーザーフォロー機能を追加していきます。
フォロー、フォロワー機能とはどういう機能なのか?
結論から言うと フォローする側のユーザー、フォローされる側のユーザーというユーザー同士の多対多 となります。
とりあえず多対多なので中間テーブルは必要そうです。
しかし、ご存知の通りUserモデルは一つしかなく、今のままだとフォローする側のユーザー、フォローされる側のユーザーがどれだか全くわかりません。
なので、 ユーザーモデルをフォローする側、される側にうまく切り分ける ことがこの実装の鍵となります。
そしてこのような自分自身との結合のことを 自己結合 と言います。自己結合は多対多だけでなく、一対多でもあり得ます。(雇用者モデルにおいて、管理者:従業員の1対多など)
UserとUserの多対多(M:N)を設計しよう(自己結合)
ユーザーモデルは一旦忘れて考えましょう。
Followingモデル
とFollowerモデル
があったとします。(フォローする方とされる方)
これらはもちろん多対多で関連しているため、中間テーブルが存在します。関連を表現しているため、Relationship
モデルと名付けます。
中間テーブルなので各親テーブルのprimary_key
をforeign_key
として保存したいです。
なので、
following_id
にはFollowingモデルのidを
follower_id
にはFollowerモデルのidを
Relationship
のforeign_key
に設定すれば良いと言うことがわかります。
こうすればやることは普通の多対多と同じです。
- Followingから見て、Followerを(Relationshipを介して)集める
- Followerから見て、Followingを(Relationshipを介して)集める
と言うことになります。
ここでふた通りのRelationshipを考えなくてはならないのは、プログラムで記述するときにわかります。これをUserモデルに直して考えると、
- フォローする側のUserから見て、フォローされる側のUserを(中間テーブルを介して)集める
- フォローされる側のUserから見て、フォローしてくる側のUserを(中間テーブルを介して)集める
と言うことになります。
ヒジョーーにややこしくなってきましたが、ここまで落とし込めば十分に Railsで実装可能 です。やっていきましょう!
設計の通りに下準備
ここは今までと同じく、設計通りにコマンドを打ち込むだけです。(中間テーブルの名前はRelationships
としました。)
$rails g model relationship following_id:integer follower_id:integer
$rails g controller relationships create destroy
$rails db:migrate
favorite
の時と同じく、親であるuserのidが欲しいため、users
の中にネストしましょう。
また、フォロー一覧、フォロワー一覧の画面も用意したいため、follows
, followers
と言うアクションへのルーティングを作ります。on member
メソッドを使います。
詳細 - Railsのルーティングを極める(後編)
Rails.application.routes.draw do
#==================削除orコメントアウト================
# get 'relationships/create'
# get 'relationships/destroy'
#=====================================
root 'tweets#index'
devise_for :users
resources :tweets do
resource :favorites, only: [:create, :destroy]
end
# ================ここをネスト(入れ子)した形に変更
resources :users do
resource :relationships, only: [:create, :destroy]
get :follows, on: :member # 追加
get :followers, on: :member # 追加
end
#======================================
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
まだviewやコントローラも触れていませんが、先にアソシエーションの記述を完成させます。
アソシエーションの記述
いよいよアソシエーションの記述です。
中間テーブルRelationship
モデルの設定
とりあえず、Userモデル
と言う呼び方はやめてFollowing
, Follower
と言うモデル名に変更したいです。
そしてそれはclass_name
と言うオプションを使えば実現できます。
class_name
:class_name
- 関連するモデルクラス名を指定。関連名と参照先のクラス名を分けたい場合に使う
ものとなります。
これを使うことにより
belongs_to 変更したい親モデル名, class_name: "元々の親モデル名"
とすることができます( "元々の親モデル名" は 文字列 なのに注意!)。これでFollowモデル
、Followerモデル
を擬似的に作り出すことができそうです。
実際、Relationshipsテーブルのforeign_keyとしてfollowing_id
、follower_id
と名前をつけており、モデル名もFollowing
, Follower
とすれば帳尻が合います。
Relationshipモデルにそれぞれ所属先を定義しちゃいましょう。
class Relationship < ApplicationRecord
belongs_to :following, class_name: "User"
belongs_to :follower, class_name: "User"
end
Userモデルの設定
Relationshipモデル側にFollowing
, Follower
モデルに所属している、と言うことを定義してありますので、それをうまく使えば良さそうです。
ここで、設計の時に決めた自己結合の文句を確認してみましょう。
- フォローする側のUserから見て、フォローされる側のUserを(中間テーブルを介して)集める。
- フォローされる側のUserから見て、フォローしてくる側のUserを(中間テーブルを介して)集める。
です。
ですが、とりあえずこの通りに記述しようとしても、has_many :relationships
をふた通り書かなくてはならないため、名前被りが起きてしまいます。なので、__フォローする側、される側ふた通り__の中間テーブルの名前を再定義しなくては行けなさそうです。
今回は、
- フォローする側のUserからみたRelationshipを
active_relationship
- フォローされる側のUserからみたRelationshipを
passive_relationship
としてみます。
参照元のモデルは先ほどもやったclass_name: "Relationship"
と指定すれば良いだけなので、
has_many :active_relationships, class_name: "Relationship"
has_many :passive_relationships, class_name: "Relationship"
となります。
ですがこれだとまだ、 親モデルの外部キーがなんなのか という情報が足りません。
active_relationship
で言うと「 フォローする側のUserからみた 」と言う情報が足りていない、と言うことになります。
そこで親モデルの外部キー指定するオプションとして、foreign_key
と言うのがあります。
foreign_key
:foreign_key
- 参照先のテーブルの外部キーのカラム名を指定できる
これにそれぞれfollowing_id
, follower_id
、つまり 親のprimary_key
を指定してあげれば、「 フォローする側のUserからみた 」と言う情報も取得することができます。
よって、最終的なUserモデルの最終的なアソシエーションの記述は以下のようになります。
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :tweets
has_many :favorites
has_many :favorite_tweets, through: :favorites, source: :tweet
# ====================自分がフォローしているユーザーとの関連 ===================================
#フォローする側のUserから見て、フォローされる側のUserを(中間テーブルを介して)集める。なので親はfollowing_id(フォローする側)
has_many :active_relationships, class_name: "Relationship", foreign_key: :following_id
# 中間テーブルを介して「follower」モデルのUser(フォローされた側)を集めることを「followings」と定義
has_many :followings, through: :active_relationships, source: :follower
# ========================================================================================
# ====================自分がフォローされるユーザーとの関連 ===================================
#フォローされる側のUserから見て、フォローしてくる側のUserを(中間テーブルを介して)集める。なので親はfollower_id(フォローされる側)
has_many :passive_relationships, class_name: "Relationship", foreign_key: :follower_id
# 中間テーブルを介して「following」モデルのUser(フォローする側)を集めることを「followers」と定義
has_many :followers, through: :passive_relationships, source: :following
# =======================================================================================
def followed_by?(user)
# 今自分(引数のuser)がフォローしようとしているユーザー(レシーバー)がフォローされているユーザー(つまりpassive)の中から、引数に渡されたユーザー(自分)がいるかどうかを調べる
passive_relationships.find_by(following_id: user.id).present?
end
end
先ほどまでの話にプラスして、has many through
で(中間テーブルを介して)followings
の時はfollower
を、followers
の時はfollowing
を集める記述をしています。
もちろんsource
でモデルの参照元を指定しています。
followingsで集めるUserはフォローされる側(follower)。逆も然りです。
以下の画像は、これまでの自己結合アソシエーションの記述をまとめたものです。
favoriteと同じく、 Userがfollow済みかどうか判定 したいため、 followed_by? と言うメソッドも追加しています。
これでUserモデルのアソシエーションの記述も完了です。
残るはcontroller、viewを実装です!
コントローラ、ビューの実装
これで本当にチュートリアルは最後です。頑張っていきましょう!!!
ビューページの実装
(最後にある完成版のソースコードに、もう少し綺麗にしたviewファイルの奴も置いてあるので、もしよろしければそちらも参考ください。)
フォローボタンの実装
<h1>Tweets#index</h1>
<p>Find me in app/views/tweets/index.html.erb</p>
<% @tweets.each do |tweet| %>
<hr>
<p><span>email: </span><%=link_to tweet.user.email, user_path(tweet.user.id) %></p>
<p><span>ツイート内容: </span><%=link_to tweet.body, tweet_path(tweet.id) %></p>
<% if user_signed_in? %>
<% if tweet.favorited_by?(current_user) %> <!-- ログインしているユーザーがファボしたかどうかで分岐 -->
<p><span>お気に入り解除: </span><%=link_to tweet.favorites.count, tweet_favorites_path(tweet.id), method: :delete %></p>
<% else %>
<p><span>お気に入り登録: </span><%=link_to tweet.favorites.count, tweet_favorites_path(tweet.id), method: :post %></p>
<% end %>
<!-- ここを追加 -->
<% if current_user != tweet.user %>
<% if tweet.user.followed_by?(current_user) %>
<p><%=link_to "フォロー済み", user_relationships_path(tweet.user.id), method: :delete %></p>
<% else %>
<p><%=link_to "フォローする", user_relationships_path(tweet.user.id), method: :post %></p>
<% end %>
<% end %>
<!-- ここまで -->
<% else %>
<p><span>お気に入り数: </span><%= tweet.favorites.count %></p>
<% end %>
<% end %>
<h1>Users#index</h1>
<p>Find me in app/views/users/index.html.erb</p>
<% @users.each do |user| %>
<hr>
<p><span>email: </span><%= link_to user.email, user_path(user.id) %></p>
<!-- ここを追加 -->
<% if current_user != user %>
<% if user.followed_by?(current_user) %>
<p><%=link_to "フォロー済み", user_relationships_path(user.id), method: :delete %></p>
<% else %>
<p><%=link_to "フォローする", user_relationships_path(user.id), method: :post %></p>
<% end %>
<% end %>
<!-- ここまで -->
<% end %>
自分をフォローする自体は避けたいので、if current_user != user
で自分をのぞいた人たちのみボタンを表示するような実装にしています。あとはほとんどfavoriteボタンと同じですね。
tweetsのshowページは省略します。理屈がわかっていれば絶対にできるはずです、やってみましょう!
次にコントローラです。
relationshipsコントローラの実装
class RelationshipsController < ApplicationController
def create
follow = current_user.active_relationships.build(follower_id: params[:user_id])
follow.save
redirect_to users_path
end
def destroy
follow = current_user.active_relationships.find_by(follower_id: params[:user_id])
follow.destroy
redirect_to root_path
end
end
favoriteの時と同じくcurrent_user.active_relationships.build
とすることで「following_id: current_user.id
」を代入しながらインスタンスを作成することができます。
最後に、フォロー、フォロワー一覧を実装しましょう。
usersコントローラの実装
class UsersController < ApplicationController
before_action :authenticate_user!
def index
@users = User.all
end
def show
@user = User.find(params[:id])
@tweets = @user.tweets
@favorite_tweets = @user.favorite_tweets
end
# ==============追加================
def follows
user = User.find(params[:id])
@users = user.followings
end
def followers
user = User.find(params[:id])
@users = user.followers
end
# ==============追加================
end
ビューの実装
follows, followersのビューは作っていなかったので、このタイミングで新規作成してください。
<h1>Users#follows</h1>
<p>Find me in app/views/users/follows.html.erb</p>
<% @users.each do |user| %>
<hr>
<p><span>email: </span><%= link_to user.email, user_path(user.id) %></p>
<% if current_user != user %>
<% if user.followed_by?(current_user) %>
<p><%=link_to "フォロー済み", user_relationships_path(user.id), method: :delete %></p>
<% else %>
<p><%=link_to "フォローする", user_relationships_path(user.id), method: :post %></p>
<% end %>
<% end %>
<% end %>
<h1>Users#followers</h1>
<p>Find me in app/views/users/followers.html.erb</p>
<% @users.each do |user| %>
<hr>
<p><span>email: </span><%= link_to user.email, user_path(user.id) %></p>
<% if current_user != user %>
<% if user.followed_by?(current_user) %>
<p><%=link_to "フォロー済み", user_relationships_path(user.id), method: :delete %></p>
<% else %>
<p><%=link_to "フォローする", user_relationships_path(user.id), method: :post %></p>
<% end %>
<% end %>
<% end %>
<h1>Users#show</h1>
<p>Find me in app/views/users/show.html.erb</p>
<hr>
<p><span>email: </span><%= @user.email %></p>
<!-- 追加 -->
<p><%=link_to "フォロー", follows_user_path(@user.id) %></p>
<p><%=link_to "フォロワー", followers_user_path(@user.id) %></p>
<% if current_user != @user %>
<% if @user.followed_by?(current_user) %>
<p><%=link_to "フォロー済み", user_relationships_path(@user.id), method: :delete %></p>
<% else %>
<p><%=link_to "フォローする", user_relationships_path(@user.id), method: :post %></p>
<% end %>
<% end %>
<!--ここまで -->
<% @tweets.each do |tweet| %>
<hr>
<p><span>ツイート内容: </span><%=link_to tweet.body, tweet_path(tweet.id) %></p>
<% end %>
<% @favorite_tweets.each do |tweet| %>
<hr>
<p><span>ファボツイート内容: </span><%=link_to tweet.body, tweet_path(tweet.id) %></p>
<% end %>
以上でフォロー機能の実装は完了です!!本当におつかれさまでした!!!!!!
自己結合多対多まとめ
-
User同士の関連付け なので、名前の重複が色々起きてしまう。そこは
class_name
オプションを使って名称変更しよう。その時のforeign_key
の指定も忘れずに。 - followする側が持つのはfollower達 。つまり foreign_keyはfollowing_id なのに注意(逆も然り)
- あとは普通の多対多とだいたい同じ
完成版のソースコード
目次 |
---|
アソシエーションとは |
データベース設計とは |
UserとTweetの実装(1対多) |
Favoriteの実装(多対多) |
Follow機能の実装(自己結合多対多) |
完成版のソースコード ←今ココ |
そのまま実装 - https://github.com/mormorbump/association_tutorial
リファクタリング済み(ヴァリデーション、パーシャル化済み) - https://github.com/mormorbump/association_tutorial_refactor
heroku - https://rails-association-tutorial.herokuapp.com
丁寧すぎるherokuへのデプロイの方法はこちら - 【初心者向け】railsアプリをherokuを使って確実にデプロイする方法【決定版】
まとめ
・・・・・・・・・・・・・・・・・・・・・・・はい、本当に長くなってしまいましたが、これでアソシエーションの根幹の部分はほぼ全て抑えられたと思います。
アソシエーションだけに絞ったため細かい実装部分は色々足りていません(validationとか)。そこはご了承ください。
dependentオプションやscopeなども本当はやりたかったのですが、割愛しました。興味があれば調べてみるのをオススメします。
ポリモーフィック関連(おまけ)
※コメント機能を実装してある前提で進めます。
いつか書きます。