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

vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

SinatraのPagination

Ruby Sinatra

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

arel_table