kaminariでページネーションを実装
今回はコンテンツの一覧表示画面なんかでよく見る
これを実装します。
Googleの検索結果表示画面とかでも(デザインは違うけど)使われてるこれですがページネーションと言います。
一覧ページの表示件数が多くなるとこのページネーションか、TwitterやYouTubeなどで使われている無限スクロールか、 どちらかを使うことになると思いますが今回はページネーションを実装しました。
ページネーションのメリットとしては
- コンテンツが何ページあるのか、始まりと終わりを知らせることができる
- 情報量が多くなることによる読み込み速度の低下を避けることができる
などが挙げられると思います。
では実装していきましょう。
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メソッドについて調べてみた
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を挿入
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の中で呼び出す。
これでバリデーションに引っ掛かった項目だけエラーメッセージを表示できる。