日本の祝日が分かるgem、holiday_jp 0.3.1をリリースしました。

単純なgemなんですが、何故か@pwimさんが高速化してくれたり、@kakutaniさんがリファクタリングしてくれたりで、gemを介して遊んでるみたいな感じです。

今回はjewelerオワコンってことでbundlerにしたり、travis-ciしたり、rubygems-test対応したりしました。

僕も他のgemで新しいツールを使う前の実験台として使ったりしています。:-)

rubygems-testって面白いですね。僕はperl使ってた時に、「cpanの自動テストうっっっz」とか思ってましたが、良い物ですね。すいません。

特にrubygems-testをユーザーがインストールをしてるとプラットフォーム毎にテストが通ったかどうかを自動でhttp://test.rubygems.org/にアップしてくれるのがイカス。

Gem Testers

holiday_jpに幸あれ、rubygems-testに幸あれ。

Railsアプリで画像添付メール経由で画像を受け取りたい。(ガラケーとか)

/etc/aliases:

foo: "| cat $1 > /tmp/foo"

これだとファイルのownerがnobodyとかになっちゃうので.forwardを使う。

/home/unk/.forward:

"| cat $1 > /tmp/foo"

Railsでメールを受け取る

Railsでメールを受け取りたい。railsをCLIから呼び出すにはrails runnerを使う。しかし、railsコマンドはRAILS_ROOT外ではrails new用の動きをするので困る。

しかし、その為にあるのかも知れないscript/railsを使えばOK。

rvm経由で呼び出すと相当長くなる。

"| BUNDLE_GEMFILE=/var/www/foo/current/Gemfile /home/unk/.rvm/bin/rvm ruby-1.9.2p290@foo exec bundle exec /var/www/foo/current/script/rails runner 'PictureMailer.receive(STDIN.read)' -e production"

しかしまだ画像ファイルが600で作成されるという問題が残る。下記の様にchmodするようにしてもいいけど、umaskを変更する方がいいかも。

mod_passenger利用時の注意点 - komagata

"| umask 0022; BUNDLE_GEMFILE=/var/www/foo/current/Gemfile /home/unk/.rvm/bin/rvm ruby-1.9.2p290@foo exec bundle exec /var/www/foo/current/script/rails runner 'PictureMailer.receive(STDIN.read)' -e production"

umaskは子プロセスに引き継がれるのでPaperclip経由で出来るファイルもこれで644になる。

# hello.rb:
require 'rubygems'
require 'sinatra/base'

class Hello < Sinatra::Base
  get '/' do
    'hello'
  end
end

Hello.run! if __FILE__ == $0
$ ruby hello.rb

Sinatra::Baseと違ってSinatra::Applicationは便利な初期化をかなり色々やってくれるので1ファイルで書く時はやっぱり普通のがオススメ。

utf-8のメールをmail.gemを使って送るとWARNINGが出る。

Non US-ASCII detected and no charset defined.
Defaulting to UTF-8, set your own if this is incorrect.

明示的にcharsetを指定すればOK.

# coding: utf-8
require 'rubygems'
require 'mail'

mail = Mail.new do
  to       'foo@example.com'
  subject  'テスト'
  body     'テストメール'
end

mail.charset = 'utf-8' # It's important!
mail.delivery_method :sendmail
mail.deliver

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。

これはセクシーだ。

怖話を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

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も残るから安心。