先週、怖話.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

デザイナーにオススメのMacでのRubyの簡単な設定方法。(何故MacのGEM_HOMEはユーザー別のが先に無いのか - komagata

Snow Leopard, bashというデフォルト状態から、GEM_HOMEを設定するだけ。

~/.bash_profileというファイルに下記を書いてターミナルを再起動してください。

export GEM_HOME=~/.gem/ruby/1.8

これでsudoとかrvmとか無しで好きなrubygemが使えます。railsとかsinatraも使えるよ。

$ gem install sinatra

よくわからなくなってしまったら~/.gemフォルダを削除すれば無かったことにできます。

Let's enjoy ruby and rubygems!

これ便利さがちょっと伝わりづらいんですが、rvmで普段使いのruby(default)用にruby-1.9.2-p290@defaultとか作って指定しておくと@globalが空なので混ざらなくて良いって話。新環境作るたびに$ gem install bundlerする必要あるけど。みんなやってることかも?

RubyKaigi2011行ってきました。

Rubyコアの話はAfter Rails時代に後乗りしてきた僕にとっては耳が痛いというか、大変な苦労をされて作ってるのをタダ乗りしてる感じで申し訳無いなーと。

また、RubyKaigiに合わせて無理矢理ベータテスト開始した、怖話.jpのカードを@machidaさんに作ってもらって配ったりしました。カードを受け取ってくださった方々ありがとうございます。

!RubyKaigiではfluxflexのお話を聞かせていただいて、Lokkaがワンクリックインストール可能になっててテンションが上がったり。

Lokkaを使ってるという方やこれから使おうと思ってると声をかけていただいて嬉しかったです。CRubyに貢献できない分、僕はRubyで出来たオープンソースアプリという形で頑張らねばなあとやる気が湧きました。

一番ビックリしたのは2日目の懇親会後にサンシャイン60の下で@holman(Zach Holman)に会ったことです。

Automating Inefficiencies on Vimeo

VAGRANCEPTION on Vimeo

酔っ払って「駅どっちだ?」とかうろうろしてたら同じ状態の外人がいて、よく見たら生@holmanでした。

「うおっ、Zack Holmanだ!」

とか素で言ってしまいました。

boom gemが好きだとか日本で広めようとしてるとか言ってたら、

「You are wrong.」

とか言いつつ写真撮ってくれました。いやーびっくりした。

Paperclipで画像サイズの最後に使うコレ

has_attached_file :foo, :style => {:thumb => '100x80>'}

いつも意味忘れてググるけどImageMagickのオプションなので下記に書いてある。

Resize or Scaling -- IM v6 Examples

  • Ignore Aspect Ratio ('!')
  • Only Shrink Larger ('>')
  • Only Enlarge Smaller ('<')
  • Fill Given Area ('^')
  • Percentage Resize ('%')
  • Pixel Area Limit ('@')
  • Cropping ('#')

参照:[rails]PaperClipでクロッピング - func09

$ sudo easy_install pip && sudo pip install dotcloud
$ dotcloud setup
$ dotcloud create dotcloudrack
# Gemfile:
source :rubygems
gem 'rack'
# config.ru:
require 'rubygems'
require 'bundler'
Bundler.setup

class App
  def call(env)
    [200, {'Content-Type' => 'text/plain'}, ['unk']]
  end
end

run App.new
# dotcloud.yml:
www:
  type: ruby
$ dotcloud push dotcloudrack

http://c0435321.dotcloud.com/

dotcloud open dotcloudrackって出来てくれるといいな。