kaminariでページネーションを実装

今回はコンテンツの一覧表示画面なんかでよく見る

Image from Gyazo

これを実装します。

Googleの検索結果表示画面とかでも(デザインは違うけど)使われてるこれですがページネーションと言います。

一覧ページの表示件数が多くなるとこのページネーションか、TwitterYouTubeなどで使われている無限スクロールか、 どちらかを使うことになると思いますが今回はページネーションを実装しました。

ページネーションのメリットとしては

  • コンテンツが何ページあるのか、始まりと終わりを知らせることができる
  • 情報量が多くなることによる読み込み速度の低下を避けることができる

などが挙げられると思います。
では実装していきましょう。

gem kaminari

ページネーションの実装のためkaminariを導入します。
kaminariはページネーションを簡単に実装できるrubyのgemです。

インストール

まずGemfileに

gem 'kaminari'

として bundle installします。

設定ファイルの作成

ターミナルで

$ rails g kaminari:config

としてkaminariの設定ファイルを作成します

config/initializers/kaminari_config.rb

# frozen_string_literal: true

Kaminari.configure do |config|
  # config.default_per_page = 25
  # config.max_per_page = nil
  # config.window = 4
  # config.outer_window = 0
  # config.left = 0
  # config.right = 0
  # config.page_method_name = :page
  # config.param_name = :page
  # config.max_pages = nil
  # config.params_on_first_page = false
end

今回は表示件数を20件にしたかったので1ページあたりの表示件数を設定している

# config.default_per_page = 25

config.default_per_page = 20

とし、一旦サーバーを立ち上げなおします。

デザインを変更する

bootstrap4を当てたいので

$ rails g kaminari:views bootstrap4

を実行。
app/views/kaminariディレクトリが生成され、ページネーションにbootstrapデザインが適用されます。

表示させる

準備が整ったので実際に表示させるためにcontroller、viewを編集します。
掲示板一覧画面でページネーションさせたいので、boards_controller

  def index
    @boards = Board.includes(:user).order(created_at: :desc).page(params[:page])
  end

とします。
pageはkaminariで定義されているメソッドです。

viewファイルのページネーションを表示させたいところに

<%= paginate @boards %>

とします。
これだけでページネーションを実装できました。

最後に

新しいgemを導入すると設定やらなんやらで時間がかかってしまうんですが、kaminariはすんなり導入できました。 ただconfigファイルをほとんど触ってないので、ユーザビリティを考えると色々設定した方が良いと思います。 今回は導入の手順ということで最小限で実装しました。 この記事が参考になれば嬉しいです。
最後まで読んでくださり、ありがとうございました。

resourcesとresource

ルーティングの設定をする際、脳死resourcesを使っていてresourceとの違いがよく分からなかったので2つの違いやどう使い分けるかを調べてみました。

違い

  • resourcesは7つのアクションのルーティングをid付きで生成する
  • resourceはindex以外のルーティングをidなしで生成する

実際にルーティングを確認してみます。

Rails.application.routes.draw do
  resources :users
end

とすると

   Prefix Verb   URI Pattern                 Controller#Action
    users GET    /users(.:format)            users#index
          POST   /users(.:format)            users#create
 new_user GET    /users/new(.:format)        users#new
edit_user GET    /users/:id/edit(.:format)   users#edit
     user GET    /users/:id(.:format)        users#show
          PATCH  /users/:id(.:format)        users#update
          PUT    /users/:id(.:format)        users#update
          DELETE /users/:id(.:format)        users#destroy

こんなルーティング になります。

Rails.application.routes.draw do
  resource :user
end

だと

   Prefix Verb   URI Pattern                 Controller#Action
 new_user GET    /user/new(.:format)         users#new
edit_user GET    /user/edit(.:format)        users#edit
     user GET    /user(.:format)             users#show
          PATCH  /user(.:format)             users#update
          PUT    /user(.:format)             users#update
          DELETE /user(.:format)             users#destroy
          POST   /user(.:format)             users#create

こうなりました。

resourceの方は

  • indexがない
  • idがない

ことがわかります。

使い分け

ではこの2つをどのように使い分ければ良いでしょうか。

resourcesはidが付与されているので、複数あるリソースからidを使って特定のリソースを絞り込む必要がある時に使います。
例えば「投稿」は複数存在し、それぞれ個別のidが必要です。そんな時は複数形のresourcesを使います。

resourceはidが付与されていないので、リソースを絞り込む必要がない時に使います。
例えば「ログインユーザー」は自分から見て常に自分1人であり、そのためindexも要らないし、わざわざidを付与する必要もないため単数形のresourceを使います。

最後に

実際はここまで単純ではなくオプションを付けたりネストしたりすることが多そうですが、ほんとの基本の部分はこんな感じだと思います。
僕自身、まだ使い分けに慣れてませんがRESTfulなルーティングを心掛けたいです。
ご指摘ありましたらコメント頂けると幸いです。

jQueryメソッドについて調べてみた

作成中のCRUDアプリの一部機能をAjax化させました。

Ajax化には

①「form系のヘルパーメソッドにremote: trueを設定して実装する方法」
②「jQueryの$.ajax()などで実装する方法」

の2つがあるそうです。

①は簡単な処理に対応でき、②は複雑な処理になる場合に使うようです。

今回は①の方法でコメント機能をAjax化させたのですが、js.erbファイルに記述するjQueryメソッドについてイマイチ理解できていないのでいくつか見ていきたいと思います。

前提として僕はjQueryについては

JavaScriptのライブラリで、なんかJavaScriptを簡単にしたやつらいしいぞ

程度の知識しかありません。0に等しいです。

ここからは知識0の僕が調べた範囲でできるだけ分かりやすく説明できたらと思います。

基本ルール

まず基本的な構文は

①操作したいHTMLを取得
②取得したHTMLを操作

です。

$("セレクタ").メソッド("パラメータ");

セレクタが①、メソッドが②に相当します。

$("#comment-<%= @comment.id %>").remove();

今回僕が実装した、「投稿についたコメントを削除」する機能を例にすると

$("#comment-<%= @comment.id %>")で削除したいコメントを取得、.remove();で削除してます。

viewはこんな感じ

<tr id='comment-<%= comment.id %>'><%= link_to comment_path(comment),
              method: :delete,
              class: 'js-delete-comment-button',
              data: { confirm: '削除しますか?' },
              remote: true do %>
    <i class="fa fa-trash"></i>
  <% end %>
</tr>

$("#hogehoge)と#を付けることで対応するidを探して取得します。

そして取得してきた要素をremoveで削除しています。

他にも

.html()

指定のhtmlに書き換える

.prepend()

要素の先頭にhtmlを挿入

.append()

要素の末尾にhtmlを挿入

.val()

指定した要素のvalueの値を取得or設定

などいろんなメソッドがあり、適宜使い分けることで実現したい機能を実装できますね。

最後に

前回の内容と少し被ってしまいした。
今までjQueryに触れたことがなく、この構文が何をしているのか分かっていなかったのですが、
今回調べてみて少しだけjQueryと仲良くなれた気がします。
かなり簡単なところだけでしたが、僕と同じような方の助けになればと思います。
間違いや、これおかしいよ、などありましたらコメントいただけると嬉しいです。
最後まで読んでくださりありがとうございます。

Ajax

Ajaxってよく聞くけどどういうものなのか、かなりざっくりした説明になってしまいますが自分なりにまとめてみました。

同期通信と非同期通信

Ajaxを知るためにまずは非同期通信、そして対になる同期通信について知る必要があります。

  • 同期通信

通常の通信方法。
ページをまるごと遷移するためレスポンスが返ってくるまで他の処理はできません。

  • 非同期通信

ページまるごと更新するのではなく、
必要な部分だけ更新するため画面遷移がありません。

Ajaxとは

既存の技術を組み合わせたアプローチで、簡単にいうと、JavaScriptで非同期通信することです。

何ができる?

  • 検索フォームに文字を入力すると候補が表示される。

  • Google Mapsのように、ドラッグで地図の表示範囲をスクロールしたり、縮尺を変えたりできる。

  • SNSなどでいいねボタンを押すと色が付く

もっとたくさんありますがこのような機能を画面遷移することなく使えるようになり、ユーザーにとって使いやすいサービスを提供してくれ、通信量も削減できるためサーバーへの負担も軽くなります。

いいね機能

今回、作成中のアプリケーションのいいね機能をAjax化したので簡単に流れを残しておこうと思います。

link_toの通信方法

link_toメソッドのデフォルトの通信方法は同期通信です。

オプションにremote: trueを渡してAjaxに対応させます。

# like.html.erb

<%= link_to board_likes_path(board), 
            method: :post,
            id: "like-button-#{board.id}",
            remote: true do %>
  <i class='far fa-star'></i>
<% end %>
# unlike.html.erb

<%= link_to board_likes_path(board),
            method: :delete, 
            id: "like-button-#{board.id}",
            remote: true do %>
  <i class='fas fa-star'></i>
<% end %>

これにより、ブラウザからHTML形式ではなくJS形式でリクエストが送られ、

最終的に出力されるファイルがhtml.erbではなくjs.erbになります。

js.erbファイルを作成

呼び出されるのはapp/views/コントローラ名/アクション名.js.erbです。

いいねの追加と解除の機能を付けたいので

  • likes/create.js.erb

  • likes/destroy.js.erb

の2つを作成しましょう。

# create.js.erb

$("#like-button-<%= @board.id %>").replaceWith("<%= j(render('boards/unlike', board: @board)) %>");
# destroy.js.erb

$("#like-button-<%= @board.id %>").replaceWith("<%= j(render('boards/like', board: @board)) %>");

"#like-button-<%= @board.id %>"の部分はlink_toメソッドで指定したidと紐づいています。

replaceWithは要素を置き換えるjQueryのメソッドで、

$(置換対象).replaceWith(置き換え後の要素)となります。

よくわかりませんよね。

今回のcreate.js.erbで言うと、

$("#like-button-<%= @board.id %>")はidで紐づいているlink_toメソッドで囲った要素、
つまり<i class='far fa-star'></i>の部分(font-awesomeの☆マーク)

レンダリングしている部分 render('boards/unlike', board: @board)、
つまりunlike.html.erbのlink_toメソッドの中の<i class='fas fa-star'></i>(★マーク)

に置き換えている。

つまり、画面遷移なしで☆から★に切り替わる。

というわけです。

コントローラを修正

元々同期通信でいいね機能を実装していたので非同期通信に合うようにコントローラも修正します。

# likes_controller.rb

class LikesController < ApplicationController
  before_action :set_board
  def create
    @bookmark = current_user.likes.create(board_id: params[:board_id])
    # redirect_back fallback_location: boards_url ここを削除
  end

  def destroy
    @bookmark = current_user.likes.find_by(board_id: params[:board_id])
    @bookmark.destroy
    # redirect_back fallback_location: boards_url ここを削除
  end

  private

  def set_board
    @board = Board.find(params[:board_id])
  end
end

いいねした後に元のページに戻るよう、redirect_backしていましたが、これがあるとページ遷移してしまうので削除します。

他にも必要な知識はあるでしょうが僕はこれでいいね機能をAjax化できました。

最後に

RailsガイドやいろんなQiita記事、技術ブログを参考になんとかまとめた、という感じなので間違いや足りてない情報があるかもしれません。 この記事も参考程度に見ていただければと思います。
JavaScriptはprogateで1周しただけで全然わかってないので余裕があるときにもう少し触ってみたいですね。 最後まで見てくださり、ありがとうございました。

render

Railsを学習していてrenderを使うことは多々あります。

なんとなく使っていて理解が浅いと感じたので、一度しっかりおさらいして

renderを使いこなせるようにしたい、と思いアウトプットすることに。


renderを使う場面

renderを使う場面は大きく分けて2種類あります。


  • controllerで使用
  • viewで使用


controllerで使用

controllerでは、renderを使うことで指定したviewファイルを呼び出します。

# users_controller

def create
  @user = User.new(user_params)
  if @user.save
    redirect_to login_path
  else
    render :new
  end
end

private

def user_params
  params.require(:user).permit(:name, :email)
end

このコードでは新規ユーザーの作成が成功したときはログインページへ遷移、

失敗したときはusers/new.html.erbを呼び出す、という流れです。

redirect_toは引数で指定したpathをサーバーにリクエストし、レスポンスを表示します。

renderはアクションは経由せず直接users/new.html.erbを表示します。


viewで使用

viewではrenderを使うことで部分テンプレートを呼び出します。

posts/new.html.erbとposts/edit.html.erbのように、新規投稿ページと投稿編集ページがあるとします。

投稿のタイトルと本文を入力するフォームは同じものを使い回せばコード量を削減できますよね。

そんなときに部分テンプレートとしてフォーム部分を別ファイルに書き出してしまって汎用化させます。

部分テンプレート用のファイルは頭にアンダーバー(_)を付けます

# posts/_form.html.erb

<%= form_with model: post, local: true do |f| %>
  <%= f.label :title %>
  <%= f.text_field :title, class: 'form-control' %>

  <%= f.label :body %>
  <%= f.text_area :body, class: 'form-control' %>

  <%= f.submit class: 'btn btn-primary' %>
<% end %>

新規投稿ページで呼び出してみます

呼び出す際はアンダーバーは付けません。

# posts/new.html.erb

<%= '新規投稿' %>
<%= render partial: 'form' %>
# 下の形に省略可
<%= render 'form' %>
# 同じディレクトリにある場合はディレクトリ名を省略できます

部分テンプレート内で変数を使用する場合はlocalsオプションで変数を定義します。

<%= render partial: 'form', locals: { post: @post } %>
# { 部分テンプレート内で使う変数 : 変数に入れる値 }
# 下の形に省略可
<%= render 'form',  post: @post %>

わざわざインスタンス変数をローカル変数に定義し直すのは、パーシャルの中でインスタンス変数を参照してしまうと、パーシャルとコントローラが密結合してしまってパーシャルの再利用性が低くなるからです。

次はcollectionオプションを見てみます。

posts/index.html.erbで全てのpostを表示する次のようなコードがあったとします

<% @post.each do |post| %>
  <%= post.title %>
  <%= post.body %>
<% end %>

タイトルと本文の部分は部分テンプレート化できそうです。

# _post.html.erb

  <%= post.title %>
  <%= post.body %>

これをeachで回すと

<% @post.each do |post| %>
  <%= render 'post', post : @post %>
<% end %>

となります。

これをさらに

<%= render partial: 'post', collection: @posts %>

さらに

<%= render @posts %>

と、ここまで省略できます。


最後に

今までふわっとした理解で使っていましたが、アウトプットを通して少し理解が深まった気がします。

他にもオプションがあるようですが僕が今まで使ってきたもののおさらいなので以上としておきます。

もし間違っている箇所がありましたらコメントいただけますと幸いです。

最後まで読んでくださりありがとうございました。

サイトのタイトルを動的に表示させる

サイトのタイトルを各ページ毎に変えたいときの手順です。

・ヘルパーの作成

・共通レイアウトに埋め込み

・各viewページで呼び出し

の流れでやっていきます。

ヘルパーの作成

module ApplicationHelper
  def page_title(page_title = '')
    base_title = 'hogehoge'
    if page_title.empty?
      base_title
    else
      page_title + ' | ' + base_title
    end
 # page_title.empty? ? base_title : page_title + " | " + base_title
 # 条件式 ? trueの処理 : falseの処理
 #三項演算子で書くとこう。コード量を減らすためよく使われる。
  end
end

共通レイアウトに埋め込み

<html>
  <head>
    <title><%= page_title(yield(:title)) %></title>

各viewページで呼び出し

<% content_for :title, 'fuga' %>

これで各viewページのcontent_forで与えた文字列をpage_titleメソッドに渡して

fuga | hogehoge

というタイトルを付けることができる。

エラーメッセージの表示

フォームに値が入っていない時にエラーメッセージを表示させたい。

エラーメッセージは使い回したいのでパーシャル化しておきます。

<% if object.errors.any? %>   # 特定の変数ではなくobjectとすることで汎用化
  <div class='alert alert-danger'>
    <ul class='mb-0'>
      <% object.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
    </ul>
  </div>
 <% end %>

呼び出したいビューファイルで

<%= form_with model: board, local: true do |f| %>
  <%= render 'shared/error_messages', object: f.object %>   # object: f.objectと指定
  <%= f.label :title %>
  <%= f.text_field :title, class: 'form-control' %>
...
<% end %>

form_withの中で呼び出す。

これでバリデーションに引っ掛かった項目だけエラーメッセージを表示できる。