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

Yamakichi’s blog

yamakichiの技術ブログ

Rails kaminariにモンキーパッチをあててmetaタグをいい感じにする。

Railsでページネーションを実装するライブラリ、kaminariに対してモンキーパッチを当てました。
人生初のモンキーパッチでもあったのでやったことをまとめておこうと思います。

なにをやるのか?

環境は、

Ruby 2.3.0
Rails 5.0.0
kaminari 0.17.0

今回モンキーパッチを当てたのはRailsでよく用いられるGemのkaminariです。
kaminariに実装してあるrel_next_prev_link_tagsというメソッドの動きを変えて課題を解決しました。

github.com

どうやったのか?

kaminariにはページネーションを実装した際に次ページリンクのmetaタグを簡単に実装してくれるメソッドrel_next_prev_link_tagsがあります。
このメソッドの引数にオブジェクトを渡せば簡単に実装できるため非常に便利です。

    #   Somewhere in body:
    #   <% content_for :head do %>
    #     <%= rel_next_prev_link_tags @items %>
    #   <% end %>
    #
    #   #-> <link rel="next" href="/items/page/3" /><link rel="prev" href="/items/page/1" />
    #

    def rel_next_prev_link_tags(scope, options = {})
      next_page = Kaminari::Helpers::NextPage.new self, options.reverse_merge(:current_page => scope.current_page)
      prev_page = Kaminari::Helpers::PrevPage.new self, options.reverse_merge(:current_page => scope.current_page)

      output = String.new
      output << tag(:link, :rel => "next", :href => next_page.url) if scope.next_page.present?
      output << tag(:link, :rel => "prev", :href => prev_page.url) if scope.prev_page.present?
      output.html_safe
    end
<%= rel_next_prev_link_tags @items %>

<link rel="next" href="/items?page=3" />
<link rel="prev" href="/items?page=1" />

上記のlinkが生成されます。楽チンですね
しかしこのリンクではGoogle検索エンジン側ではあまり良しとはされていないようで、ドメインなども含めたリンクを生成したいという課題にぶつかりました。
rel="next"とrel="prev" の使い方
Google、ページネーション問題を解決するrel=“next”タグとrel=“prev”タグをサポート開始 | 海外SEO情報ブログ

課題

生成されるlinkをドメインも含めたものにしたい。

現状

<link rel="next" href="/items?page=3" />
<link rel="prev" href="/items?page=1" />

これを、

GOAL

<link rel="next" href="http://hoge.js/items?page=3">
<link rel="prev" href="http://hoge.js/itemspage=1">

にしたいと思います。

方法

/config/initializers/kaminari_config.rb

Kaminari.configure do |config|
  config.default_per_page = 15
  # 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
end

module KaminariPatch
  def rel_next_prev_link_tags(scope, options = {})
    url = root_url
    query_hash = build_next_prev_query
    url << "?#{query_hash.to_query}" if query_hash.present?

    output = ""
    output << '<link rel="next" href="' + url + "&page=#{scope.next_page}" + '"/>' if scope.next_page
    output << '<link rel="prev" href="' + url + "&page=#{scope.prev_page}" + '"/>' if scope.prev_page
    output.html_safe
  end
end

::ActionView::Base.prepend(KaminariPatch)

initializers以下にあるkaminari_config.rbはRails起動時に必ず最初に呼び出されるファイルです。
ここに処理を変更するメソッドを定義し、ActionView::Baseクラスにprependして動きを変えます。
上記では、build_next_prev_queryが定義されていますが。
これはシステム側の動きをこのメソッドでまとめて汲み取っているだけなのであまり気にしなくても大丈夫です。
この辺りは各々の環境でいい感じにしてください。

なぜモンキーパッチを当てたのか?

なぜモンキーパッチを当てたのかというそもそもの話をすると、モンキーパッチを当てて課題解決したほうが良い感じに落ち着きそうだったからです。
本来、モンキーパッチ自体はあまり好まれたテクニックではありません。
なぜなら、モンキーパッチをあてることによって本来の動きを変えてしまうことによって起きる弊害があるからです。
この弊害の話は技術書などでも口酸っぱく書かれていたりもします。
はじめは別メソッドに定義して解決することも考えましたが、なるべく無駄なコードは作りたくなかったのと、
kaminari側で処理が存在しているならばその処理に対して修正を加えて利用するほうがシンプルに収まるだろうと考えたからです。

おわり

今回が初のモンキーパッチを当てて課題を解決する経験でとても学びが多かったです。
・別メソッドを定義する考えからモンキーパッチで解決する方向への切り替え
・kaminari本来の動きを追うためのソースコードを読むこと
・PRの意図を探ること
オープンクラス、メソッド探索、メソッドのオーバーライド、prepend、クラス継承などなどメタプログラミングRuby 第2版で学んだ知識が所々に現れてきました
・モンキーパッチの良し悪しを決めるライン
・実際に課題を解決する経験

kaminariのGitHubにちょっと気になるissueがありました。
rel_next_prev_link_tags ignores host option · Issue #496 · amatsuda/kaminari · GitHub

今回参考にした記事

techlife.cookpad.com

engineer.crowdworks.jp

クックパッドさんとクラウドワークスさんのモンキーパッチに関する記事を参考しました。
とても勉強になったので記事を書いてくださった方々には感謝しかないです。ありがとうございます。


これでRubyエンジニアとしてのレベルをまた1つ上げた気がする。がんばろう