40歳からのキャリアチェンジ

20代はエンジニア・PM、30代はWeb系エンジニア向けのキャリアアドバイザー。40代の今はフリーランスで開発含めて色々やってます。技術ネタとしてはRuby/RailsとJavaScript関連あたり

第2回GemJam:Kaminari編を行いました

前回やったのが、2014年12月なのでだいぶ久しぶりな感じになったけど、2回目を昨日実施しました。

ちなみに、GemJamは

GemJamについて

何気なく使ってるgemについて各自のレベルに応じて今よりもちょっと深く理解する インプットだけではなくアウトプットもセットで行うことでアウトプットするという習慣を得るきっかけ作り を目的にしたもくもく会形式の勉強会です。

という感じの勉強会です。

ちょっと長いので目次つけておきます

当日のおおまかな流れ

f:id:h5y1m141:20160407065646j:plain

  • 会場に来た順番でこんな↑感じで今日やることをザックリ書いてもらって後はもくもくと作業
  • 時間になったら振り返りをしてもらう

という感じで運営しました。

イベントページでそれぞれのレベルに応じて勉強してもらうようにガイドラインとなるものを書いておいたのですが、Kaminariはそれなりに使ったことがあるのでコードを読む (Kaminariのキモとなるようなページネーション処理の実装がどうなってるのか調べる)を行いました

当日の作業振り返り

コールドリーディングをした時にメモを取りながら作業をしていたので、以下にその記録まとめておきます

ページネーション処理がどうやって実現されてるのかとっかかりとなる箇所を調べる

最初は、spec/配下のファイルをじっくり読もうと思ったのですが、結構な分量だったので、ちょっと方針変換して、2016年4月6日時点でのGitHub上のコミットのページで一番最後のページを見つけて、そこから最初にページネーションの実装をしたと思われるものを履歴から見つけようと思いました。

それで、the first implementation of pagination helperといういかにもそれっぽいのを見つけて、そこの変更履歴で

module Kaminari
  module Helpers
    def paginate(scope, options = {}, &block)
      prev_label, next_label, left, window, right, truncate, style = (options[:prev] || '« Prev'.html_safe), (options[:next] || 'Next »'.html_safe), (options[:left] || 2), (options[:window] || 5), (options[:right] || 2), (options[:truncate] || '...'), (options[:style] || 'page')
      current_page, num_pages = scope.current_page, scope.num_pages
      content_tag :div, :class => 'pagination' do

とあったので、どうも個々の処理が関連ありそうと判断。

上記とは別に手元のRailsのアプリで導入したKaminariのソースコードに対して、ターミナル上でfindで以下のように調べることも行ってみました

find ./vendor/bundle/ruby/2.2.0/gems/kaminari-0.16.3 -type f -name "*.*" | xargs grep 'paginate' -n | more

すると

./vendor/bundle/ruby/2.2.0/gems/kaminari-0.16.3/lib/kaminari/helpers/action_view_extension.rb:17:    def paginate(scope, options = {}, &block)
./vendor/bundle/ruby/2.2.0/gems/kaminari-0.16.3/lib/kaminari/helpers/action_view_extension.rb:75:    # Ported from mislav/will_paginate

という情報があって、action_viewという単語が気になったのでこれがそもそも何かを知らなかったのでそれを調べることにした

ActionViewについて調べた記録

ActionView を単体で使ってみるがとてもわかりやすい情報があったのと、幸いにそこの記事で紹介してた内容のサンプルコードが あったのでそのコードを写経しながら理解を深めることにしました

サンプルコードをベースにして、以下の様なコードを書いてみました

require 'bundler/setup'
Bundler.require(:default)
lookup_context = ActionView::LookupContext.new('./views')
lookup_context.cache = false
view_context = ActionView::Base.new(lookup_context)
view_context.assign(first_name: 'xx')
view_context.assign(last_name: 'xxxxx')
result = view_context.render(template: 'main', 
                             prefixes: 'prefix', 
                             layout: 'layouts/appliation')
puts result

上記はmain.rbとしたので、こんな風にして実行

bundle exec ruby ./main.rb
/Path/to/ruby/use-actionview/vendor/bundle/ruby/2.1.0/gems/actionview-4.1.4/lib/action_view/path_set.rb:46:in `find': Missing template layouts/appliation with {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:erb, :builder, :raw, :ruby, :slim]}. Searched in: (ActionView::MissingTemplate)

あー、Railsのアプリ作ってよく見かけMissing Templateのエラーにここで出くわすとは思わなかった^^;

require 'bundler/setup'
Bundler.require(:default)
lookup_context = ActionView::LookupContext.new('./views')
lookup_context.cache = false
view_context = ActionView::Base.new(lookup_context)
view_context.assign(first_name: 'xxx')
view_context.assign(last_name: 'xxxxx')
view_context.assign(name: 'xxxxxx')
result = view_context.render(template: 'hoge',
                          prefixes: 'prefix',
                          layout: 'layouts/application')
puts result

としたら以下のように問題なく表示されました

--
Hello, xxxxxxxxx
--

で、よくよく見ると、タイプミスをしてました・・

正しくない:layout: 'layouts/appliation' 正しい: layout: 'layouts/application'

ここまででActionViewについてわかったこと

  • ActionViewというgemがあった
  • そこで行われてるのは、インスタンス変数をセットすることが出来る
  • render()メソッドを通じてテンプレートファイルを呼び出すことが出来る
    • テンプレートとなるファイルを探すのはActionView::LookupContextを通じてディレクトリを指定
    • テンプレート自体の参照は、ActionView::Baseのrenderのオプションを指定することで行える感じ
      • template: xxx
      • prefix: これを指定しないとテンプレートが見つけられないらしい
ActionView::Base.new
view_context = ActionView::Base.new(lookup_context)
view_context.assign(first_name: 'ファーストネーム') => @first_name というインスタンス変数にファーストネームという値が格納される

ActionViewがわかった上で、Kaminariのページネーションについて改めて調べる

ここまででおおまかにActionViewの位置づけとか仕組みがわかったので改めてページネーション処理の実装がどこでされてるのか調べることにしました。

spec/controllers/application_helper_spec.rbで

equire File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
class ApplicationController < ActionController::Base; end
class UsersController < ApplicationController
  def index
    @users = User.page params[:page]
    render :inline => '<%= paginate @users %>'
  end
end

というのがあったけど、

:inline => '<%= paginate @users %>'

というコードがありました。

作業してる時に参考にさせてもらったActionViewのサンプルコード

# 02_assign.rb
ret = view_context.render(inline: '<%= @last_name %> <%= @first_name %>')

という感じで、inline: xxxx という似た感じの処理になっていることに気づきました。

Kaminariのpaginate()はどこで定義されてるかthe first implementation of pagination helperのコードを調べてみたところ、lib/kaminari/railtie.rbで

module Kaminari
    class Railtie < ::Rails::Railtie
    initializer 'paginatablize' do |app|
      ::ActionView::Base.send :include, Kaminari::Helpers

というのをした上で

module Kaminari
  module Helpers
    def paginate(scope, options = {}, &block)

が定義されてるので、この↑paginateが鍵になりそうな感じがしたのでここを深掘りしてみることにしました

実際のpaginateを読む

初期の頃の実装を順番に読んでいきました

scope.num_pagesまでの処理

ここは引数のscope, optionsのオブジェクトの値をチェックしつつ、値が存在してたらそれを変数に代入して、値が存在しない場合には、デフォルト値を設定する処理のようでした。

Ruby多重代入についてよく理解してなかったので、以下コードを読んでて、すぐにわからずかなりの時間を費やしたのはここだけの話

def paginate(scope, options = {}, &block)
  prev_label, next_label, left, window, right, truncate, style = (options[:prev] || '&laquo; Prev'.html_safe), (options[:next] || 'Next &raquo;'.html_safe), (options[:left] || 2), (options[:window] || 5), (options[:right] || 2), (options[:truncate] || '...'), (options[:style] || 'page')
  current_page, num_pages = scope.current_page, scope.num_pages

content_tagの処理

content_tagはしっかり調べたことがなかったけど、2009年08月29日22:28に書かれた Railsのcontent_tagメソッドが便利で使い方も簡単を読みつつ、content_tagの使い方が紹介されていたので、Pry上で試してみた

include ActionView::Helpers
=> Object
[11] pry(main)> content_tag(:h1, 'ふー', :style => 'color:red')
=> "<h1 style=\"color:red\">ふー</h1>"

ここまで理解できた上でさらに読み進めようと思ったのですが、時間になったので今回の作業はここまででした

コールドリーディングをしてみて

Kaminariは普段何気なく利用していたのですが、今回コードを読んでみて、その下ではActionViewという仕組みを利用してる→そもそもActionViewという存在を知らなかったのでそれだけでも自分にとってはプラスでした。

また、他の人の振り返りを聞いてて、知らない機能とかがちょっと出てきたりして改めてRailsの奥深さを実感したのと、1つのgemに絞って、各自のレベルに応じてもくもく勉強してアウトプットしてもらうというスタイルにしたことの良さを実感できた勉強会かなと思ってます

今度はDevise あたりをとりあげてやろうかなと何となく考えてます