突然の自分語り
あれはそこまで遡らないこと去年の8月。
RubyやRailsを学び始めてひと月とちょっとが経ち、色々見よう見まねで作ってはみたものの、未だにいいね機能やフォロー、フォロワー機能などの仕組みがわからず。
というか、よくよく考えてみるとどういう仕組みでSNSを使っているユーザーが投稿していて、その投稿とユーザーが結びついているのがわかっていない、という状況にありました。いや、今から考えるとどんだけわかってなかったんや。
こういったモデル同士、つまりユーザーやその投稿、そして投稿につけられる"いいね"やユーザーに対するフォローなどの関連付けの事をアソシエーションと言うのですが、そのような概念がプログラミング初心者のみなさんにはとても難しく感じるのではないか??いや、きっとそうに違いない!というスーパーお節介な動機からこのような記事を執筆するに至りました。そしたらバカみたいに長くなってしまいました。これちゃんと最後までやってくれる人いるかな・・・・・。
正直、アソシエーションやリレーションの記事などは、SQLから学ぶのなどもう既にたくさん出ています。
ですが、意外とテキストベースの解説が多く個人的には初心者がとっつきにくいような気がしており、なるべく図などを使ってわかりやすく説明できたらなと思っております。噛み砕きすぎている点についてはツッコミください。
では、よろしくお願いします!!!
目次
目次 |
---|
アソシエーションとは |
データベース設計とは |
UserとTweetの実装(1対多) |
Favoriteの実装(多対多) |
Follow機能の実装(自己結合多対多) |
完成版のソースコード |
普段やってること
普段は機械学習でこういうの予測してます。(HMMで予測しましたが最終的にLSTMになりました)
AIによる宅建士試験出題予測『未来問』無料プレゼント
バンドもやってます。HPはプログラミング初めて3ヶ月ぐらいの時に自分で作った物です(Railsで作ってCMSっぽくしてあります。)
2018年のサマソニ出演しました!!!!!CDも全法人で全国流通してます!!!!!!
ravenknee official HP
この記事を読むにあたっての前提
・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モデル一つのみ実装されていました。
Image may be NSFW.
Clik here to view.
ですが、そのサイトをたまたま見つけてしまったBさんが、勝手に自分の投稿をし始めてしまいました。
Image may be NSFW.
Clik here to view.
これではどれが誰の投稿かわからなくなってしまう、とAさんはブログにユーザーログイン機能を付け足しました。これでアプリにはArticleモデルとUserモデルの二つモデルが実装されていることになります。
Image may be NSFW.
Clik here to view.
そして、この時にまさしくどれが誰の投稿なのかを関連付けるものがアソシエーションなのです。
Image may be NSFW.
Clik here to view.
そして、アソシエーションを行う際は、そのモデル同士の親子関係がどのようになっているかがとても大事になってきます。
モデル同士の親子関係とは
図で考えてみましょう。
Aさん(User)は、自分が作ったブログサイトでたくさんの記事(Article)を投稿します。
Image may be NSFW.
Clik here to view.
Bさんも少しは投稿します。
Image may be NSFW.
Clik here to view.
つまり、User一人一人は沢山のArticleを持っている(User has many articles.)、と考えることができます。(この突然出てきた英文は伏線ですよ!!!!)
Image may be NSFW.
Clik here to view.
逆の立場(Article)も考えてみましょう。
ある日投稿された記事(Article)は、Aさん(User)によって書かれました。
Image may be NSFW.
Clik here to view.
次の日投稿された記事はBさんによって書かれました。
他の日に投稿された記事も、それぞれAさん、Bさんどちらかによって書かれた記事です。Aさん、Bさんが共作で書いた記事というのはありえません。
つまり、Articleは誰か一人のUserに所属している(Article belongs to a user.)と考えることができます。
Image may be NSFW.
Clik here to view.
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に近いですね。(実際は違います!)
そして項目を見るとわかると思いますが、エンティティはアプリの要件定義と表裏一体なのです。ここら辺は調べるといくらでも深い記事が出てくるので割愛します。
Image may be NSFW.
Clik here to view.
関連(リレーション)
関連(リレーション)とは、結び付きのあるエンティティ同士をリンクさせるものです。先ほどの例だとUserとArticle(ツイッターでいうとUserとTweet)が簡単な関連となります。
ここで、「あれ、関連付けって、アソシエーションっていうじゃないの?」と思う方もいらっしゃると思いますが、アソシエーションとリレーションは同じものと思って大丈夫です。
Image may be NSFW.
Clik here to view.
属性(プロパティ)
属性とは、あるエンティティ(Model)に従属する項目のことで、エンティティを1つに定めたときに、一緒に分かる情報だと思ってください。例えば、あるユーザーを指定したら、そのユーザーのemailやアカウント名などがこれにあたります。テーブルのカラムと同じですね。
Image may be NSFW.
Clik here to view.
関連の多重度
関連のあるエンティティAとBについて、片方から他方を見たときに相手が1つなのか、複数なのかということを明らかにすることです。
先ほどの例にすると、Userから見るとArticleは複数、Articleから見るとUserは1つということになります。
すなわち、1つのAからBを見たときおよび1つのBからAを見たときの相手の数を明らかにすることが、多重度を決定するということになります。
Image may be NSFW.
Clik here to view.
・・・・はい、ここまでがデータベースの設計についての基本の概念であり、その中の関連(リレーション)、関連の多重度がまさにアソシエーションにあたります。
アソシエーションとデータベース設計が密接に関わっていること、お分りいただけたでしょうか。
早速難しい話からスタートしてしまいましたが、最初はわからないのは当たり前です。
実際の開発では、設計を完璧に仕上げることがほとんどの場合においてマストですが、とりあえず今回は小さく設計して実装=>また小さく設計&実装という形で一つ一つ確認していきましょう。
では、チュートリアルに入ります!
(ありがちだけど)twitterクローンを作ってみよう
Rails tutorialやその他シラバスなどでおなじみの、twitterクローンでアソシエーションを学んでいきます。
今回学びたいのはアソシエーションの部分のみなので、見栄えやその他細かい機能は必要最低限で行こうと思います。ご了承ください。
それではまずは設計です。設計の時によく使われるのが、ER図というものです。
ER図とは
データベース設計(データモデリング)で使う設計手法です。お絵かきの方法です。
ER図は、データベースが必要なWEBサイトやシステムの設計では必ずと言ってよいほど作成されます。逆に言うと、ER図なしにはデータベースを構築できません。データベース設計の基本中の基本と言える設計手法です。
様々な記法があったりするのですがここではIE記法というものを採用します。(とても簡単です)
IE記法
データベース設計のうち、関連(リレーション)、関連の多重度の二つを決めるものです。要するにアソシエーションを決める記号となります。
上記のデータベース設計で出て来た画像も、ER図であり、IE記法でかかれています。
以下三つの記号から成り立っています。
Image may be NSFW.
Clik here to view.
今回はこれのうち鳥の足(多)を使ってツイッタークローンをシンプルに設計します。
ログイン機能とツイート機能をER図を使って設計しよう
目次 |
---|
アソシエーションとは |
データベース設計とは |
UserとTweetの実装(1対多) ←今ココ |
Favoriteの実装(多対多) |
Follow機能の実装(自己結合多対多) |
完成版のソースコード |
(設計図の作成には Cacooというのを使用していますが、皆さんは手書きで問題ありません。)
最初に言った通り、まずは小さく設計しましょう。UserとTweetについて決めて行きます。
上述の通りエンティティ(Model)はUserとTweetの二つになります。
次にプロパティです。
Userはdeviseというgemを使う予定なので、デフォルトのカラムであるemailとpassword(deviseではencrypted_password
という名前になっています。)を使って行きましょう。
Tweetは本文(body)のみで大丈夫でしょう。
とりあえずここまでを図に落とし込んでみましょう。
Image may be NSFW.
Clik here to view.
次にアソシエーション部分である、関連と関連の多重度についてです。
UserとTweetは最初に出した例であるUserとArticleの関係と同じであり、一つのUserはTweetを複数持っています。
つまり、UserとTweetは一対多(1:N)の関係というわけです。
なので、ER図はこのようになります。
Image may be NSFW.
Clik here to view.
ただ、ここで注意するべきことがあります。このままだとTweetがどのUserに所属しているのかという情報がありません。
それを設定するために、foreign_keyを設定する必要があります。
foreign_keyとは
まず、データには一つ一つ識別するためのidがあります。これをPrimary Key(主キー)と言い、Railsではidというカラム名でテーブル作成時に標準搭載されています。
そして、foreign keyというのはその親のid(Primary key)を保存するカラムというわけです。
これによりどの親に所属しているのか?というのを識別することができます。
Image may be NSFW.
Clik here to view.
(赤丸のuser_id
がforeign_key
)
なのでプロパティにforeign_keyを追加しましょう。親のモデル名_idという名前とするとRailsの命名規則により色々便利になるので、user_idとしましょう。
ER図はこのようになります。
Image may be NSFW.
Clik here to view.
これで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
以下のように表示されればOKです。
Image may be NSFW.
Clik here to view.
そしてルーティングの設定です。以下のように記述してください。
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に代入されているのがわかると思います。
Image may be NSFW.
Clik here to view.
アソシエーションしているデータの受け取り方
ここまででアソシエーションした状態でデータ保存をすることができました。
次は「ユーザーがツイートしたデータ」や、「このツイートをしたユーザー」などといった表示の方法です。
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
このようになっていればうまくいっています。
Image may be NSFW.
Clik here to view.
この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機能の実装(自己結合多対多) |
完成版のソースコード |
さて、次はお気に入り機能の実装です。ここまででめちゃめちゃ長くなってしまいましたが、気合いで頑張っていきましょう!
お気に入り機能とはどういう機能なのか?
お気に入りとは、ユーザーがツイートに対してつける印だとみなすことができます。
また片方ずつ考えていきましょう。
ユーザーは「たくさんのツイートをお気に入り」することができます。
Image may be NSFW.
Clik here to view.
逆も考えてみます。
「ツイートはたくさんのユーザーにお気に入り」されます。
Image may be NSFW.
Clik here to view.
このように「ユーザーもツイートもたくさん持っている」関係を多対多(M:N)の関係といいます。
多対多(M:N)を設計しよう
それではER図に落とし込んでいきたいのですが、Railsでは多対多の関係をUserモデル、Tweetモデルのみでは実装することができません。
実装するためには、中間テーブルというものが必要です。
中間テーブルとは
多対多をプログラムで実装するためには、お互いがお互いのforeign_key
を知らなくてはなりません。
Image may be NSFW.
Clik here to view.
ですが「複数のuserによってどんどんファボが増えていった」場合、画像のように配列にどんどん追加していくか、カラムをtweetsテーブルにどんどん追加しなくてはならず、非常に面倒です。
そのような状態を避けるために、お互いのidだけを保存するテーブルが必要となります。
それを、中間テーブルといいます。
これで「どうやってお気に入り数や、誰のお気に入りなのかを判断するの?」と思うかもしれませんが、それは中間テーブルに保存されるtweet_id
とuser_id
で判断しています。
Image may be NSFW.
Clik here to view.
例えば、「あるツイートについたお気に入り数」をみたいときは、そのtweet_id
と同じレコードに保存されているuser_id
の数を見ればよく、
「あるユーザーのお気に入りしたツイート」がみたいときは、そのuser_id
と同じレコードに保存されているtweet_id
から、ツイートを検索すれば良いことになります。
中間テーブルを使ってER図の作成
それでは中間テーブル込みのER図を作っていきましょう。
通常、中間テーブルはテーブル1_テーブル2の複数系
(user_tweets
)などとつけることが多いですが、今回中間テーブルの意味が「お気に入り登録」とはっきりわかっているため、favorites
テーブルとしてしまいましょう。
Image may be NSFW.
Clik here to view.
中間テーブルはたくさん持たれる側(どちらにもbelongs_to)なのに注意してください。
それでは、これを実装していきましょう!
設計の通りに下準備
設計の通り、Favorite関連のコントローラ、モデル、テーブルを作成します。
$rails g model Favorite user_id:integer tweet_id:integer
$rails g controller favorites create destroy
$rails db:migrate
Image may be NSFW.
Clik here to view.
あとはルーティングですが、後々のことを考えて、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)
が省略された形で生成されているのがわかると思います。
Image may be NSFW.
Clik here to view.
これで下準備は完了です。
アソシエーションの記述
中間テーブル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
のところをきちんと確認しましょう。
Image may be NSFW.
Clik here to view.
あとはコントローラです。
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
コメント機能も作ってみよう
コメント機能も多対多となります。
Image may be NSFW.
Clik here to view.
ここまでの知識があればきっと実装することができると思うので、是非ともやってみてください!
最後にソースコードを置いておきます。
- ヒント
- ・中間テーブルに文章を入れれるカラムを追加
- ・「一回コメントした人も何度もコメントできるようにするか」はあなたの自由(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機能の実装(自己結合多対多) ←今ココ |
完成版のソースコード |
いよいよラストです!(本当に長かった)最後に、ユーザーフォロー機能を追加していきます。
フォロー、フォロワー機能とはどういう機能なのか?
結論から言うとフォローする側のユーザー、フォローされる側のユーザーというユーザー同士の多対多となります。
Image may be NSFW.
Clik here to view.
とりあえず多対多なので中間テーブルは必要そうです。
しかし、ご存知の通りUserモデルは一つしかなく、今のままだとフォローする側のユーザー、フォローされる側のユーザーがどれだか全くわかりません。
Image may be NSFW.
Clik here to view.
なので、ユーザーモデルをフォローする側、される側にうまく切り分けることがこの実装の鍵となります。
そしてこのような自分自身との結合のことを自己結合と言います。自己結合は多対多だけでなく、一対多でもあり得ます。(雇用者モデルにおいて、管理者:従業員の1対多など)
UserとUserの多対多(M:N)を設計しよう(自己結合)
ユーザーモデルは一旦忘れて考えましょう。
Followingモデル
とFollowerモデル
があったとします。(フォローする方とされる方)
Image may be NSFW.
Clik here to view.
これらはもちろん多対多で関連しているため、中間テーブルが存在します。関連を表現しているため、Relationship
モデルと名付けます。
中間テーブルなので各親テーブルのprimary_key
をforeign_key
として保存したいです。
なので、
following_id
にはFollowingモデルのidを
follower_id
にはFollowerモデルのidを
Relationship
のforeign_key
に設定すれば良いと言うことがわかります。
Image may be NSFW.
Clik here to view.
こうすればやることは普通の多対多と同じです。
- Followingから見て、Followerを(Relationshipを介して)集める
- Followerから見て、Followingを(Relationshipを介して)集める
と言うことになります。
Image may be NSFW.
Clik here to view.
ここでふた通りのRelationshipを考えなくてはならないのは、プログラムで記述するときにわかります。
これをUserモデルに直して考えると、
- フォローする側のUserから見て、フォローされる側のUserを(中間テーブルを介して)集める
- フォローされる側のUserから見て、フォローしてくる側のUserを(中間テーブルを介して)集める
と言うことになります。
Image may be NSFW.
Clik here to view.
ヒジョーーにややこしくなってきましたが、ここまで落とし込めば十分に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)。逆も然りです。
以下の画像は、これまでの自己結合アソシエーションの記述をまとめたものです。
Image may be NSFW.
Clik here to view.
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のビューは作っていなかったので、このタイミングで新規作成してください。
Image may be NSFW.
Clik here to view.
<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なども本当はやりたかったのですが、ここまで説明すれば独学でいけるだろうと思い割愛しました。興味があれば調べてみるのをオススメします。
ではみなさん、素敵なRailsライフをお過ごしくださいませ。ありがとうございました!
(最後に僕のバンドのMVを置いておきます)
https://www.youtube.com/watch?v=6bj2idcHdqk
https://www.youtube.com/watch?v=StMZoXUMPng
https://youtu.be/q_UVKz1P2CI
https://www.youtube.com/watch?v=kPnydGG5cps
ポリモーフィック関連(おまけ)
※コメント機能を実装してある前提で進めます。
いつか書きます。