新米パパの育児留学

読者です 読者をやめる 読者になる 読者になる

新米パパの育児留学

『育児留学』とは、育児を通して異なる視点を得たり新しいことに挑戦して自己成長に繋げること。育児奮闘中の新米パパが育児を通して得た気づきや感じたこと、育休中に習得したプログラミングに関する話題を発信していきます。

Ruby on Rails いいね(like)機能拡張 (railsチュートリアル)

プログラミング プログラミング-Rails_チュートリアル

目的

Ruby on Rails チュートリアル 5.0(第4版)で実装したサンプルアプリケーション(+検索機能拡張版)を元に、いいね機能を拡張します。

完成版アプリケーション

実際に検索機能を拡張したアプリケーションを見てみたい方はこちら。

https://fathomless-shore-36670.herokuapp.com/about

開発環境

Rails 5.0

・cloud9

実装

最終的なソースコードと確認画面

[View]

[microposts/_micropost.html.erb]

<li id="micropost-<%= micropost.id %>">
  <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
  <span class="user"><%= link_to micropost.user.name, micropost.user %></span>
  <span class="content">
    <%= micropost.content %>
    <%= image_tag micropost.picture.url if micropost.picture? %>    
  </span>
  <span class="timestamp">
    Posted <%= time_ago_in_words(micropost.created_at) %> ago.
    <% if current_user?(micropost.user) %>
      <%= link_to "delete", micropost, method: :delete,data: { confirm: "You sure?" } %>
    <% end %>
  </span>
  <!--like拡張機能-->
  <%= render partial: 'likes/like', locals: { micropost: micropost, likes: @likes } %>
</li>

[likes/_like.html.erb]

<% if micropost.like_user(current_user.id) %>
  <%= button_to micropost_like_path(likes, micropost_id: micropost.id), method: :delete, id: "like-button", remote: true do %> 
    <%= image_tag("icon_red_heart.png") %>
    <span>
      <%= micropost.likes_count %>
    </span>
  <% end %>
<% else %>
  <%= button_to micropost_likes_path(micropost),id: "like-button", remote: true do %>  
    <%= image_tag("icon_heart.png") %>
    <span>
      <%= micropost.likes_count %>
    </span>
  <% end %>
<% end %>

[Controller]

[users_controller.rb]

class UsersController < ApplicationController
(中略)
  def show
    @user = User.find(params[:id])
    # 検索拡張機能として.search(params[:search])を追加    
    @microposts = @user.microposts.paginate(page: params[:page]).search(params[:search])
    # like拡張機能
    @likes = Like.where(micropost_id: params[:micropost_id])
  end
(中略)
end

[static_pages_controller.rb]

class StaticPagesController < ApplicationController
  def home
    if logged_in?
      @micropost  = current_user.microposts.build
      # 検索拡張機能として.search(params[:search])を追加 
      @feed_items = current_user.feed.paginate(page: params[:page]).search(params[:search])
      # like拡張機能
      @likes = Like.where(micropost_id: params[:micropost_id])
    end
  end
(中略)
end

[likes_controller.rb]

class LikesController < ApplicationController
  def create
    @like = Like.create(user_id: current_user.id, micropost_id: params[:micropost_id])
    @likes = Like.where(micropost_id: params[:micropost_id])
  end

  def destroy
    like = Like.find_by(user_id: current_user.id, micropost_id: params[:micropost_id])
    like.destroy
    @likes = Like.where(micropost_id: params[:micropost_id])
  end
end

[Model]

[models/micropost.rb]
class Micropost < ApplicationRecord
  belongs_to :user
  has_many :likes, dependent: :destroy
  default_scope -> { order(created_at: :desc) }
  mount_uploader :picture, PictureUploader
  validates :user_id, presence: true
  validates :content, presence: true, length: { maximum: 140 }
  validate  :picture_size
(中略)
  # like拡張機能
  def like_user(user_id)
    likes.find_by(user_id: user_id)
  end

  private
(中略)
end

[models/like.rb]
class Like < ActiveRecord::Base
  belongs_to :micropost, counter_cache: :likes_count
  belongs_to :user
end

[JS]

[likes/create.js.erb]
$("#like-buttons").html("<%= j(render partial: 'like', locals: { micropost: micropost, likes: @likes, like: @like}) %>");

[likes/destroy.js.erb]
$("#like-buttons").html("<%= j(render partial: 'like', locals: { micropost: micropost, likes: @likes }) %>");

[Routes]

[routes.rb]
Rails.application.routes.draw do
  get 'password_resets/new'

  get 'password_resets/edit'

  root   'static_pages#home'
  get    '/help',    to: 'static_pages#help'
  get    '/about',   to: 'static_pages#about'
  get    '/contact', to: 'static_pages#contact'
  get    '/signup',  to: 'users#new'
  post   '/signup',  to: 'users#create'  
  get    '/login',   to: 'sessions#new'
  post   '/login',   to: 'sessions#create'
  delete '/logout',  to: 'sessions#destroy'
  resources :users do
    member do
      get :following, :followers
    end
  end
  resources :account_activations, only: [:edit]
  resources :password_resets,     only: [:new, :create, :edit, :update]
  resources :relationships,       only: [:create, :destroy]

#like機能拡張用に指定
  resources :microposts do
    resources :likes, only: [:create, :destroy]
  end

end

【Usersページ】 f:id:mochikichi321:20170408065023p:plain

【Homeページ】 f:id:mochikichi321:20170408065030p:plain

実装ステップ

STEP1 現状把握

まず、現時点での[View],[Controller],[Model]及び[Routes]はこのようになっている。

[View]

[microposts/_micropost.html.erb]

<li id="micropost-<%= micropost.id %>">
  <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
  <span class="user"><%= link_to micropost.user.name, micropost.user %></span>
  <span class="content">
    <%= micropost.content %>
    <%= image_tag micropost.picture.url if micropost.picture? %>    
  </span>
  <span class="timestamp">
    Posted <%= time_ago_in_words(micropost.created_at) %> ago.
    <% if current_user?(micropost.user) %>
      <%= link_to "delete", micropost, method: :delete,
                                       data: { confirm: "You sure?" } %>
    <% end %>
  </span>

#ここへlike拡張機能追加予定

</li>

[Controller]

[users_controller.rb]

class UsersController < ApplicationController
(中略)
  def show
    @user = User.find(params[:id])
    # 検索拡張機能として.search(params[:search])を追加    
    @microposts = @user.microposts.paginate(page: params[:page]).search(params[:search])
  end
(中略)
end

[static_pages_controller.rb]

class StaticPagesController < ApplicationController
  def home
    if logged_in?
      @micropost  = current_user.microposts.build
      # 検索拡張機能として.search(params[:search])を追加 
      @feed_items = current_user.feed.paginate(page: params[:page]).search(params[:search])
    end
  end
(中略)
end

[Model]

[models/micropost.rb]

class Micropost < ApplicationRecord
  belongs_to :user
  has_many :likes, dependent: :destroy
  default_scope -> { order(created_at: :desc) }
  mount_uploader :picture, PictureUploader
  validates :user_id, presence: true
  validates :content, presence: true, length: { maximum: 140 }
  validate  :picture_size
(中略)
end

[Routes]

[routes.rb]

Rails.application.routes.draw do
  get 'password_resets/new'

  get 'password_resets/edit'

  root   'static_pages#home'
  get    '/help',    to: 'static_pages#help'
  get    '/about',   to: 'static_pages#about'
  get    '/contact', to: 'static_pages#contact'
  get    '/signup',  to: 'users#new'
  post   '/signup',  to: 'users#create'  
  get    '/login',   to: 'sessions#new'
  post   '/login',   to: 'sessions#create'
  delete '/logout',  to: 'sessions#destroy'
  resources :users do
    member do
      get :following, :followers
    end
  end
  resources :account_activations, only: [:edit]
  resources :password_resets,     only: [:new, :create, :edit, :update]
  resources :microposts,          only: [:create, :destroy]
  resources :relationships,       only: [:create, :destroy]
end

STEP2 likesテーブルを作成

ターミナルでlikesテーブルを作成。カラムはinteger型でuser_idとmicropost_idを追加。

$ rails g model Like user_id:integer micropost_id:integer                                       

作成したLikeモデルに以下を記載。

[models/like.rb]

class Like < ActiveRecord
  belongs_to :micropost, counter_cache: :likes_count
  belongs_to :user
end

counter_cahce: :likes_countは、子モデル(リレーションされているlike)の数を親モデルのカラム(micropostのlikes_count)に保存を意味する。なので、likes_countカラムをMicropostテーブルに追加する。

counter_cahceとは?

STEP3 Micropostテーブルにlikes_countカラムを追加

Micropostテーブルにinteger型のlikes_countというカラムを追加する。

$ rails g migration add_likes_count_to_microposts likes_count:integer

$ rails db:migrate

さらに同モデルファイルに以下を記載する。

[models/micropost.rb]

class Micropost < ApplicationRecord
  belongs_to :user
  has_many :likes, dependent: :destroy
(中略)
  def like_user(user_id)
   likes.find_by(user_id: user_id)
  end

  private
(中略)
end

like_userメソッドは指定のユーザーが既にマイクロポストにいいねしているかを確認するメソッド。

STEP4:ルーティング設定

resourcesを使いmicropostsとlikesの構造を規定。

[routes.rb]

Rails.application.routes.draw do
  get 'password_resets/new'

  get 'password_resets/edit'

  root   'static_pages#home'
  get    '/help',    to: 'static_pages#help'
  get    '/about',   to: 'static_pages#about'
  get    '/contact', to: 'static_pages#contact'
  get    '/signup',  to: 'users#new'
  post   '/signup',  to: 'users#create'  
  get    '/login',   to: 'sessions#new'
  post   '/login',   to: 'sessions#create'
  delete '/logout',  to: 'sessions#destroy'
  resources :users do
    member do
      get :following, :followers
    end
  end
  resources :account_activations, only: [:edit]
  resources :password_resets,     only: [:new, :create, :edit, :update]
#削除
  resources :relationships,       only: [:create, :destroy]

#like機能拡張用に指定
  resources :microposts do
    resources :likes, only: [:create, :destroy]
  end

end

STEP5 likesコントローラーのアクションを定義

likes controllerを作成し、そのファイルに以下を記載する。

[likes_controller.rb]

class LikesController < ApplicationController
  def create
    @like = Like.create(user_id: current_user.id, micropost_id: params[:micropost_id])
    @likes = Like.where(micropost_id: params[:micropost_id])
  end

  def destroy
    like = Like.find_by(user_id: current_user.id, micropost_id: params[:micropost_id])
    like.destroy
    @likes = Like.where(micropost_id: params[:micropost_id])
  end
end

likeを増やしたり消したりするlikeとその後の合計like数を表示するための@likesを定義。

合わせて、usersコントローラーとstatic_pagesコントローラーにも@likesを定義。

[users_controller.rb]

class UsersController < ApplicationController
(中略)
  def show
    @user = User.find(params[:id])
    # 検索拡張機能として.search(params[:search])を追加    
    @microposts = @user.microposts.paginate(page: params[:page]).search(params[:search])
    # like拡張機能
    @likes = Like.where(micropost_id: params[:micropost_id])
  end
(中略)
end

[static_pages_controller.rb]

class StaticPagesController < ApplicationController
  def home
    if logged_in?
      @micropost  = current_user.microposts.build
      # 検索拡張機能として.search(params[:search])を追加 
      @feed_items = current_user.feed.paginate(page: params[:page]).search(params[:search])
      # like拡張機能
      @likes = Like.where(micropost_id: params[:micropost_id])
    end
  end
(中略)
end

STEP6 like部分テンプレートを作る

今回はいいねボタンを部分テンプレートを使って作る。

STEP3で定義したlike_userメソッドを使用し、指定ユーザーがマイクロポストにいいねしているかを判断し、表示を切り分ける。いいねしていたら赤色のハート、いいねしていなければ灰色のハートを表示。

likesのモデルファイルでcounter_cacheの記述があるので、micropost.likes_countとするだけでそのマイクロポストに結びつくlike数が表示される。

どちらのアクションのパスもremote: trueの記述をしてajaxを使いアクションを実行する。

icon_red_heard.pngがいいね後の赤色のハート、icon_heart.pngがいいね前の灰色のハート。画像を準備して"assets/images"フォルダの中にそれぞれ保存をしておく。

[画像サンプル]

f:id:mochikichi321:20170408070249p:plain

f:id:mochikichi321:20170408070253p:plain

[likes/_like.html.erb]

<% if micropost.like_user(current_user.id) %>
  <%= button_to micropost_like_path(likes, micropost_id: micropost.id), method: :delete, id: "like-button", remote: true do %> 
    <%= image_tag("icon_red_heart.png") %>
    <span>
      <%= micropost.likes_count %>
    </span>
  <% end %>
<% else %>
  <%= button_to micropost_likes_path(micropost),id: "like-button", remote: true do %>  
    <%= image_tag("icon_heart.png") %>
    <span>
      <%= micropost.likes_count %>
    </span>
  <% end %>
<% end %>

like部分テンプレートを表示したい箇所に挿入する。

[microposts/_micropost.html.erb]

<li id="micropost-<%= micropost.id %>">
  <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
  <span class="user"><%= link_to micropost.user.name, micropost.user %></span>
  <span class="content">
    <%= micropost.content %>
    <%= image_tag micropost.picture.url if micropost.picture? %>    
  </span>
  <span class="timestamp">
    Posted <%= time_ago_in_words(micropost.created_at) %> ago.
    <% if current_user?(micropost.user) %>
      <%= link_to "delete", micropost, method: :delete,
                                       data: { confirm: "You sure?" } %>
    <% end %>
  </span>
  <!--like拡張機能-->
  <%= render partial: 'likes/like', locals: { micropost: micropost, likes: @likes } %>

</li>

STEP7 JSファイルを作る

ajaxを使ってページ遷移をせずにいいねを増やしたり消したりするjavascriptファイルを作る。

[likes/create.js.erb]

$("#like-buttons").html("<%= j(render partial: 'like', locals: { micropost: micropost, likes: @likes, like: @like}) %>");

[likes/destroy.js.erb]

$("#like-buttons").html("<%= j(render partial: 'like', locals: { micropost: micropost, likes: @likes }) %>");

以上で簡易like機能の実装が完了。

参考

http://qiita.com/YuitoSato/items/94913d6a349a530b2ea2

プログラミング未経験から2か月でエンジニアへ転職したノウハウを知りたい方の相談も受け付けております。

関連記事

mochikichi.hatenablog.com