昨日ですが、日頃お世話になってるコワーキングスペースCo-Edoにて、GemJamという勉強会を行いました。
この勉強会を企画した背景
2つほど理由があって
- 何気なく使ってるgemについて定期的に勉強してみたくなった
- アウトプットの習慣を得るきっかけ作り
という所から企画&実施しました。
何気なく使ってるgemについて定期的に勉強してみたくなった
だいぶ前ですが、WEB+DB PRESS Vol.70で あなたの知らないActiveSupport事例で深くわかる使い方という特集記事があり、本文中で
Ruby on Rails(以下Rails)の縁の下の力持ち」とも言えるActiveSupport gemについて取り上げます
という出だしの文章がありました。
こういう感じでRailsのコアモジュールですごく恩恵をこうむってるけど実は内部のことをよくわからずに使ってるようなgemって結構あるのかなと思ってます。
そういう恩恵をこうむってるものを深く掘り下げて1人で勉強するのは特に経験がまだそれほどない段階では、モチベーションをあげづらいかなと思い、同じような気持ちを持った人同士集まって、各自のスキルに応じた勉強をしてもらいつつ最後にアウトプットをしてもらうことで勉強した成果を確認しやすいかなとふと思ったので企画してみました。
アウトプットの習慣を得るきっかけ作り
ちょっとした縁で酒と泪とRubyとRailsとのモリジュンさんと知り合いになったのですが、少し前に「3分 Gem クッキング」というタイトルでLT発表しました!ということを書かれてました。
最後の方の反省点の所を読んでて、LTみたいなものって、ちょっとづつ経験を積んでいくことで、だんだんと話しやすくなっていくのかと以前から思っているけど、なかなかそういう練習を気軽に詰める場って無いなぁと思ったので、モリジュンさんみたいな経験がある人が、学んだこととかを気軽に発表できる場づくりという意味でもこういう勉強会があってもいいのかなと思いました
ネーミングの由来
音楽のジャムセッションっぽく、あまり事前にこまかい打ち合わせとか約束事を決めず、その時の参加者の属性とか雰囲気に合わせたスタイルにしたいと思って、Gemのジャムセッションっぽいものということで、GemJamにしました。
さっきDoorkeeperにコミュニティページを作ったので興味有る方は登録だけでもいただければと思ってます。(次回開催日はまだ決めてないですが来月にPryをとりあげたいなぁと思ってます)
自分の勉強内容の振り返り
以下、長文ですが、自分の振り返りをしておこうかなと。
ActiveSupportは多機能すぎてどんなことができるのか全体像がつかめてない状態だったのですが、まずは適当にいじることにしました。
利用する時に最初ハマったこと
require 'active_support'
として
4.hours.ago
としても、エラーになる。
active_supportだけをrquireしてもIntegerクラスに対する時刻関連の機能拡張が読み込まれるわけではないとのことで、そのため、時刻関連のようなものを利用する場合には
require 'active_support/time'
とするか、面倒な場合には
require 'active_support/all'
とすればOK。ただallとすると当然読み込みが重くなるみたい
文字列処理
class AdminUser end "AdminUser".underscore => "admin_user"
という感じで、キャメルケースな名前をスネークケースに変換してくれてるのは、ActiveSupportさんのおかげっぽいことを知れて、地味に感動しました。
日付処理
文字列処理についてそれい以上何かしらべようという気分にならなかったので、日付処理についてちょっと試行錯誤してみました。
Date.new(2014, 12, 27).tomorrow => Sun, 28 Dec 2014 Time.now.beginning_of_day => 2014-12-27 00:00:00 +0900 Time.now.end_of_day => 2014-12-27 23:59:59 +0900 Time.now.change(hour: 4, min: 30) => 2014-12-27 04:30:00 +0900
Time.currentとかはRuby標準っぽいなぁと思っていたら、ActiveSupportの機能だった
ActiveSupportをrequireしてると
Time.current => 2014-12-27 14:52:53 +0900
となるけど、requireしないと
irb irb(main):001:0> Time.current NoMethodError: undefined method `current' for Time:Class
とかなるのを知った
JavaScript/Node.js界隈でよく利用されるMoment.jsと日付処理が似てることに気づく
beginning_of_dayとか見ててどっかで見覚えあるなぁと思ったら、Moment.jsの使い方とすごく似てると思ったので対比させてみました。
現在時刻を取得
moment = require('moment'); moment().format() // '2014-12-27T14:10:14+09:00'
明日を取得
moment().add(1, 'days').format() //'2014-12-28T14:15:19+09:00' moment().add(1, 'days').format('YYYY/MM/DD') // '2014/12/28'
当日の午前4時30分
状況としては、毎日稼働してるバッチ処理の中でその日の午前4時30分というのを指定したいようなケース
moment().hours(4).minute(30).second(00).format() // '2014-12-27T04:30:00+09:00'
気になったので、日付処理のソースをちょっと読んだ
上記のような試行錯誤をしてる段階で
Time.now.change(hour: 4, min: 30)
みたいなやつのchange()の処理がどうなってるのか気になったので該当するソースがどこか調べてみた
active_support/core_ext/time/calculations.rbを見ると
def change(options) new_year = options.fetch(:year, year) new_month = options.fetch(:month, month) new_day = options.fetch(:day, day) new_hour = options.fetch(:hour, hour) new_min = options.fetch(:min, options[:hour] ? 0 : min) new_sec = options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec) if new_nsec = options[:nsec] raise ArgumentError, "Can't change both :nsec and :usec at the same time: #{options.inspect}" if options[:usec] new_usec = Rational(new_nsec, 1000) else new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000)) end if utc? ::Time.utc(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec) elsif zone ::Time.local(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec) else raise ArgumentError, 'argument out of range' if new_usec > 999999 ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec + (new_usec.to_r / 1000000), utc_offset) end end
最初の方のoptions.fetchっていうのがどの辺で行われてる処理なのか気になったので、ターミナル上で
find . -type f -name "*.rb" | xargs grep "options"|more
という感じで検索してでactive_support/cache.rbあたりがそれっぽい処理をしてるようにみえたのでちょっと読んで見ました
def fetch(name, options = nil) if block_given? options = merged_options(options) key = namespaced_key(name, options) cached_entry = find_cached_entry(key, name, options) unless options[:force] entry = handle_expired_entry(cached_entry, key, options) if entry get_entry_value(entry, name, options) else save_block_result_to_cache(name, options) { |_name| yield _name } end else read(name, options) end end
何気なく読んでて、ふと引数に渡す時、分、秒がありえないケースの場合にどういう処理をしてるのかというのが気になったので
Time.now.change(hour: 23, min: 60)
とかやると、ArgumentError: argument out of range というメッセージをactivesupport-4.2.0/lib/active_support/core_ext/time/calculations.rb:95行あたりでエラーを吐き出すようになってる。
この辺りを読んでくと
- new_usec > 999999という謎の処理がある
- 前後のソースを読んでいたら、Rational(nsec, 1000))みたいなものが関連しそう
- Rationalっていうのが気になってこれはActiveSupport固有の処理かと思ったら、Ruby標準のものらしく、有理数というのがあるらしい。有理数の解説はこちら
という結論になったけど、これ以上深追いしても、結局何のためにこれ読んでるんだっけ?っていう状態になりそうだったのでソースコードリーディングは一旦切り上げました。
結局作ったもの
全くエンジニア経験のないshiro615さんに、現在プログラミングを教えていて、彼に、クローラー開発について教えていたこともあり、何かそっちに関連するようなものを作ろうと決めました。
デーモン化したRubyのスクリプトから毎日決まった時間、例えば、午前4時30分にクローラーを実行するというのを、cron使わずに、呼び出すようなサンプルアプリだったらActiveSupportの機能を使えそうかなと思って、クローラーは別に開発していたので、デーモン化したやつで、毎日決まった時間に実行するっていうベースになりそうなところだけ作ってみました。