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

TitaniumMobile勉強記

Web系エンジニア向けのキャリアアドバイザーやってましたが現在はフリーランスで開発含めて色々やってます。技術ネタとしてはRuby/RailsとJavaScript関連(Node.js、Titanium)あたり

GemJamという勉強会を行いました

昨日ですが、日頃お世話になってるコワーキングスペース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標準のものらしく、有理数というのがあるらしい。有理数の解説はこちら
    • Ruby関係ないけど、MATLABの情報を見てる感じだと、経過時間の精度をかなり厳密に行うためにこれを使ってるっぽい

という結論になったけど、これ以上深追いしても、結局何のためにこれ読んでるんだっけ?っていう状態になりそうだったのでソースコードリーディングは一旦切り上げました。

結局作ったもの

全くエンジニア経験のないshiro615さんに、現在プログラミングを教えていて、彼に、クローラー開発について教えていたこともあり、何かそっちに関連するようなものを作ろうと決めました。

デーモン化したRubyスクリプトから毎日決まった時間、例えば、午前4時30分にクローラーを実行するというのを、cron使わずに、呼び出すようなサンプルアプリだったらActiveSupportの機能を使えそうかなと思って、クローラーは別に開発していたので、デーモン化したやつで、毎日決まった時間に実行するっていうベースになりそうなところだけ作ってみました。