Railsで非同期処理?

railsで非同期処理をやる場合、最近はdelayed_jobメジャーらしいですね。

以前はbackgrounDRbが定番だったようだけど、EngineYardが「友達にBackgrounDRbを使わせるな」とまで書いているので、そこまで言われると使う気になりませんでした。実際リソース食いだったし。

使い方参考ページ

使い方はそんなに難しくないので、ここで説明することは放棄します。

READMERailscastsのエピソード171でなんとかなると思います。

再試行のロジック

いろいろとすっとばして本題、キューの再試行のロジックが変だなぁと思ったのでメモです。

Delayed::Job::max_attempts

delayed_jobには試行回数上限があって、この上限値を超えるまでリトライしつづけます。 デフォルトは25回で、Delayed::Job::max_attempts という定数に設定されています。

Delayed::Worker::sleep_delay

ワーカーが起動するインターバルです。デフォルトは5秒。

例えば絶対に失敗するキューがあった時、

キュー失敗、5秒待機、キュー失敗、5秒待機、(以下25回失敗するまで繰り返し)

という流れを勝手にイメージしていたのですが、そうではないみたいです。

キューが失敗した時の再スケジューリング

キューが失敗すると、次に実行する予定時刻を決めます。そのロジックが

On failure, the job is scheduled again in 5 seconds + N ** 4, where N is the number of retries.

となっています。

「トライした回数の4乗に5秒足した時間」後にリトライをするよう再スケジューリングするので、失敗すればするほどインターバルが開いていきます。

1回失敗したら次回のリトライは6秒後
2回失敗したら次回のリトライは21秒後
3回失敗したら次回のリトライは86秒後
4回失敗したら次回のリトライは261秒後
5回失敗したら次回のリトライは630秒後
6回失敗したら次回のリトライは1301秒後
7回失敗したら次回のリトライは2406秒後
8回失敗したら次回のリトライは4101秒後
9回失敗したら次回のリトライは6566秒後
10回失敗したら次回のリトライは10005秒後

最初の方は1分以内にリトライさせますが、10回目ともなるとリトライは2時間後。
何度も失敗していると、もうこいつアカンわ・・と見捨てられていく感じを良く表現したロジックですねー。

Posted in ruby on rails, 日記 at 12月 15th, 2009. No Comments.

なんかto_jsonについて調べてたら

activerecord/lib/active_record/serializers/json_serializer.rb

にらきすたのこなたが出てくるんだけど、これって有名なんだろうか・・。

あまりにも意外すぎて、なんの作業してたか忘れたよ。いい迷惑だ!

http://github.com/rails/rails/blob/55501b9f6ab46d45db04a81956579402511ad092/activerecord/lib/active_record/serializers/json_serializer.rb#L18-75

追記:

例えばこんな感じ、コメントの中にサンプルコードとして登場します

    #   konata = User.find(1)
    #   ActiveRecord::Base.include_root_in_json = true
    #   konata.to_json
    #   # => { "user": {"id": 1, "name": "Konata Izumi", "age": 16,
    #                   "created_at": "2006/08/01", "awesome": true} }

はてブのコメントによると2年前からコードの中に存在していたらしい。
アニメ好きな外人のコミッターが犯人だったんすね。

http://blog.codefront.net/2007/10/15/konata-izumi-in-edge-rails/

Posted in ruby on rails at 11月 2nd, 2009. No Comments.

friendship.rb — bdd_sample

RailsCasts大好きです。

  • Rails好き
  • わかりやすい
  • ネタが早い
  • 声が素敵

などいろいろ良い点がありますね。

さて、最近TextMateを使うようになりまして(メインはNetBeansです) あこがれのRailsCastsみたいな色にしたいと奮闘しましたが 本家からダウンロードできるのでした・・

http://railscasts.com/about

好きな人はぜひ!

Posted in ruby at 10月 30th, 2009. No Comments.

RailsでApplicationHelperのスペックを書くときにハマったのでメモ。

準備

まず普通にスペックを書く場合

spec/helpers/appliction_helper_spec.rbを以下のように準備

require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')

describe ApplicationHelper, :type => :helper do

  helper_name :application
  %w{hoge}.each do |method_name|
    it "##{method_name}メソッドが存在すること" do
      helper.should be_respond_to(method_name)
    end
  end

end

helper_nameでへルーパー名を定義しておくのがポイント。
そしてExampleの中から「helper」を通じて、エクスペクテーションを書いていきます。(言葉の使い方あってる?)

リクエストを使うスペックを書きたい場合

ヘルパーメソッドの中でリクエストを参照する場合(そもそもヘルパーがリクエストを参照するのが美しいことなのか疑問を抱きましたが、とりあえず無視)

ApplicationControllerにスタブアクションを作ってあげる、というやり方でなんとかしました。

require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')

describe ApplicationHelper, :type => :helper do

  class ApplicationController
    def stub_action
      render :text => self
    end
  end

  helper_name :application

  it "ヘルパーの中からリクエストを参照できること" do
    get :stub_action
    helper.mobile_tracking_code.should be_xxxx
  end

end

こんな感じで、普通にコントローラースペックを書くときのようにgetとかpostとかヘルパースペックの中からリクエストを使ったテストを一応かけます。

もっと良い方法がある気がする。

Posted in ruby, ruby on rails at 10月 19th, 2009. No Comments.

個人的メモ

結構リソース食うので次はdelayed_jobにしたい・・

実行してことはあるけど、正常に終了してないキューを取得する

BdrbJobQueue.find(:all, :conditions => ['finished = ? AND taken = ?',0,1])

キューをもう一回実行させる

BdrbJobQueue#release_job

上の合わせ技

BdrbJobQueue.find(:all, :conditions => ['finished = ? AND taken = ?',0,1]).each do |job|
  job.release_job
end
Posted in ruby, ruby on rails at 9月 30th, 2009. No Comments.

Amazon CloudFrontはAmazon S3上のファイルをキャッシュするソリューションです。

前回paperclipからS3を使う設定はわかったので、今回はさらにCloudFrontを利用するための設定です

前回のおさらい

has_attached_file :avatar,
  :storage => :s3,
  :s3_credentials => "#{RAILS_ROOT}/config/s3.yml",
  :path => ":attachment/:id/:style.:extension",
  :bucket => 'mybucket'

前回こんな感じでS3の設定をしました。

コンソールから利用してみると

user = User.new
user.avatar = File.open('/tmp/myface.jpg')
user.save

puts user.avatar.url #=> http://mybucket.s3.amazonaws.com/avatar/1/original.jpg

こんな感じでS3のURLがとれるようになっているはず。

s3_host_aliasを効かす

キャッシュサービスであるCloudFrontを利用する場合は、ホスト名を適宜変えるだけなので paperclipが勝手にやってくれるといいなぁ〜なんて思いました。

has_attached_file :avatar,
  :storage => :s3,
  :s3_credentials => "#{RAILS_ROOT}/config/s3.yml",
  :path => ":attachment/:id/:style.:extension",
  :bucket => 'mybucket',
  :s3_host_alias => "xxxxxxxxxxxx.cloudfront.net",
  :url => ":s3_alias_url"

オプションが2つ増えてます

  • :s3_host_alias は、エイリアスのFQDNを記述しておきます。
  • :url には「”:s3_alias_url”」を記述します。注意なのは、rubyのシンボルじゃなくて、文字列だってところです。ハマりました。
user = User.new
user.avatar = File.open('/tmp/myface.jpg')
user.save

puts user.avatar.url #=> http://xxxxxxxxxxxx.cloudfront.net/avatar/1/original.jpg

わーーこりゃ幸せだ。

余談

さらにサイト全体でアセット関係(public以下)もCloudFrontにしたければ

config/environments/#{RAILS_ENV}.rb 内で

config.action_controller.asset_host = "http://xxxxxxxxxxxx.cloudfront.net"

こうしとけば、image_path()やらimage_tag()やらjavascript_include_tag()やらを使ったときに、リクエスト先を書き換えてくれてさらに幸せ度アップ

Posted in ruby, ruby on rails at 9月 10th, 2009. No Comments.

PaperClipはとってもシンプルなActiveRecordで添付ファイルを管理するプラグインです。

http://github.com/thoughtbot/paperclip/tree/master

便利すぎて鼻血がでそうなプラグインです

使い方のおさらい

カラムを追加して

  class AddAvatarColumnsToUser < ActiveRecord::Migration
    def self.up
      add_column :users, :avatar_file_name,    :string
      add_column :users, :avatar_content_type, :string
      add_column :users, :avatar_file_size,    :integer
      add_column :users, :avatar_updated_at,   :datetime
    end

    def self.down
      remove_column :users, :avatar_file_name
      remove_column :users, :avatar_content_type
      remove_column :users, :avatar_file_size
      remove_column :users, :avatar_updated_at
    end
  end

モデルクラスで定義

class User < ActiveRecord::Base
    has_attached_file :avatar
end

とするだけですね。 詳細はRDocをみてください

Amazon S3を使う

PaperClipはストレージにS3を使うためのオプションがあります

has_attached_file :avatar,
    :storage => :s3,
    :s3_credentials => "#{RAILS_ROOT}/config/s3.yml",
    :path => ":attachment/:id/:style.:extension",
    :bucket => 'mybucket'

config/s3.ymlには

access_key_id: 456...
secret_access_key: 456...

こんな感じのアクセスキーとシークレットキーを置いておきます

productionでだけS3を使いたい

開発中はローカルで完結したい場合は

  has_attached_file :avatar, Proc.new {
    if RAILS_ENV == 'production'
      {
        :storage => :s3,
        :s3_credentials => "#{RAILS_ROOT}/config/s3.yml",
        :path => ":attachment/:id/:style.:extension",
        :bucket => 'mybucket'
      }
    else
      {}
    end
  }.call

こんな感じでいいかな

Posted in ruby, ruby on rails at 9月 9th, 2009. No Comments.

こんにちは、久々ですね

Railsでroutes.rbは本当に便利ですよね。

map.resources :users

なんて書くだけで、RESTFulなAPIが用意されます。

  • index
  • new
  • create
  • show
  • edit
  • update
  • destroy

に対応したURLをRailsがよしなにやってくれますよね。

しかし、こんなにたくさんのアクションはいらないー。わしゃcreateとshowだけでいいんじゃーなんて時もあったりします。そんなときは

map.resources :users, :only => ['create','show']

として、必要なアクションだけ列挙しましょう。

また、:exceptなんてオプションもあって、必要ないオプションを列挙します。

このへんにかいてありました

Posted in ruby, ruby on rails at 9月 4th, 2009. No Comments.

Railsが2.3.3に上がったので、gemをアップグレードしたりパッケージをアップデートしたりしているうちに
script/autospecが動かなくなってしまった。

解決方法はここを参考にしました

ZenTestを4.0系→4.1系にバージョンアップする場合の注意点
http://d.hatena.ne.jp/TrinityT/20090630/1246334665

  • autotest-railsをインストール
sudo gem install autotest-rails
  • ZenTest-4.0.0/lib/autotest/*をZenTest-4.1.3/lib/autotest/ へコピー
# Macではこんな感じ
sudo rsync -ave /Library/Ruby/Gems/1.8/gems/ZenTest-4.0.0/lib/autotest/ /Library/Ruby/Gems/1.8/gems/ZenTest-4.1.3/lib/autotest

これでまたscript/autospecが使えるようになりました。

Posted in ruby, ruby on rails at 7月 23rd, 2009. No Comments.

DBに配列やハッシュのデータを保存したいなと思って調べました。

ActiveRecord.serializeで実に簡単に実現できるんですね。
http://api.rubyonrails.org/classes/ActiveRecord/Base.html#M002229

If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object, then specify the name of that attribute using this method and it will be handled automatically. The serialization is done through YAML. If class_name is specified, the serialized object must be of that class on retrieval or SerializationTypeMismatch will be raised.

このメソッドでオブジェクトとして保存・取得したい属性の名前を指定すると、自動的に処理されるようになる。 YAMLでシリアライズされ、class_nameオプションが指定されると格納できるオブジェクトのクラスを制限できる。あるいはSerializationTypeMismatchの例外となる。

ポイント

  • シリアライズしたいカラムの型をtextにする
  • serializeメソッドでカラムを指定する
  • 格納できるクラスを指定可能
  • 内部的にはYAML

実例

さて、やってみます

まずマイグレーション内で、text型のdataというフィールドを用意する。
ここにシリアライズしたデータが格納されます。

class CreateItems < ActiveRecord::Migration
  def self.up
    create_table :items do |t|
      t.text :data
    end
  end
  def self.down
    drop_table :items
  end
end

そしてモデル内でシリアライズするフィールドを指定。

class Item < ActiveRecord::Base
  # シリアライズされたデータとして扱いたいフィールドを指定する
  serialize :data
end

script/consoleなどをおもむろに起動し、本当にオブジェクトが保存できるのか確認します。

# 配列を代入する
Item.create :data => [1,2,3]

item = Item.find(:all).last
p item.data #=>[1,2]
p item.data.class #=>Array

ちゃんと配列として取得できました。めでたしめでたし。

どうなってるの?

DBに格納する際にオブジェクトをYAML化しているようです。

検索したい場合

findしたい場合は、オブジェクトではなく文字列として評価されてしまうので Object#to_yamlを使うことになります。

Item.find_by_params([1,2,3]) # => これではレコードが見つからない
Item.find_by_params([1,2,3].to_yaml) # => 見つかる

使い勝手が悪い。。

格納する型を限定する

このままだとオブジェクトならなんでも格納できてしまいます。 そこで格納するクラスを指定してやります。

class Item < ActiveRecord::Base
  # 配列以外のクラスは格納しない
  serialize :data, Array
end

script/consoleで試してみます

# 配列の格納は成功
>> Item.create :data => ['bob','alice']
=> #<item id: 1, data: ["bob", "alice"]>

# ハッシュの格納は例外が発生する
>> Item.create :data => {:name => 'bob'}
ActiveRecord::SerializationTypeMismatch: data was supposed to be a Array, but was a Hash

これでかなり使い勝手が良くなりましたけど、配列の中身までは保証できないので、精神的安定のために使う程度でしょうか?

最後に

DBにシリアライズしたデータを保存しなければならない状況は無いにこした事は無いと思いますので 設計レベルで回避できる場合はそちらをおすすめします。

Posted in ruby on rails, 日記 at 5月 21st, 2009. No Comments.