前回やったのが、2014年12月なのでだいぶ久しぶりな感じになったけど、2回目を昨日実施しました。
ちなみに、GemJamは
GemJamについて
何気なく使ってるgemについて各自のレベルに応じて今よりもちょっと深く理解する インプットだけではなくアウトプットもセットで行うことでアウトプットするという習慣を得るきっかけ作り を目的にしたもくもく会形式の勉強会です。
という感じの勉強会です。
ちょっと長いので目次つけておきます
当日のおおまかな流れ
- 会場に来た順番でこんな↑感じで今日やることをザックリ書いてもらって後はもくもくと作業
- 時間になったら振り返りをしてもらう
という感じで運営しました。
イベントページでそれぞれのレベルに応じて勉強してもらうようにガイドラインとなるものを書いておいたのですが、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] || '« 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の処理
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 あたりをとりあげてやろうかなと何となく考えてます