rails3.1でMySQLからやってくる文字列がASCII-8BITになっているのでto_jsonすると壊れる(to_jsonがencodingを見て処理するので)。sqlite3では起こらない。

環境はSnow Leopard、ruby1.9.2-p290、homebrewで入れたmysql 5.1.54。

% rails new foo
% cd foo
% vi Gemfile
(...)
gem 'mysql'
(...)
% bundle
% vi config/database.yml
(...)
development:
  adapter: mysql
  encoding: utf8
  database: foo_development
  pool: 5
  username: root
  password: 
  host: localhost
  socket: /tmp/mysql.sock
(...)
% rails g model post title:string
% rake db:create
% rake db:migrate
% vi db/seeds.rb
Post.create!(title: 'うんk')
% rake db:seed
% rails c
ruby-1.9.2-p290 :001 > puts Post.first.title
うんk
 => nil 
ruby-1.9.2-p290 :002 > Post.first.title.encoding
 => #<encoding:ascii-8bit>
ruby-1.9.2-p290 :003 > puts Post.first.title.to_json
"\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd"
 => nil

CentOS 5.6でも同じ。

解決:

mysql gemはruby1.9.1からのencodingに対応してない。だからmysql2を使えば解決でした。

% vi Gemfile
(...)
  gem 'mysql2'
(...)
% vi config/database.yml
(...)
  adapter: mysql2
(...)
% rails c
ruby-1.9.2-p290 :001 > Post.first.title.encoding
 => #<Encoding:UTF-8>

adapterにmysql2と書けるというところが盲点でした・・・。

% node -v  
v0.4.8
% rails -v
Rails 3.1.0
% rails new foo
% cd foo
% vi app/assets/stylesheets/foo.css.scss
body {
  background: image-url("rails.png");
}
% rake assets:precompile
rake aborted!
rails.png isn't precompiled
  (in /Users/komagata/tmp/foo/app/assets/stylesheets/foo.css.scss)

Tasks: TOP => assets:precompile
(See full trace by running task with --trace)

何故だろう?

とりあえずはconfig/environments/production.rbconfig.assets.compile = true

にしてLive compileして凌いでいる。

追記:

rails3.1のバグでした。3.1.1を待て。

@kakutaniさんからpull requestが来てたので改めて、holiday_jpについて。

2009年に仕事で必要になって作ったgemです。祝日が必要なソフトウェアは結構あると思いますが、こんな調査に時間を割くのは一人でいいんで共有できればと。

祝日は法律によって増えたり減ったりするので未来に渡って計算で算出することができません。

休日・祝日の定義について

休日は単に休みの日です。祝日は”国民の祝日を定める法律”で規定されるお祝いのための休日です (土日は祝日ではありません)。皇室の祭典を行った大祭日などが元になっています。

立春の日・秋分の日について

また厄介なのが立春の日と秋分の日です。基本的には国立天文台の算出する定気法による日(天体の動き)から決定されるため「ほぼこの日だろう」という日は分かりますが、実際には前年度の2月1日に閣議決定され、官報で告知されるまで確かなことはわかりません。(天文学に基づいて年毎に国家の祝日が決定されるのは世界的に見ても珍しいそうです。)

つまり、「現行法に置いては」という但し書きをつけても、日本の祝日は1年先までしかわからないということです。

「1年毎に誰かがメンテナンスする必要があるのであればgithubとかあるし100年分ぐらいgemの中に持っちゃえばいいじゃん。」

ということでholiday_jpができました。

関連:holiday_jp - 国民の祝日が分かるライブラリ - komagata

Travis CI - Distributed build platform for the Ruby community

ヒャッハー!ruby1.9.3preview1で動かねえところもたまらねえぜ。

module Foo
  module Helpers
    def bar
      'unk'
    end
  end
end

こういうHelpersを

helpers do
  include Foo::Helpers
end

こういう風に使ってた場合。

RSpec.configure do |config|
  config.include Foo::Helpers
end
describe Foo::Helpers do
  context 'bar' do
    it 'should return unk' do
      bar.should eql('unk')
    end
  end
end

Spec::Runner.configでincludeするとテスト出来る。

LokkaはSinatraベースなので同じようにHelpersのテスト書ける。でもRspecややこしいな。config.includeのとことか。とTest::Unit, Shoulda信者が申しております。

先週、怖話.jpのランキングを重いサブクエリを書いて実装しましたが、Rails3レシピブック(Recipe 093 カウンタキャッシュを利用する)を見ていたらcounter_cacheというので簡単に速くなりそうだったのでやってみた。

ASCIIcasts - “Episode 23 - Counter Cache Column”

要はfoo has many barsという関係の時にbarのbelongs_toに:counter_cache => trueと書いて、fooのDBにbars_countというカラムを追加すればいいらしい。

怖い話(story)に付いている怖い(scare)とコメント(comment)の集計にcounter_cacheを使ってみた。

class Scare < ActiveRecord::Base
  belongs_to :story, :counter_cache => true
end
class Comment < ActiveRecord::Base
  belongs_to :story, :counter_cache => true
end
class AddCounterCacheToStories < ActiveRecord::Migration
  def self.up
    add_column :stories, :comments_count, :integer, :null => false, :default => 0
    add_column :stories, :scares_count, :integer, :null => false, :default => 0

    Story.all.each do |story|
      Story.update_counters(story.id, :comments_count => story.comments.count)
      Story.update_counters(story.id, :scares_count => story.scares.count)
    end
  end

  def self.down
    remove_column :stories, :comments_count
    remove_column :stories, :scares_count
  end
end

counter_cacheはfooのcreateとdestroyのコールバックとして数を増減するというだけの動作なので、後付けする場合はupdate_countersメソッドで集計し直してやる必要がある。

class Story < ActiveRecord::Base
  scope :order_by_ranking, joins(:user).order("view + (select count(*) from scares where scares.story_id = stories.id) * #{Scare::RANKING_WEIGHT} + (select count(*) from comments where comments.story_id = stories.id) * #{Comment::RANKING_WEIGHT} desc, stories.id desc").includes(:user)
end

お陰でこんな醜悪なクエリが、

class Story < ActiveRecord::Base
  # scares_count and comments count is using counter_cache.
  scope :order_by_ranking, joins(:user).order("view + scares_count * #{Scare::RANKING_WEIGHT} + comments_count * #{Comment::RANKING_WEIGHT} desc, stories.id desc").includes(:user)
end

ホッとするシンプルなクエリに。

しかし、キャッシュ系によくあることですが、カウンタキャッシュを使っているということをちゃんと意識してないと端からみたらわかり辛いことになるので、現在は一人開発ですが、将来の自分を含めた他人に向けて注意を喚起するコメントを残しておくことにしました。

rails + jenkinsでgithubにpushしたらテストというところまでは下記を参照してください。

ウェブオペレーション継続的デプロイというキャッチーな単語を知ったので試してみた。

kowabana Config [Jenkins]

継続的デプロイなんつっても、上記の様にいつものテストにcapのタスクを追加するだけ。簡単。

kowabana [Jenkins]

githubにpushされると勝手にjenkinsが動き出して…

怖話 | スマホで怖い話

ステージング環境にデプロイ。

これでデザイナーの@machidaさんがgit pushした時も勝手にステージング環境が最新になる。デザインが変わっただけでも頻繁にデプロイされるので問題点などが議論し易い。(特にスマホサイトは実機からアクセス出来る環境があると便利。)

最近はデザイナーも簡単にGithubが使える環境が揃ってきたので、テスト・開発・チェックイン・デプロイというサイクルにデザイナーが入る良いタイミングかも。

デザイナーのためのGithub for Mac の使い方「リポジトリ作成編」 - KUROIGAMEN(黒い画面)

% rake
`include Capybara` is deprecated please use `include Capybara::DSL` instead.

include Capybaraがdeprecatedになってたので指示通り書き換えました。

# test/test_helper.rb:
class ActiveSupport::TestCase  
  (...)
  class ActionDispatch::IntegrationTest 
    include Capybara::DSL      
    Capybara.app = KowabanaJp::Application
  end
end

Unit::TestとCapybaraでのIntegrationTestは簡単なのにちゃんと動く(バグを見つけたりデグレを防ぐ役割をちゃんと果たすという意味で)のでお気に入りです。

毎日昼飯をどこで食べるのかに本業より頭を使ってる気がするので自動化した。

require 'rubygems'
require 'nokogiri'
require 'open-uri'
require 'mail'

api_key = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
version = 'v1.0'
station_ids = ['2800', '2811'] # hatagaya, hatsudai
page = 1
entries = 10
total_entries = 100

restaurants = []

station_ids.each do |station_id|
  while page * entries < total_entries
    url = "http://api.gourmet.livedoor.com/#{version}/restaurant/?api_key=#{api_key}&station_id=#{station_id}&sort=total"
    doc = Nokogiri::XML(open("#{url}&page=#{page}"))
    total_entries = doc.xpath('/results/total_entries').text.to_i

    doc.xpath('/results/restaurant').each do |restaurant|
      restaurants << {:name => restaurant.xpath('name').text,
                      :link => restaurant.xpath('permalink').text}
    end

    page += 1
  end
end

restaurants.shuffle!

body = ''
restaurants[0, 10].each_with_index do |restaurant, i|
  body += <<-EOS
#{i + 1}. #{restaurant[:name]}
#{restaurant[:link]}

  EOS
end

mail = Mail.new do
  from    'komagata@gmail.com'
  to      'komagata@gmail.com,machidanohimitsu@gmail.com'
  subject "Today's recommended restaurants"
  body    body
end

mail.delivery_method :sendmail
mail.deliver

ライブドアグルメの初台、幡ヶ谷の店からランダムで10個、12時になったらメールしてくれる。(これを前のエントリーの要領でcronに登録する)

Today's recommended restaurants - komagata@gmail.com - Gmail

こういうメールが届く。

これをWebサービスとして公開し、ユーザー同士で同じ店に行くことになったら偶然を利用してマッチングするというのを考えた。でもkowabana.jpをやりたいので誰か作って。

0 12 * * 1,2,3,4,5 bash -c 'source ~/.rvm/scripts/rvm; rvm ruby-1.9.2p290@default exec ~/code/random_lunch/random_lunch.rb'

Use bash -c and rvm exec