下準備
フォロー機能を作るまで下準備します。 あくまでフォロー機能を試したいだけなので、デザインはゴミです。
まずは、deviseインストール。pry-railsも一応。
gem 'devise' gem 'pry-rails'
deviseのコマンドをもろもろ。
rails g devise:install rails g devise:views rails g devise user
usersコントローラーを作る。
rails g controller users
とりあえず適当に書く。
class UsersController < ApplicationController before_action :authenticate_user! def index @users = User.all end def show @user = User.find(params[:id]) end end
対応するルーティングとviewを設定。
Rails.application.routes.draw do devise_for :users root 'users#index' resources :users end
index.html.erb
<h1>ユーザー一覧</h1> <% @users.each do |user| %> <%= user.name %> <br> <%= image_tag(user.avatar, class: "avatar_image") %> <%= link_to("#{user.name}のページだよ。", user_path(user.id) ) %> <br> <% end %>
show.html.erb
<%= @user.name %>のページ
プロフィールに画像が欲しいのでcarrierwaveで画像投稿機能をつける。
まずは、usersテーブルに画像用のカラムを追加しよう。 nameも登録させたいから入れておきます。
rails g migration AddColumnToUsers
class AddColumnToUers < ActiveRecord::Migration def change add_column :users, :avatar, :text add_column :users, :name, :string end end
carrierwaveインストール。
gem 'carrierwave'
bundle。
rails g uploader avatar
Userモデル。
mount_uploader :avatar, AvatarUploader
deviseのnew.html.erbにnameとavatarのフィールドを追加。
<div class="field"> <%= f.label :avatar %> <%= f.file_field :avatar %> </div> <div class="field"> <%= f.label :name %> <%= f.text_field :name %> </div>
deviseは初期段階では、nameやavatarは認証に使えないので記述を追加。 application.html.erb
before_action :configure_permitted_parameters, if: :devise_controller? def configure_permitted_parameters devise_parameter_sanitizer.for(:sign_up).push(:name, :avatar) end
フォロー機能
参考記事はこちらです。
フォロー機能の難しい部分は、多対多の関係にもかかわらず、Userモデルが一つだけの点だね。
普通だったら異なるモデルが多対多の関係で結ばれるけど、フォローの場合はユーザー同士だからこんなイメージになる。
これを実現するために、アソシエーションの記述をちょっと工夫しなくちゃいけない。 モデルはUserモデルとRelationshipモデルの二つを使う。アソシエーションを工夫して、両者でhas_many throughの関係を行う。
第12章 ユーザーをフォローする | Rails チュートリアル
railsチュートリアルだけど、目指す関係性はこれです。
relationshipモデル追加。
rails g model relationship
class CreateRelationships < ActiveRecord::Migration def change create_table :relationships do |t| t.integer :follower_id t.integer :following_id t.timestamps end add_index :relationships, :follower_id add_index :relationships, :following_id add_index :relationships, [:follower_id, :following_id], unique: true end end
フォローのみを考える。
まずは、フォローのみを考えましょう。
user.rb
has_many :active_relationships,class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy
relationship.rb
belongs_to :follower, class_name: "User"
こんなんでできる。active_relationshipsってなに?って思いますね。class_nameをつけることによってモデルの指定。follower_idで外部キーを指定。指定することによってそのレコードを特定することができる。そのレコードの名前がactive_relationship。
例としてUser_idが1のユーザーを見て見ます。User_idが1のユーザーは何人フォローしているか。relationshipsテーブルのfollower_idでそのユーザーがフォローしているかどうかは確認できるので今回は2人です。
フォローされることを考える
次にフォローされることを考えます。
さっきはuser_idが1のユーザーがフォローしたので、その結果誰がフォローされたのかを考えましょう。
つまり、user_idが1のユーザーにフォローされるユーザーです。フォローされたユーザーをidが2と3にします。
これはuser_idが1のユーザーがuser_idが2と3のユーザーをフォローしていることを表しています。
user_idが1のユーザーが2,3,4のユーザーをフォローするならこんな感じになる。
モデルに定義を追加する
上記であげた関係性を保つために、モデルに定義を追加しましょう。まずはフォローしているユーザーを特定するための記述です。
has_many :active_relationships,class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy
ユーザーidが1のユーザーがいたとして、そのフォローしている人やフォロワーはrelationshipモデルのfollowing_idとfollower_idを通して行います。follwer_idとなっていて分かりにくいけど、実際にはフォローしているidが入るのでこのようにします。
上記のように記述することにより以下のようなメソッドが使用可能になります。
@user = User.find(1) @user.active_relationships これでユーザーid1がフォローしているユーザーのレコードが全て取ってこれます。
次にフォローされている人を特定するための記述を追加しましょう。
has_many :passive_relationships, class_name: "Relationship", foreign_key: "following_id", dependent: :destroy
ここもfollowing_idとなっていて分かりにくいですが、実際はフォローされているユーザーを表しています。
following_idがフォローするユーザー、follower_idがそのユーザーが、フォローしているユーザーです。 一番上で言えば、1のユーザーが2のユーザーをフォローしています。
上記のように書くことで以下のようなメソッドが使用可能となります。
@user = User.find(1) @user.passive_relationships これでユーザーid1がフォローされている、つまり「フォロワー」のレコードが全て取ってこれます。
今後のことを考えて、user.followingやuser.followersを使えるようにする。
user.rb has_many :following, through: :active_relationships, source: :following has_many :followers, through: :passive_relationships, source: :follower
これは別に書かなくてもactive_relationshipsやpassive_relationshipsを通して同じ値を得ることはできそう。だけど、user.active_relationships.countなんて分かりにくいですね。user.following.countの方がわかりやすいです。
# 現在のユーザーがフォローしてたらtrueを返す def following?(other_user) following.include?(other_user) end
ちょっとこれは後で使うので書いておきます。
コントローラーに記述する
ああ、もう面倒臭くなってきたので、コード記載して終わろう。そうしよう。 UsersControllerはこんな感じ。
class UsersController < ApplicationController before_action :authenticate_user! def index @users = User.all end def show @user = User.find(params[:id]) end def following @user = User.find(params[:id]) @users = @user.following render 'show_follow' end def followers @user = User.find(params[:id]) @users = @user.followers render 'show_follower' end end
Relationshipsコントローラー。
class RelationshipsController < ApplicationController def create Relationship.create(create_params) redirect_to controller: 'users', action: 'index' end def destroy relationship = Relationship.find(params[:id]) relationship.destroy redirect_to controller: 'users', action: 'index' end private def create_params params.permit(:following_id).merge(follower_id: current_user.id) end end
viewの記述
index.html.erb
<h1>ユーザー一覧</h1> <% @users.each do |user| %> <%= user.name %> <br> <%= image_tag(user.avatar, class: "avatar_image") %> <%= link_to("#{user.name}のページだよ。", user_path(user.id) ) %> <br> <% end %>
show.html.erb こいつは部分テンプレートとか使って、もっとわかりやすくした方がいいですね。
<%= @user.name %>のページ <% if user_signed_in? %> <div id="follow_form"> <% if current_user.following?(@user) %> <%= render 'unfollow' %> <% else %> <%= render 'follow' %> <% end %> </div> <% end %> <a href="<%= following_user_path(@user) %>"> <%= @user.following.count %> following </a> <a href="<%= followers_user_path(@user) %>"> <%= @user.followers.count %> followers </a>
show_follow.erb
<% @user.following.each do |user| %> <table> <tr> <td> <%= user.name %> </td> <td><%= link_to '詳細', user_path(user) %></td> </tr> </table> <% end %>
show_follower.erb
<% @user.followers.each do |user| %> <table> <tr> <td> <%= user.name %> </td> <td><%= link_to '詳細', user_path(user) %></td> </tr> </table> <% end %>
_follow_form.erb
<div id="follow_form"> <% if current_user.following?(@user) %> <%= render 'unfollow' %> <% else %> <%= render 'follow' %> <% end %> </div>
_follow.html.erb
<%= form_for(current_user.active_relationships.build) do |f| %> <div><%= hidden_field_tag :following_id, @user.id %></div> <%= f.submit "Follow", class: "btn btn-primary" %> <% end %>
_unfollow.html.erb
<%= form_for(current_user.active_relationships.find_by(following_id: @user.id), html: { method: :delete }) do |f| %> <%= f.submit "Unfollow", class: "btn" %> <% end %>
参考記事はこちらです。