SinatraのPagination
Pagination
以前DBのレコード数をカウントしてそこからページャーを生成する、と書いていたのですが、実はそれを実現してくれるGemがあるとのこと。
それに気づいたのはブログ本文で改行する方法を調べていたときのこと。
下記のページで「will_paginate」がRailsの標準的なモジュールになっていると知り、早速置き換えてみました。
Gemfile
Gemfileには以下を追加しました。
gem 'will_paginate'
app.rb
require 'will_paginate' require 'will_paginate/active_record' require 'will_paginate/view_helpers/sinatra' class MainApp < Sinatra::Base # viewでwill_pagenateを使用するのに必要. helpers WillPaginate::Sinatra::Helpers get '/' do @pager = Post.paginate(:page => params[:page], :per_page => 5) slim :blog end
- コメントでも書いていますが、「helpers WillPaginate::Sinatra::Helpers」の記述がないとviews/blog.slimでページャーを表示しようとするとundefinedと怒られます。
- 「@pager〜」の行で、postsテーブルの全検索件数からページャーを作成します。1ページに表示するデータの量は、第二引数の[:per_page]で指定します。
blog.slim
main == will_paginate @pager, :previous_label=>'<前へ', :next_label=>'次へ>'
- ページャーのみ表示するサンプルです。
- will_paginateの第一引数に、app.rbでDB(ActiveRecord)から取得したデータを渡すことでページャーが表示されます(後の引数はオプション)。
- [:previous_label]はページ数の前に表示するリンク、[:next_label]はページ数の後に表示するリンクの文字列を指定します。デフォルトでは[← Previous]、[Next →]と表示されます。
データの取得数とオフセット
「will_paginate」を使用すると、戻り値の取得に使用していたlimitとoffsetも自動で適用してくれるようです。
そのため、記事データを取得するコードが結構シンプルになりました。
before
app.rb
get '/' do intPageOffsetNum = getPageOffset(params[:page]) aryPosts = Post.all.order(post_id: 'desc').limit(5).offset(intPageOffsetNum) @intCount = aryPosts.count - 1 @aryPostUrl = [] @aryPostTitle = [] aryPosts.each do |post| @aryPostUrl << '/article/' + post.post_id.to_s @aryPostTitle << post.post_title end slim :blog end
after
app.rb
get '/' do aryPosts = Post.order(post_id: 'desc').paginate(:page => params[:page], :per_page => 5) @aryPostUrl = [] @aryPostTitle = [] aryPosts.each do |post| @aryPostUrl << '/article/' + post.post_id.to_s @aryPostTitle << post.post_title end @intCount = @aryPostUrl.size - 1 slim :blog end
- [@intCount]はpaginateを使用すると、countが使用できず(エラー)、sizeを使うと表示するデータ数にかかわらず5になるため、別のデータを使用しています。
言い訳
まぁ俗にいう「車輪の再発明」というやつですが、自分で考えて実装してみるのも勉強になるし楽しいものです(最初の一回だけは)。と言っておきます。
これまで作成したものも、ググってみるとよりスマートに実装できるものもあるかと思いますが、見つけたら置き換えていくようにしようかと。
ActiveRecordでDBの前後のデータを取得する
今度は詳細ページです。開いているページの前・後の記事のタイトルをリンクとして表示してみました。
最初に
今記事のIDは1、2と連続した数値になっているため、表示しているID+1とID-1で良いかな、と思ってみましたが、記事を削除して歯抜けになった場合、また最新の記事で+1が存在しない場合の処理が面倒なのでやめました。
現在の記事より大きいIDのうち最小のもの、小さいIDのうち最大のものをそれぞれ取得することとします。
複雑な検索条件の指定
- 「Xより大きい」のような検索条件を指定するためには、「Model.arel_table」を使うそうです。
- 「Xより大きい」を指定するには「Model.arel_table」に[.gt(比較する値)]を、「Xより小さい」を指定するには[.lt(比較する値)]を指定します。
- 検索結果のうちIDが最小の行を取得するには[.first]を、最大の行を取得するには[.last]を使用します。
以上から、下記のようなコードとなりました。
app.rb
get '/article/:name' do # 記事IDからタイトルなどを検索し、詳細ページに表示する. aryArticle = Post.where(post_id: params[:name]).first @strTitle = aryArticle.post_title @strPost = aryArticle.post @datUpdateDate = aryArticle.updated_at searchCriteria = Post.arel_table # 一つ前の記事のURL、タイトルを取得する. aryPrevious = Post.where(searchCriteria[:post_id].lt(params[:name])).last if aryPrevious == nil @strPreviousUrl = "" @strPreviousTitle = "" else @strPreviousUrl = "/article/" + aryPrevious.post_id.to_s @strPreviousTitle = "< " + aryPrevious.post_title end # 一つ後の記事のURL、タイトルを取得する. aryNext = Post.where(searchCriteria[:post_id].gt(params[:name])).first if aryNext == nil @strNextUrl = "" @strNextTitle = "" else @strNextUrl = "/article/" + aryNext.post_id.to_s @strNextTitle = aryNext.post_title + " >" end ...
- 以前と同じく、[:name]には記事IDが渡されます。
- 該当するデータが存在しない場合は空の文字列を表示します。
記事本文の改行について
DB登録時にブログ記事本文内で改行コードを入れて、それをそのままslimに渡してもHTMLやSlimのタグとして認識されません。
Controllerで改行コードで切り分けて、Pタグなどでくくってやれば実現できるとは思いますが、もう少しスマートなやり方があるような気がしています。
もう一つ、markdownで表示する方法もあるようなのですが、一旦引き続きHTML(Slim)として表示する方法を調べてみたいと思います。
参考
will_paginate
- Railsプロジェクトでwill_paginateを使用する手順 - Qiita
- will_paginateとSinatraでBootstrap版ページネーションをやってみる - 動かざることバグの如し
- will_paginateのオプション設定でpaginationの外観を変更する方法 - memo.yomukaku.net