USO800

Web開発者oakbowのブログです

TokyuRubyKaigiでLTしてきました。

f:id:oakbow:20160531190300j:plain

LT というのは Lightning Talk の略。 5分程度の短いプレゼンテーションのことで、IT系のカンファレンス、イベント、勉強会などではとてもメジャーです。 短い分それほど前準備に時間をかけず、カジュアルに参加出来るものが多いです。

Tokyu Ruby Kaigi#10 のLT 募集に応募したら当選したので、フィリピン留学についてしゃべってきました。 発表スライドはこちら。

スライドをただ見ただけではあんまり何しゃべったか分からないと思うので、気になった方は SlideShare のサイト でスライドをダウンロードしてください。 発表者ノートに詳細を書いています。 LT ではこの内容を要約して話しているので、ある意味こちらが正規版ですね。

伝えたかったこと

フィリピン留学ってそこそこメジャーになりつつある認識があって、それなりにアンテナを立ててるプログラマだったら聞いたことあるかな、と思っています。 興味はあるし英語話せるようになるんなら行ってみたいけど、実際どうなのかな〜という人はそこそこいる気がしていたので、実際のところを軽く紹介した感じでした。

多分、私の英語力は勉強会に来ているようなプログラマと比較すると標準かちょっと下あたりだと思うので、等身大の紹介が出来たのではないかな?と思います。 とはいえ LT ではどうしても要点だけ手短に話す必要が合ったので、詳しいことは直接聞いてね、になっちゃったんですが。

まあ、時間が制限されている方が却って良い発表になることも多いですけどね。

後ほど 3分の追加 LT が募集になったので、せっかくだからとちょっとオフレコっぽい話もしてみたんですが、これは余分だった気もしてます。 まだまだおしゃべりスキル高くないので、たいした準備もなしにいいスピーチをするのは、例え 3分でも難しいですね。反省。

参考図書

LT では触れる余裕あまりなかったんですが、フィリピン留学を考えている人にはとてもお勧めの本があります。 スライドの末尾に乗せていますが、こちら。

3ヶ月でTOEIC300点上げる フィリピン語学留学

3ヶ月でTOEIC300点上げる フィリピン語学留学

タイトルは割とありがちな気がしてくるんですが、内実は暴露本です。フィリピン留学の。

いや、もちろんちゃんとこのタイトル通りの話もしてるんですけどね。

フィリピン留学は(欧米豪に比較して)安いと言っても結構なお値段だし、そうそう行けるものでもなし。

一生に一度か二度の経験で失敗したくない人のために、割とギリギリなところまで踏み込んで正直なところが書いてあります。

著者はフィリピン留学の事業者側なんですが、良くここまで書けるなあと感心しきりでした。

値段は 500円ですが、Kindle 版だと 250円なのかな?期間限定なのかずっとこれなのか分かりませんが。

まだ続いているのかもう終わってるのか分かりませんが、こちらのキャンペーンだとタダでもらえるかもしれません。

電子書籍プレゼント『3ヶ月でTOEIC300点上げるフィリピン語学留学』 | フィリピン語学留学|サウスピーク

Tokyu Ruby Kaigi とは

f:id:oakbow:20160531190617j:plain

Tokyu Ruby Kaigi はプログラム言語 Ruby をテーマとした地域カンファレンスですが、非常に敷居が低く、誰でもオッケー的な懐の広さと深さを感じさせてくれます。 発表内容はすべて LT で、Ruby がちょっとでも絡んでいれば何でも良い、というゆるさ。

ぶっちゃけ私の ↑の発表なんて Ruby 関係ないじゃんって突っ込み入りそうなレベルですが、大丈夫です。 もちろんちゃんと技術カンファレンスらしい話を発表する人もいます。しないひともいます。

食べ物はすべて参加者の持ち寄り。 飲み物も基本は参加者の持ち寄り。

LT と 食べ物は人気投票があり、それぞれ LT 王 と 飯王 が表彰されます。 トップ画像の生ハムの原木は今回の飯王のものです。

飲み物については参加者持ちよりだけでなく、スポンサーのご好意でプレミアムモルツがかなり潤沢にありました。 受け付け終わったらプレモルお姉さんにビールをコップで渡され、席について飲み干したら別種の奴をプレモルお姉さんが運んでくれ、という感じでアンリミテッド・プレモル状態でした。 実に素晴らしい。

また次も LT ネタこしらえて参加したいと思います。

Rails4のActiveRecordは、IN演算子のサブクエリを使える

f:id:oakbow:20150324011856p:plain

結論

SQLでは WHERE 句で IN 演算子というものを使えます。 この IN 演算子の中は、カンマ区切りの配列のような形で要素を列挙する形でも、別の SQL の出力結果(サブクエリ)を利用することもできます。

SELECT * FROM users WHERE id IN (1,5,7,10,50...)

SELECT * FROM users WHERE id IN (SELECT user_id FROM presences WHERE ...) 

こんな感じの二つの書式を使うことが出来ます。

Rails3 ではこのうち前者のタイプは普通に使えたんですが、後者は Squeel などの外部の力を借りないとできなかったと思います。

Rails4 ではどちらもできるようになっているよ、というお話です。

こんな感じで使えます

Student(学生)モデルとReport(レポート)モデル、Credit(単位)モデルがあるとします。

Student:Report は1対多、Student:Credit は1対1の関係にあります(話を簡単にするため、教科の概念をなくしています)。

ここで、最終レポートを提出した学生全員に単位を与える処理を考えてみましょう。

アプリケーションサイドから素直に考えた場合、こんなコードになると思います。

 student_ids = Student.joins(:reports).where('reports.type= ?', :final).pluck(:id)

 Credit.where('student_id IN ? ', student_ids).update_all(grade: 'good')

SQL はこんな感じになります。

SELECT students.id 
FROM students 
INNER JOIN reports ON students.id = reports.student_id 
WHERE reports.type = 'final'

UPDATE credits SET grade = 'good' WHERE student_id IN (1, 5, 12, 33, 156, ...)

でもこれ、こう書けるんですね。

 student_ids = Student.joins(:reports).where('reports.type= ?', :final).select(:id)

 Credit.where(student_id: student_ids).update_all(grade: 'good')

SQL はこうなります。↑と違って一回だけ発行されます。

UPDATE credits 
SET grade = 'good' 
WHERE student_id IN (
  SELECT students.id 
  FROM students 
  INNER JOIN reports ON students.id = reports.student_id 
  WHERE reports.type = 'final')

そう。 同じ書式のままで、配列でも ActiveRecord::Relation でも受け取ってくれるんです。SQL の IN 演算子と似ていて、非常に直観的ですね。これはすごい。 Rails3 で Squeel が入った状態から始めた私はいまいち素の AR にできることできないことをちゃんと把握できていないんですが、これ、Arel 使わないとできないと思ってました。。

サブクエリのすすめ

前者は SQL を二回発行して、まずレポートを提出した学生の ID 一覧を取得し、その後学生の ID 一覧に合致する単位データを一括で「良」に更新しています。 後者は SQL の発行回数は一回。レポート提出者の学生の ID の集合を取得し、その集合に合致する単位データを更新しています。

データ量が少なければ微々たる差しかありませんが、どの RDBMS でもほぼ確実に後者の方が速いです。

SQL というのは集合を扱うことに関しては非常に優れていて、集合の状態のまま条件としてうまく渡して一度で処理できると、特にデータ量が増えてきたときにものすごい差になって来るんです。 なので可能な限り後者の形で発行してあげる必要があります。 集合の扱いに関しては通常の言語が及ぶことはまずないので、配列やハッシュでいったん受け取って加工して…などということは基本的に考えない方がいいです。

サブクエリは遅いこともある。。。という話をSQL初級講座的なものを読んで避けようとする方もいると思いますが、それはサブクエリじゃない通常のクエリとの対比の話であって、ネイティブコードで実行される、集合処理に特化した命令(SQL)の代わりに、Ruby で配列処理やっても余計に遅くなるだけなので、素直にサブクエリ使いましょう。

もちろん例外はありますが、基本的には SQL の発行回数が少なければ少ないほど速いです。

実際に遭遇したコードに近いもの

今回の話はコードレビューで知りました。

本物のコードは上の例とは違って、「最終レポートを提出していない学生をすべて不可にする」という感じの処理をやっています。

同じではありませんが、こんなコードになっています。

Report モデルは、レポートの提出がないとレコードが生成されません。

 student_query =
      Student
      .joins(
        sanitize_sql_array([<<-SQL, :final])
          LEFT OUTER JOIN reports
            ON students.id = reports.student_id
              AND reports.type = ?
        SQL
      )
      .where("reports.student_id IS NULL").select(:id)

  Credit
  .where(student_id: student_query)
  .update_all(grade: 'not_good')

こんな感じ。 sanitize_sql_arrayプレースホルダのために利用しています。

結合条件を AR4 で書けると良いのですが、そこまではさすがにできないのでこんな書き方をしています。

最後に

ここに書いたコードやクラスは説明のために頭で考えたミニマムコード(例として十分な最小限のコード)です。

実際に動かしている訳ではないので不備がある可能性があります。

記事の内容やコードに間違いがありましたらご連絡ください。

可能な限り補足修正したいと思います。

はー、はじめて技術ブログみたいな記事書いたわ。

OSSのコントリビュータになりました

タイトルの通り、コントリビュータになりました。


contributorの意味 - 英和辞典 Weblio辞書

 


hadoop - contributor って何ですか? どうやったらなれますか? - Qiita

 

OSSへの貢献の仕方はいろいろあるようですが、分かりやすいのはやはり何らかのコードの修正をするやり方だと思います。

非常に小さなものですが、先日修正したコードのプルリクエストが取り込まれ、コントリビュータになることができました。

せっかくなのでその修正の経緯などを記事にしたいと思います。

 

対象のOSSプロジェクト

今回私がコントリビュータになったのは、Rails のgemである rails-footnotes です。

RailsでのWebアプリ開発の非常に強力な武器となるデバックツールで、個人的には better-errors(+binding-caller)の次くらいに手放せないツールになっています。

基本的にdevelopmentやstaging環境でしか使用しないgemですが、Webページの最下部にデバッグに役立つ各種情報を表示してくれます。

gemの利用面での詳細はQiitaに記事を書きましたので、こちらもご覧ください。


Rails4 - Rails Footnotes でWebアプリを快適開発 - Qiita

 

例外が発生

現在関わっているプロジェクトでは以前から rails-footnotes が使われていて、バージョンアップ作業を行っていました。

以前のバージョンで機能していたイニシャライザをそのまま使うと DEPRECATION WARNING が発生していたので、最新バージョンの 4.1.4 が生成してくれるイニシャライザに刷新。

コメントアウト状態で設定項目が記述されていたので、軽い気持ちでnoteと呼ばれるフッタに表示する項目の設定を有効化して動作させてみました。修正箇所はここ。

  # Only toggle some notes :
  f.notes = [:session, :cookies, :params, :filters, :routes, :env, :queries, :log, :general]

単にコメントアウトを解除しただけです。元は rails-footnotesが生成しているイニシャライザな訳ですから、設定を有効にしても基本的にちゃんと動作するはず...なのに例外発生。

Footnotes General Exception: uninitialized constant Footnotes::Notes::GeneralNote

なぜだー!ってことで調査開始です。

例外の発生源の調査

スタックトレースから原因を特定することにしました。 出力されているログはこんな感じ。

Footnotes General Exception: uninitialized constant Footnotes::Notes::GeneralNote
/activesupport-4.1.8/lib/active_support/inflector/methods.rb:240:in `const_get'
/activesupport-4.1.8/lib/active_support/inflector/methods.rb:240:in `block in constantize'
/activesupport-4.1.8/lib/active_support/inflector/methods.rb:236:in `each'
/activesupport-4.1.8/lib/active_support/inflector/methods.rb:236:in `inject'
/activesupport-4.1.8/lib/active_support/inflector/methods.rb:236:in `constantize'
/activesupport-4.1.8/lib/active_support/core_ext/string/inflections.rb:66:in `constantize'
/rails-footnotes-4.1.4/lib/rails-footnotes/filter.rb:37:in `block in start!'
/rails-footnotes-4.1.4/lib/rails-footnotes/each_with_rescue.rb:15:in `block in each_with_rescue'
/rails-footnotes-4.1.4/lib/rails-footnotes/each_with_rescue.rb:13:in `each'
/rails-footnotes-4.1.4/lib/rails-footnotes/each_with_rescue.rb:13:in `each_with_rescue'
/rails-footnotes-4.1.4/lib/rails-footnotes/filter.rb:36:in `start!'
/rails-footnotes-4.1.4/lib/rails-footnotes/extension.rb:15:in `rails_footnotes_before_filter'
   :

active_support 側に問題があるのではなく、想定外の値が rails-footnotes やそれ以前から吐き出されていることが原因と見て、filter.rbから見ていくことにします。 周辺のソースはこうなっています。

def start!(controller)
  self.each_with_rescue(Footnotes.before_hooks) {|hook| hook.call(controller, self)}

  @@klasses = []
  self.each_with_rescue(@@notes.flatten) do |note|
   klass = "Footnotes::Notes::#{note.to_s.camelize}Note".constantize # ←37行目!
   klass.start!(controller) if klass.respond_to?(:start!)
   @@klasses << klass
   end
end

見るからに怪しいですね。 見た感じ notegeneral という文字列が入っていることで、37行目で以下のような処理が実行されているようです。

klass = "Footnotes::Notes::GeneralNote".constantize

例外の内容を見るに、やはりここが直接的な引き金になっているようです。

そもそも note って何?ってのを知っていると解決が早いんですが、これは rails-footenots が提供するデバッグ情報の分類です。 私は良く理解できていなかったので、 note に何が入っているのか知るために、古典的なデバッグライトを使いました。 要するに pp note って記述を36行目に追加したんですね。 gemの中身のソースを修正したのでサーバを再起動して、再び例外を発生させます。 するとこんな文字列がログに出力されるようになりました。先ほどのコードの37行目に渡される note の変数の中身の一覧です。

controller
view
layout
partials
stylesheets
javascripts
assigns
session
cookies
params
filters
routes
env
queries
log
general

これは結構大きなヒント。 gemのファイルをあちこち漁っていて気づいたんですが、これらの文字列に対応するファイル群が /lib/rails-footnotes/notes/ に存在します。 controller_note.rb とか log_note.rb とか諸々。でも general_note.rb はありません。 whyなぜ?

公式に戻る

generalgrep をかけても芳しい情報は得られず、どうやら general_note.rb がないことが影響しているらしいことしか分かりません。 こういう時は公式です。こちらで general で検索してみると...、あった、ありました!

== Footnotes v4.0.0
  :
  * Remove 'general' note

rails-footnotes/CHANGELOG at 5479be18602ec4796a87fde4953dedc1970b2061 · josevalim/rails-footnotes · GitHub

GeneralNote 消されとるやんけ!そら無いもの指定したら例外吐くわ!! 思わずえせ関西弁で突っ込みたくなる結果です。 ってことは、無いもの指定していた設定ファイルである、 /config/initializers/rails-footnotes.rb が悪いということに。 この設定ファイルは公式の推奨通りのコマンドを打つと生成されるようになっていて、テンプレートファイルが lib/generators/templates/rails_footnotes.rb にあります。

  # Only toggle some notes :
  # f.notes = [:session, :cookies, :params, :filters, :routes, :env, :queries, :log, :general]

gem を generalgrep しまくっていたので存在には気づいていたんですが、この配列末尾の :general があるのがいけなかったんですね。 実際に使っているイニシャライザ側でこの記述を削除したところ、例外が発生すること無く正常に動作することを確認できました。 めでたしめでたし。

プルリクエストを送る

問題は解決しているんですが、同様の問題で苦しむ方がもしかしたらいるかもしれません。 本来であれば GeneralNote を機能的に削除した際に、このテンプレートから :general の記述を削除するべきだった訳ですから、gem のバグといっても良いでしょう。 バグは修正されるべきなので、プルリクエスト(以下PR)を送ってみることにしました。

PRを簡潔に説明したサイトをうまく見つけられませんが、要約すると「俺様がやったこの素敵改修を見てくれ。気に入ったら取り込んでね?」って機能です。 git ではなく github の機能で、bitbucket などでも使えます。 PRが送られると送り主のコメントの他、修正箇所のコードの差分が視覚的に分かりやすく表示され、素敵改修の中身をチェックすることが出来ます。 コードの行単位でコメントをし合えるので、「このコードどういう意味?」「ここってこうした方がいいんじゃない?」という風にコードレビューを物理的に離れた人同士で行える、とても強力な機能です。

PRは github や bitbucket をチーム開発で採用していれば普通に使う機能なので、これ自体には慣れていました。 とはいえ、OSS でやるのははじめて。ドッキドキです。 git の commit コメントを書く段階から、これ通じるかなー大丈夫かなーと汗かきながらがんばりました。 その結果がこれ。

fix Footnotes General Exception, because general note was removed. by oakbow · Pull Request #126 · josevalim/rails-footnotes · GitHub

f:id:oakbow:20150222175346p:plain

やったー!! 無事マージしてもらえました。 次のバージョンで反映されるそうです。

修正内容的にコミッターにはすぐそれと分かる内容だったでしょうから、特に議論などもなくマージされています。 そうなるだろうとは思ってましたが、心配で1時間に1回くらいリロードしてたのは秘密です。

gem のソースを追うやり方やデバッグのやり方は、正直もう少し効率の良いやり方がある気がします。 ソースを見るのは普段使っている RubyMine が便利なのでそれはいいとして、デバッグライトはなあ。。 ブレークポイントうまく使えればいいんですが、そのうち調べてみようと思います。

英訳に当たっては、@kon_yuさん に助けていただきました。ありがとうございます(それでも英語がアレなのはもちろん私の問題です)。