TokyuRuby会議04でLokkaについてLTさせていただきました。

TokyuRuby会議に初めて参加させていただきました。運営に携わった皆様ありがとうございました。スポンサーのサントリー様のプレミアムモルツ美味しゅうございました。@2celebさんの料理もとても美味しかったです。

Lokkaでもpadrino-helpersをマージしようとしてますが、Padrinoエヴァンジェリストの@udzuraさんと話せたのも嬉しかったです。

改めて勉強会やイベントの目的とは何なのか考えさせられたイベントでした。

routesがシンプルなところ。

# routes.rb:
require 'rubygems'
require 'sinatra'
require 'pp'

get('/foo')  {}
post('/foo') {}
get('/bar')  {}
post('/bar') {}

pp Sinatra::Application.routes

exit
% ruby routes.rb 
{"GET"=>
  [[/^\/foo$/,
    [],
    [],
    #<Proc:0x000001009c5a58@/Users/komagata/.rvm/gems/ruby-1.9.2-p290@default/gems/sinatra-1.3.1/lib/sinatra/base.rb:1212>],
   [/^\/bar$/,
    [],
    [],
    #<Proc:0x0000010159c318@/Users/komagata/.rvm/gems/ruby-1.9.2-p290@default/gems/sinatra-1.3.1/lib/sinatra/base.rb:1212>]],
 "HEAD"=>
  [[/^\/foo$/,
    [],
    [],
    #<Proc:0x0000010159cd90@/Users/komagata/.rvm/gems/ruby-1.9.2-p290@default/gems/sinatra-1.3.1/lib/sinatra/base.rb:1212>],
   [/^\/bar$/,
    [],
    [],
    #<Proc:0x0000010159bee0@/Users/komagata/.rvm/gems/ruby-1.9.2-p290@default/gems/sinatra-1.3.1/lib/sinatra/base.rb:1212>]],
 "POST"=>
  [[/^\/foo$/,
    [],
    [],
    #<Proc:0x0000010159c868@/Users/komagata/.rvm/gems/ruby-1.9.2-p290@default/gems/sinatra-1.3.1/lib/sinatra/base.rb:1212>],
   [/^\/bar$/,
    [],
    [],
    #<Proc:0x0000010159ba08@/Users/komagata/.rvm/gems/ruby-1.9.2-p290@default/gems/sinatra-1.3.1/lib/sinatra/base.rb:1212>]]}

Hash, Array, Regex, Procの組み合わせに過ぎない。自由度MAX。

これはセクシーだ。

GoogleのWebアプリの脆弱性スキャナ、skipfishを使ってみました。

$ brew install skipfish
$ skipfish -o log -W ~/homebrew/Cellar/skipfish/2.03b/libexec/dictionaries/minimal.wl http://hamcutlet.fjord.jp

homebrewで簡単にインストールできます。結果はhtmlファイルで出るので-oで結果の格納先ディレクトリを指定します。(作っておく)

-Wで辞書を指定します。まずは簡単にと思って一番手軽そうなminimal.wlを選んでHam Cutletのサイトに対して実行してみました。

・・・。

これが滅茶苦茶時間かかる。下記のScan timeを見てください。超簡単なサイトにminimal.wlでやっただけで4時間掛かりました・・・。

skipfish version 2.03b by 

  - hamcutlet.fjord.jp -

Scan statistics:

      Scan time : 4:06:32.814
  HTTP requests : 544785 (36.8/s), 177569 kB in, 211488 kB out (26.3 kB/s)  
    Compression : 185 kB in, 529 kB out (48.1% gain)    
    HTTP faults : 9 net errors, 0 proto errors, 841 retried, 0 drops
 TCP handshakes : 5489 total (99.3 req/conn)  
     TCP faults : 0 failures, 8 timeouts, 2 purged
 External links : 28 skipped
   Reqs pending : 0           

Database statistics:

         Pivots : 29 total, 29 done (100.00%)    
    In progress : 0 pending, 0 init, 0 attacks, 0 dict    
  Missing nodes : 18 spotted
     Node types : 1 serv, 26 dir, 1 file, 0 pinfo, 0 unkn, 1 par, 0 val
   Issues found : 82 info, 10 warn, 1 low, 85 medium, 0 high impact
      Dict size : 2182 words (11 new), 35 extensions, 196 candidates
        
[+] Wordlist '/Users/komagata/homebrew/Cellar/skipfish/2.03b/libexec/dictionaries/minimal.wl' updated (11 new words added).
[+] Copying static resources...
[+] Sorting and annotating crawl nodes: 29
[+] Looking for duplicate entries: 29
[+] Counting unique nodes: 17
[+] Saving pivot data for third-party tools...
[+] Writing scan description...
[+] Writing crawl tree: 29
[+] Generating summary views...
[+] Report saved to 'log/index.html' [0x1366e0ff].
[+] This was a great day for science!

結果は下記のように分り易いHTMLで出ます。(Chromeではjsが動かなかった)

Skipfish - scan results browser

コレだけを持ってお客さんに「弊社のサイトは安全です」と言えるかというとうーむ・・・。しかし、結果を見てみると、なるほどという点もかなりあるので怪しいところを修正する目安になりました。

でもまあ、兎に角実行時間が長い・・・。実用的なサイトに対してフルに実行したら1日じゃ終わらなそうです。

怖話をruby1.8.7 & rails3.0.8からruby1.9.2 & rails3.1.1の最新状態に更新しました。引っかかったところメモ。

yamlのエラー

$ rake db:migrate
rake aborted!
couldn't parse YAML at line 267 column 42

rake db:migrateがエラー。

require 'yaml' YAML::ENGINE.yamler= 'syck'

config/boot.rbの2行目に下記を追加。

require 'yaml'
YAML::ENGINE.yamler = 'syck'

日本語

db/seeds.rbで日本語を使ってたので先頭に下記magick commentを追加。

# coding: utf-8

何でもかんでもmagick commentを入れるのは良くないので必要なところにだけ。rubyのm17nは素晴らしいと思います。

Array#choice

ruby1.9.2ではArray#choiceが無くなってる。Array#sampleを使えばいい。

Fixtures

FixturesがActiveRecord::Fixturesになりました。

db/seeds.rbでfixtureを読み込む場合(test用のとは別に用意してる場合とか)

require 'active_record/fixtures'
Fixtures.create_fixtures("#{Rails.root}/db/seed", 'stories')

こんな感じだったんですが、

require 'active_record/fixtures'
ActiveRecord::Fixtures.create_fixtures("#{Rails.root}/db/seed", 'stories')

こうなりました。

Asset Pipeline

developmentで余裕こいてるとproduction世界でひっそり幕を閉じる。(via ブロントさん)

Asset Pipelineを大きく下記の4つに分けて考えるとわかりやすいです。

  • preprocessing(coffee -> jsなど)
  • concatenation(複数のファイルを一つにする)
  • compression(圧縮)
  • fingerprinting(ファイル名にハッシュ値を付ける)

対象は下記3つ

  • javascript
  • stylesheet
  • image

productionではrake assets:precompileで上記4つを対象3つに施してpublic/assetsフォルダーに入れ、nginx等のWebサーバーに配信させてrailsは関与しません。アプリ起動前からpublic/assets以下に必要な全てのファイルが揃って準備完了している必要があります。

precompileされるにはファイルがsprocketsの記法= require tree .等やconfig.assets.precompile += %w()のリストに入ってる必要があります。ここから漏れているファイルはpublic/assetsに無いことになるので表示されません。

jsの中でひっそり使われてる画像や特定のページでだけ読み込んでるcss、そういえば使ってたjqueryプラグインなど、とても見落としやすいので気を付けなければなりません。

それを乗り越えればnginxでcache期限無限のフィーバー状態です。でも既存アプリのバージョンアップなどでは上記見落としを無くすのはとても難しいと思います。もし見逃しそうならば・・・

config.assets.compileをtrueにするんだな。
おまえにもかぞくがいるだろう…

怖話でコメント欄がスパムで埋め尽くされて困ったので日本語が入ってないコメントは弾くようにしました。

「日本語が入ってない場合弾く」というのはMovableTypeの時からあるやり方ですが、イマイチ綺麗に実装できる感がしなくて控えてました。しかしmojiという素晴らしいgemを使ったら簡単に出来ました。

class Comment < ActiveRecord::Base
  validates_format_of :body,
    :with => Moji.regexp(Moji::ZEN_ALNUM | Moji::ZEN_KANA | Moji::ZEN_KANJI),
    :message => I18n.t('errors.messages.not_a_zenkaku')
end

mojiは色々な文字種の正規表現を持っていて手軽に組み合わせたりできます。最初はMoji::ZENで全角文字があればOKにしました。しかし実データ(スパム)にはギリシャ文字やキリル文字が存在していたのでそれも除外するようにしました。

$ rails c -E production
>> Comment.all.each {|c| c.destroy unless c.valid? }

これで今までの全てのスパムが消滅。今のところ新しいスパムも防げてるようでスッキリ。

カウンターキャッシュのリセット

以前、update_countersメソッドを使ってカウンターキャッシュを設定したつもりになっていました。しかし、update_countersメソッドは与えた数だけカウンターが増えるという動作だったようです。代わりにreset_countersメソッドを使えばcomments.countの数にresetしてくれるようです。

Story.all.each do |story|
  Story.reset_counters(story.id, :comments)
end

コレをHerokuに移しました。

何よりも「僕が12時に起きてパソコンを立ち上げていないと動かない」という致命的な問題への対応です。

他の人も登録可能にすれば便利かな?誰か頼む・・・。

komagata/random_lunch - GitHub

Facebookページ・アプリでハマるところシリーズ。

RailsでFacebookアプリ作ります。FacebookアプリはIFRAME内のページをPOSTメソッドで呼び出します。エントリー一覧(/entries)のようなものをFacebookアプリのトップページにしようとしたらアレ?となります。(POSTしたらcreate呼ばれるから)

オーマイゴッド!じゃあ/entries/newをPOSTメソッドも受け入れるようにしてこれをトップにしよう。しかしFacebookアプリは/で終わるURLしか指定できません。仕方ないので/entries/new/を受け入れるようにしよう・・・。

しかしログインしたはずが情報が引き継がれません。おかしいな。

WARNING: Can't verify CSRF token authenticity

ログを見るとCSRF tokenのWARNING。考えてみりゃ当然ですが、FacebookからPOSTで呼ばれる時にはCSRF tokenなんぞ付いてないのでWARNINGが出ます。そして危ないのでsessionは一旦破棄されるわけです。自動ログアウト。

paperclipを使ってて「縦長の画像をアップしたら横長になる」という問題。

これはpaperclipが悪いんじゃなくて、元のファイルもMacのPreviewで見ると縦長なんだけど、ブラウザで見ると横長。MacのPreview他、ExifのOrientationタグに対応したソフトで見ると縦長に見える。

Webアプリ的にはちょっと困る。「縦長の画像をアップしたら横長になった!」と言われても見てるツールの違いで最初から横長なんだから。

imagemagickのconvertにはExifのOrientationタグの内容に合わせて画像データを回転させてくれる超便利な-outo-orientオプションがあるのでそれを使えばいい。

paperclipはrmagickを使わず、convertコマンドを呼び出すだけで、好きなオプションが渡せるというイカシタ作りになっているのでこんな感じでOK。

class User < ActiveRecord::Base
  has_attached_file :picture, convert_options: {all: '-auto-orient'}
end

allは複数のstyleが合った場合に全部にこのオプションを付けるという指定。originalも残るから安心。

phpでアップロードサイズね。はいはい、upload_max_filesize、ワロスワロスとかいってっとやべーぞ!

nginxのclient_max_body_sizeとphp.iniのupload_max_filesize, post_max_sizeを変える必要がある。

それぞれデフォルトは1MB, 2MB, 8MBになってっからデカイ画像とかアップできない。

# nginx
client_max_body_size 32M;
; php.ini
post_max_size = 32M
upload_max_filesize = 32M

sudo するときに sbin にパスを設定する方法 | Carpe Diem

僕はDebian派です。CentOSは嫌い・・・というかCentOSが俺のこと嫌いなんじゃないかと思っていた原因の一つがコレ。

オフィスにn0tsさんが来てたので、せっかくなので日ごろ気になってたアレコレを聞いてみたらスッキリした解決方法を教えてくれました。

「visudoは構文チェック機能のために存在する」こととか。