ひげろぐ

技術者として仕事人としての思うところや覚え書きやらです
Home      Profile      Works     
2008-05-12

Rails2.0でauto_completeを使う

Railsにはもともとテキストフィールドにオートコンプリート機能を簡単に付けることのできる機構が用意されていたが、Rails2.0でこれを使おうとするといくつかつまずく点がある。

プラグインになっている

まずひとつめはページネーションなどと同じくRails2.0になって機能がプラグインに分離されたと言う点。
なのでプラグインをインストールする必要がある。

$ ruby script/plugin install auto_complete

CSRFプロテクションにブロックされる

次にRails2.0から付け加えられたCSRFプロテクションのためにこのまま使ってもエラーが出てしまうので、auto_completeプラグインのソースをいじってやる必要があるようだ。

それには以下のページを参考になる。

Rails 2.0.2で、auto_completeを使うと、 – Invalid Token

これで従来と同じように使えるようになる。

auto_completeの使い方自体は以下のページが詳しい。

検索可能なプルダウンリストを作る。 – ザリガニが見ていた…。

ほかぐぐってもたくさん出てくるのでauto_completeに関してこれ以上の説明は割愛する。

2008-04-21

acts_as_taggable_on_steroidsの使い方まとめ

モデルにタグ付けの機能がつくacts_as_taggableの機能追加版であるacts_as_taggable_on_steroidsの使い方を整理してみた。

  • 導入
  • モデルにタグ機能をつける – acts_as_taggable
  • モデルオブジェクトへのタグ付けと参照 – tag_list
  • タグの付いているモデルオブジェクトを取得する – find_tagged_with
  • タグの集計リストを取得する – tag_counts
  • タグクラウド – tag_cloud
  • タグのデリミタを変更する – TagList.delimiter
  • タグのキャッシュと使われなくなったタグの削除

導入

プラグインをインストールしてマイグレーションでデータベースの準備。

プラグインのインストール
$ ruby script/plugin install http://svn.viney.net.nz/things/rails/plugins/acts_as_taggable_on_steroids
データベースの準備
$ ruby script/generate acts_as_taggable_migration

これでマイグレーション用のファイルができるのでマイグレーション。

$ rake db:migrate

導入は以上。

モデルにタグ機能をつける – acts_as_taggable

モデルに「acts_as_taggable」と一行追加。

class Item < ActiveRecord::Base
  acts_as_taggable
end

これでそのモデルのオブジェクトにタグ付けできるようになる。

モデルオブジェクトへのタグ付けと参照 – tag_list

tag_listを介してタグ付けと付けたタグの参照ができる。

タグ付け

tag_listにタグのリストを代入するとモデルオブジェクトにタグが付く。
モデルオブジェクトをsaveするとタグも保存される。

item.tag_list = "テント, 設営器具, ペグ"
# => ["テント", "設営器具", "ペグ"]
# item.tag_list = ["テント, "設営器具", "ペグ"] でもよい
item.save

以下タグのリストに関する仕様。

  • タグのリストは配列でも文字列でも文字列で指定する
  • 文字列で指定した場合のデリミタのデフォルトはコンマ
  • 重複タグは自動的にユニークになる
  • タグの前後の空白は取り除かれる
  • 空タグ(nilや空文字列)は取り除かれる
タグの参照

付けたタグはtag_listで参照できる。返値は配列。

item.tag_list
# => ["テント", "設営器具", "ペグ"]

文字列で欲しい場合はto_s。デリミタで区切られた文字列で返される。

item.tag_list.to_s
# => "テント, 設営器具, ペグ"

デリミタが半角スペース以外の時にはデリミタとタグの間に半角スペースが入った形になる。

タグリストの変更 – add, remove, toggle

タグリストへの追加や削除を行うためのメソッドが用意されている。
モデルオブジェクトにすでにタグが付いているときなど、既存のタグリストに対して簡単にタグの追加や削除を行ったりすることができるので便利。

tag_list.addで追加。

item.tag_list.add("Coleman, コールマン")
# => ["テント", "設営器具", "ペグ", "Coleman", "コールマン"]

tag_list.removeで削除。
指定したタグがタグのリストの中に存在していなくてもエラーにはならない。

item.tag_list.remove("Coleman, コールマン")
# => ["テント", "設営器具", "ペグ"]

tag_list.toggleでトグルなんてこともできる。

item.tag_list.toggle("スチール製")
# => ["テント", "設営器具", "ペグ", "スチール製"]

item.tag_list.toggle("スチール製")
# => ["テント", "設営器具", "ペグ"]

タグの付いているモデルオブジェクトを取得する – find_tagged_with

find_tagged_withを使うと付いているタグからレコードを取得できる。
引数にはタグを指定する。

items = Item.find_tagged_with("テント")
# => 「テント」のタグが付いているレコードを取得

find_tagged_withには通常のfindと同じく:conditions、:order、:limitなどを設定することが可能。

items = Item.find_tagged_with("テント", :conditions => ["maker_id = 1"], :order => 'price', :limit => 20)
複数のタグを指定してレコードを取得

複数のタグを指定すると、デフォルトではそれらタグのどれかがついているレコードを取得する。
つまりOR条件での取得になる。

items = Item.find_tagged_with("テント, タープ")
# => 「テント」もしくは「タープ」のいずれかのタグを持つレコードを取得

AND条件っぽく取得したい場合はオプションで「:match_all => true」を指定する。
すると指定されたタグがすべて付いているレコードのみ取得する。

items = Item.find_tagged_with("テント, 本体", :match_all => true)
# => 「テント」でかつ「本体」両方のタグを持つレコードを取得
ページネーション

取得したデータのページネーションはwill_paginateを使っていると簡単。
find_tagged_withに続けてpaginateを使う。

コントローラには次のように書く。:conditionsや:orderなどはfind_tagged_withの方に含める。

@items = Item.find_tagged_with('テント', :order => :price).paginate(:page => params[:page], :per_page => 20)

ビューで次のように書く。

<%= will_paginate(@items) %>

参考:will_paginateに移行 – ひげろぐ

コントローラではpaginate_tagged_withというメソッドを使ってもいいのだがその場合は「:match_all => true」とあわせて使うことができないようだ。

@items = Item.paginate_tagged_with('テント', :page => params[:page], :order => :price, :per_page => 20)

タグの集計リストを取得する – tag_counts

タグクラウドのようなタグの一覧を作ったりするためにタグの使用回数が集計されたリストを取得することができる。

すべてのタグを取得

Tag.countsですべてのタグの集計を取得できる。

返値はTagモデルオブジェクトの配列。
個別のタグにはタグ名と使われている回数の情報が含まれており、それらはTagモデルのオブジェクトのメソッドで取得できる。

name タグ名
count タグの使われている回数
tags = Tag.counts
p "#{tags.first.name}(#{tags.first.count})"
# => テント(123)

複数のモデルにタグ機能を付けている場合はすべてのモデルに付いているタグが全部出てくる。
なのでこの取得の仕方はあまりしない方がいいだろう。

複数のモデルをまたいでタグの集計リストを取得するという明確な意図がある場合は別だが。

あるモデルのレコードに付けられているすべてのタグを取得

モデルのクラスメソッドでtag_countsというメソッドが使えるようになっているのでこれを使う。
そのモデルに付いているタグのみを取得するという以外はTag.countsと同じ。

tags = Item.tag_counts
p "#{tags.first.name}(#{tags.first.count})"
# => テント(123)
tag_countsのオプション

tag_countsにはいろいろオプションがある。

:start_at タグ付けされた日時が指定した日時以降のタグのみ対象に取得
:end_at タグ付けされた日時が指定した日時以前のタグのみ対象に取得
:conditions findと同じようにwhere条件を指定。モデルのカラムの他、tags.nameやtaggings.created_atなどのカラムも条件に含めることができる。
:limit 取得件数の制限。findで指定するのと同じ。
:order ソート条件の指定。findで指定するのと同じ。
:at_least タグのついているレコード数が指定した数値以上のタグのみを対象に取得
:at_most タグのついているレコード数が指定した数値以下のタグのみを対象に取得

これらを使うことでいろいろな条件で集計が可能。
タグごとにタグ付けされた日時が保存されているので例えば月間ごとにタグクラウドを出したりとかそんなこともできる。

関連と一緒に使う

こともできる。

Category.find_by_name('テント・タープ').items.tag_counts

楽でよろしい。

タグクラウド – tag_cloud

用意されているtag_cloudヘルパーを使うことでタグクラウドを簡単に作ることができる。
tag_cloudヘルパーはTagsHelperをインクルードすると使用可能となる。

tag_cloudヘルパーの引数にはTag.countsの返値とCSSクラスの配列を与え、ブロックを渡す。

サンプルで説明するとまずヘルパーをインクルードして

module ApplicationHelper
  include TagsHelper
end

ビューで次のように使う。

<% @tags = Item.tag_counts %>
<% tag_cloud(@tags, ['tag-x-small', 'tag-small', 'tag-medium', 'tag-large', 'tag-x-large']) do |tag, css_class| %>
  <%= link_to(tag.name, { :action => :tag, :id => tag.name }, :class => css_class) %>
<% end %>

対応するCSSは以下。

.tag-x-small { font-size:  80%; }
.tag-small   { font-size:  90%; }
.tag-medium  { font-size: 100%; }
.tag-large   { font-size: 120%; }
.tag-x-large { font-size: 150%; }

「@tags = Item.tag_counts」の箇所は実際にはコントローラ内に書くと思うがサンプルと言うことでひとつ。
CSSクラスの数は任意に変更でき大きさの段階を細かくしたい場合には増やせば、粗くしたい場合は減らせばよい。

タグのデリミタの変更 – TagList.delimiter

TagList.delimiterの値を変更することでタグのデリミタを変更できる。

半角スペースにしたい場合は次のように。

TagList.delimiter = " "

デリミタはいつでも変更できるが基本的に使っている途中で別のものに変更するとかは考えにくいのでconfig/environment.rbに書くのが普通だろう。

タグのキャッシュと使われなくなったタグの削除

パフォーマンスなどに関わるデータベース周りの話。

タグリストのキャッシュ

タグをモデルのテーブルに文字列としてキャッシュすることができる。
モデルについているタグを参照するときにタグテーブルにアクセスしにいかなくなるのでパフォーマンス的に助かる。

このキャッシュと使うためには単に「cached_tag_list」という文字列型のカラムをモデルのテーブルに作ればよい。

例えば以下のようなマイグレーションで。
ひとつのオブジェクトにつくタグの数が多い場合はカラムのサイズに注意すること。

class CacheItemTagList < ActiveRecord::Migration
  def self.up
    add_column :items, :cached_tag_list, :string
  end
end

カラムがあれば自動的にキャッシュされるようになる。
タグリストを変更した場合にはキャッシュの内容も自動的に変更されるので、キャッシュを使っていると意識する必要はまずない。

何らかの事情でカラムの名前を変えたければset_cached_tag_list_column_nameを使う。

class Item < ActiveRecord::Base
  acts_as_taggable

  set_cached_tag_list_column_name "tags_cache"
end

カラムが存在しなくてもエラーにはならない。
なければキャッシュが働かないと言うだけ。

使われなくなったタグを自動的に削除する

いったん作られたタグはモデルオブジェクトのタグリストから削除されて、まったく使われなくなってもtagsテーブルに残り続ける。
これが目障りなら以下のコードをどこかに書いておけば使われなくなったタグは自動的に削除される。

Tag.destroy_unused = true

config/environment.rb辺りに書いておけばよいと思う。
この削除はタグリストから削除されたタイミングで行われるので、destroy_unusedをtrueにする前に使われなくなったタグはやっぱりそのまま残り続ける。

まあパフォーマンスに影響が出ているとか何らかの問題がなければ残りっぱなしでほっといてもかまわんと思う。

参考

2008-04-11

BackgrounDRbでワーカーの自動起動を制御するメソッド

set_no_auto_load

ワーカーを作ってBackgrounDRbサーバを起動すると基本的にワーカーが自動的に起動して待機した状態になる。

必要がないのにずっとアイドリングしているのはリソースの無駄だ。
あるいは開発中のワーカーが勝手に起動してしまうのは邪魔だ。

そんなことが気になる場合にはワーカーのコード内でset_no_auto_loadを引数trueで呼び出す。
するとワーカーが勝手に起動しない。

class FooWorker < BackgrounDRb::MetaWorker
  set_worker_name :foo_worker
  set_no_auto_load true

  ...

これをtrueにしたりfalseにしたりしつつ、script/consoleなどから

MiddleMan.all_worker_info

で確認するとワーカーが起動していたりしていなかったりする様が確認できる。

ちなみに手動で新しいワーカーを起動するには以下のコードで。

MiddleMan.new_worker(:worker => :foo_worker)

new_workerの返値はworkerじゃないので注意。
起動したworkerを取得するにはMiddleMan.workerを使わないといけない。

参考

2008-04-08

BackgrounDRbのちょっとイマイチと思うところ

最近しばらく使っててイマイチと思ったところ。

開発時にサーバー再起動がめんどい

ワーカーのコード書き換えるたびにサーバー再起動しないといけないのでめんどい。

まあ開発の大部分はサーバーを動かさずにメソッドごとのユニットテストで仕上げていくので、めんどいのは最後の仕上げとか動作確認しつつの調整の時くらいなんだけど。
そんくらい我慢しやがれですか。そうですか。

しかしせめてrestartが欲しい。
自分で書きやがれですか。そうですか。

なんでBackgrounDRbのサーバーすぐ死んでしまうん?

MiddleMan.workerの引数を間違えてハッシュで与えたりとかして取得したワーカーで何かしようとするとあっさり死ぬ。
ワーカーが死ぬんじゃなくてBackgrounDRbサーバーが死ぬ。

例えば以下のコードで瞬殺。

worker = MiddleMan.worker(:worker => :hoge_worker)
worker.ask_status

これでエラー吐いて死亡。
ちなみにワーカー取得の正しいコードは

worker = MiddleMan.worker(:hoge_worker)

単に間違えるなって話ですか。
でも死んでしまうのはちょっとショックというか不安感をあおるなぁ。

あとサーバー再起動したときに再起動前に取得したワーカーを使っても同じように死亡。
サーバー再起動のタイミングとか環境によっては気を遣わないとだめっぽいですな。

もうちょっと不測の事態に強くなって欲しい。
さすがにワーカーのコードで何かエラーが起きただけで死んだりすることはないけど。

しかしながら

とまあこういった不満はちょっとあるけど、大旨は問題なく使ってます。
さらにバージョンが上がって練り上がってくるのが楽しみ。

2008-04-06

BackgrounDRbによるタスクのスケジューリング方法まとめ

2010年5月追記 今使うならdelayed_jobとかResqueとかがいいっぽいです。今動いてるものはまだしも今から作るものでBackgrounDrbはやめた方がたぶん幸せになれます。

BackgrounDRbのスケジューリング方法いろいろいじってみたのでそのまとめ。

以下の三種類がある。

  • タイマーによるスケジューラ
  • Cronスタイルのスケジューラ
  • Unixスタイルのスケジューラ

タイマーによるスケジューラ

ワーカーのコードに書くタイプのスケジューラ。
ワーカーのメソッドでタイマーを設定できる。

メソッド 説明
add_timer(n) 与えたブロックをn秒後に1回だけ実行
add_periodic_timer(n) 与えたブロックをn秒ごとに実行

createでadd_periodic_timerを呼び出せば定期的に繰り返すバッチ処理などを行うことが可能。
コードに書くだけで設定できるのでお手軽と言えばお手軽。

add_timerは1回だけ実行される。
create以外の場所でも使うことができるので、何らかのタスクを行った一定時間後に後始末処理を行うとかいった使い方ができそう。

サンプルコード – lib/workers/foo_worker.rb

タイマーでログにタスク実行の日時を書き込むだけのサンプル。

class FooWorker < BackgrounDRb::MetaWorker
  set_worker_name :foo_worker
  def create(args = nil)
    add_timer(10) do
      logger.info "#{Time.now} - oneshot"
    end

    add_periodic_timer(1) do
      hello("#{Time.now} - repeated")
    end
  end

  def hello(message)
    logger.info message
  end
end

Cronスタイルのスケジューラ

設定ファイルであるconfig/backgroundrb.ymlに設定を書くタイプのスケジューラ。
crontabに書く形式でスケジュールを指定できる。

「:schedules:」というセクションを設定ファイルに追加して定義する。
具体的にどう書くかは言葉での説明がしんどいのでサンプルコード参照。

サンプルコード – config/backgroundrb.yml
:backgroundrb:
  :port: 11006
  :ip: 0.0.0.0 

:schedules:
  :foo_worker:
    :hello:
      :trigger_args: 0 0 8 * * *
      :data: Hello from a cron style scheduler
    :good_night:
      :trigger_args: 0 0 22 * * *
      :data: Good night from a cron style scheduler at 22:00
    :good_night:
      :trigger_args: 0 0 20 * * *
      :data: Good night from a cron style scheduler at 20:00

ワーカー、タスク(メソッド)は複数書くことができる。
ただし同じタスクに関するスケジュール設定を複数書くことはできず、複数書くとエラーにはならないが一番最後に定義されたものだけが有効になる。
上の例だとgood_nightは20時タスクに実行される。

「:trigger_args:」にcrontabと同じ形でスケジュールを指定。
「:data:」はそのまま文字列としてタスクの引数になる。

Unixスタイルのスケジューラ

個人的にいまいちなじみがないけどUnixスタイルのスケジューラもある。
Cronスタイルのスケジューラと同じようにスケジューリングの設定をconfig/backgroundrb.ymlに書く。
設定ファイルにCronスタイルとUnixスタイルの書き方を共存させても問題はない。

:backgroundrb:
  :port: 11006
  :ip: 0.0.0.0 

:schedules:
  :foo_worker:
    :hello:
      :trigger_args:
        :start: <%= Time.now + 1.second %>
        :end: <%= Time.now + 10.minutes %>
        :repeat_interval: <%= 3.seconds %>
      :data: Hello from a unix style scheduler

「:start:」の時間が来ると繰り返しがスタートし「:repeat_interval:」ごとに実行される。
「:end:」の時間が来ると繰り返しは終了。
「:start:」と「:end:」の間の時間よりも「:repeat_interval:」が長いとタスクは一度も実行されずに繰り返しは終了する。

スケジュールの書き方の他はCronスタイルのスケジューラと同じ。

参考

近況

しばらくバイクで放浪してました。明石焼きとか赤福とか食ってきました。
それにしても天気予報でエイプリルフールとか勘弁していだだきたい。しかも自分の走ってた地域だけピンポイントでとか。

2008-03-30

HasFinderというRails pluginが激しく便利

nick – HasFinder — It’s Now Easier than ever to create complex, re-usable SQL queries

一言で説明すると「いろいろな条件のFindをモデルのメソッドにして便利に利用できる」というもの。

導入

とりあえずは入れて使う方法から。

$ sudo gem install has_finder

environment.rbに以下追記。

gem 'has_finder'
require 'has_finder'

使ってみる

早速メソッド作ってみる。

class Item < ActiveRecord::Base
  has_finder :stocked, :conditions => ["stock > 0"]
  has_finder :has_image, :conditions => {:has_image => true}
end

これだけなら別に普通にメソッド定義できるものをちょっと簡単に書けるようにしただけのことでフーンってかんじ。
だがこれらのメソッドをいろいろと組み合わせて柔軟に使っていけるところがHasFinderの真骨頂。

手始めとしてメソッドを連結して使ったりとか。

items = Item.stocked.has_image

絞り込み条件がいいかんじにDRYに。
この後にさらにfindで絞り込んだり並べ替えたりも可能。集計関数も行ける。

items = Item.stocked.has_image.find(:all, :conditions => ["maker_id = ?", params[:maker_id]], :order => 'price')
items = Item.stocked.has_image.count

関連といっしょに使っても問題なし。

class Category < ActiveRecord::Base
  has_many :items
end

class Item < ActiveRecord::Base
  has_one :category
  has_finder :stocked, :conditions => ["stock > 0"]
  has_finder :has_image, :conditions => {has_image => true}
end

category.items.stocked

ページネーションもおk。

items = Item.stocked.paginate(:page => params[:page], :conditions => ["category_id = ?", @category.id], :order => 'name', :per_page => 20)

メソッドに引数を取って条件の値の指定を動的に行うこともできる。

class Item < ActiveRecord::Base
  has_finder :price_under, lambda {|price| { :conditions => ["price < = ?" , price] } }
end

items = Item.price_under(10000)

ほかAssociation extentionsみたいなこともできる。

もうすぐ標準

RailsのEdgeでは本体に取り込まれている模様。

Ryan’s Scraps: What’s New in Edge Rails: Has Finder Functionality

has_finderではなくnamed_scopeで定義するようになってたり若干変わってるようだけど、もうすぐ標準で使えるようになるみたいですな。

2008-03-22

BackgounDRbでRailsの非同期処理とかバッチ処理とか

2010年5月追記 今使うならdelayed_jobとかResqueとかがいいっぽいです。今動いてるものはまだしも今から作るものでBackgrounDrbはやめた方がたぶん幸せになれます。

BackgrounDRbは名前の通りバックグラウンドでの処理に便利なプラグイン。

2007年の末に1.0がリリースされていた。
現在のバージョンは1.0.3になっている。

BackgrounDRbを使うとバックグラウンドの処理をコントローラからキックしてスタートさせたり、BackgrounDRbの持つスケジューリング機能によって実行することができる。
つまり時間のかかる非同期処理やバッチ処理に使うことができる。
ロギングやステータス確認の仕組みも整っていて、バックグラウンド処理のフレームワークとしてなかなかいいかんじになっている。

1.0になって以前のバージョンとは互換性がなくなっている部分もあるので、アップグレードするには若干コードの修正が必要になるかもしれない。
またWindows上のサーバで使えなくなっているようだ。(対応の予定はあるそうだ)

以下にインストールから軽く使うまでをまとめてみた。

インストール

BackgrounDRbはchronicとpacketというgemに依存しているのでRailsのプラグインとして入れる前にそれらを入れる必要がある。

$ sudo gem install chronic packet

BackgrounDRbのインストールは次のコマンドで。

$ ruby script/plugin install http://svn.devjavu.com/backgroundrb/trunk

ちなみに古いバージョンが入っている場合は事前に消しておいた方がいい。

セットアップ

使い始める前に rake backgroundrb:setup でセットアップが必要。
rakeコマンドでセットアップすると必要なファイルやスクリプトがRAILS_ROOT以下に配置される。

$ rake backgroundrb:setup
(in /var/www/bgrb_sample)
Copying backgroundrb.yml config file to /var/www/bgrb_sample/config/backgroundrb.yml
Copying backgroundrb script to /var/www/bgrb_sample/script/backgroundrb
Creating /var/www/bgrb_sample/lib/workers
Copying Worker Test helper file /var/www/bgrb_sample/test/bdrb_test_helper.rb

古いバージョンを入れていた場合はこれらのファイルも消すか待避させておく必要がある。
特にbackgroundrb.ymlとscript/backgroundrbに関しては互換性がないので。

config/backgroundrb.yml – 設定ファイル

セットアップ直後のconfig/backgroundrb.ymlの内容は次の通り。

:backgroundrb:
  :port: 11006
  :ip: 0.0.0.0 

このファイルで動作環境(production,development,test)やログに関する設定、スケジューリングの定義などを行うことができる。
なお必要があればerbのテンプレートに書くのと同じ要領で変数の埋め込みが可能。

特にいじらないでも動かすだけなら動くのでここではこのままで行く。

ワーカー(worker)の作成

バックグラウンドで行いたい処理の内容はlib/workers以下のワーカーのスクリプトに書く。
コントローラやモデルと同じようにジェネレータでワーカーのひな形を作ることができる。

$ ruby script/generate worker Foo
exists  lib/workers/
create  lib/workers/foo_worker.rb 

ジェネレータで作られたワーカーの内容は以下のごとく。

class FooWorker < BackgrounDRb::MetaWorker
  set_worker_name :foo_worker
  def create(args = nil)
    # method gets called, when new instance of worker is created.
  end
end 

createメソッドの内容はワーカーのインスタンスが作成されたときに実行される。
それ以外にメソッドを定義していくと、コントローラやスケジューリングによってキックすることが可能なタスクになる。

とりあえずログに実行した時刻と渡されたメッセージを記録するだけのタスクを作ってみる。

class FooWorker < BackgrounDRb::MetaWorker
  set_worker_name :foo_worker
  def create(args = nil)
    # method gets called, when new instance of worker is created.
  end

  def bar(message = 'nothing')
    logger.info "#{Time.now} #{message}"
  end
end 

なおメソッドの引数はタスク呼び出し側の仕様でひとつしか与えられない。
複数の値をタスクに渡したい場合は引数をハッシュなどにするといいだろう。

BackgrounDRbサーバの起動

ワーカーができたらBackgrounDRbサーバを起動。
以前のバージョンではrakeで起動していたが、スクリプト直叩きになった。

$ ruby script/backgroundrb start

-eで環境を指定できる。(設定ファイルに書くこともできるが、ここで指定することもできる)

$ ruby script/backgroundrb start -e production

何も指定しないとdevelopmentで動く。

ちなみに起動時に定義されているすべてのワーカーのインスタンスがひとつずつ作られる。(これを抑制する方法はあるがとりあえず割愛)

コントローラからタスクをキック

いわゆる非同期処理的使い方。

MiddleManというプロキシ的なクラスを使ってワーカーのインスタンスを取得してタスクを呼び出す。
そこ中の人とか呼ばないように。

worker = MiddleMan.worker(:foo_worker)
worker.bar('Hello from some controller')

これを例えばコントローラのアクションに書いておくと、そのアクションが呼び出されたときに非同期でタスクが走り始める。
非同期なのでタスクの終了を待たずにアクションの処理は次の行以降へと進んでいく。

スケジューリングによってタスクをキック

こちらはいわゆるバッチ処理的使い方。

いくつか方法があるが、ここではCronっぽく設定ファイルに定義する方法で。
config/backgroundrb.ymlの末尾に以下の内容を追加する。

:schedules:
  :foo_worker:
    :bar:
      :trigger_args: * * * * * *
      :data: Hello from a cron style scheduler

trigger_argsにcrontabに書くのと同じ形式でスケジューリングする。
dataはタスクに渡る引数。

つまりこの場合はFooWorkerのbarメソッドが引数 “Hello from a cron style scheduler” で毎秒呼び出される。

BackgrounDRbサーバの停止

以上のお試しが終わったら一応停止しておく。
起動の仕方がわかれば停止の仕方も察しがつくと思うが、その通りstopで。

$ ruby script/backgroundrb stop

開発中でもタスクの内容を変更するとBackgrounDRbサーバを再起動しないと行けないのでちょっとめんどい。
その時restartみたいな気の利いたオプションは用意されてないのでstopしてからstartしないといけないのでさらにめんどい。

以上

とりあえず動かすまで。
その他諸々に関してはまた別途まとめる。かもしれない。

ところで自分の環境の問題かもしれないけど、タスク呼び出しの取りこぼしなどがある気がするので(BackgrounDRbサーバ起動途中やワーカー作り途中でタスクをキックすると無言でスルーしてくれるような?)安定を求める人はもうちょっと待つか、その対策をして使った方がいいかもです。

参考

追記

一応Railsアプリのバッチ処理に関する以前の記事もよろしければどうぞ。

追記2

このあとBackgrounDRbに関する記事をいくつか書いてます。

2008-03-08

メールアドレスのバリデーションを行うRailsプラグイン

独自に実装すると割とめんどいメールアドレスのバリデーションを行うプラグイン二種。
validates_format_ofでやってたけどプラグインあるなら使った方が楽だなということで。

validates_email_format_of

Railsify! > Plugins > validates_email_format_of

単純に正規表現によるフォーマットチェックでバリデーションを行うプラグイン。

インストール
ruby script/plugin install http://code.dunae.ca/validates_email_format_of/
サンプルコード
class User < ActiveRecord::Base
  validates_email_format_of :email
end

validates_email_veracity_of

Railsify! > Plugins > validates_email_veracity_of

こちらはフォーマットチェックの他にドメインの存在チェックまで可能なプラグイン。
許可しないドメインも指定できるので、フリーメールのアドレスは不可、といったことも実装できる。

インストール
ruby script/plugin install http://svn.savvica.com/public/plugins/validates_email_veracity_of
サンプルコード
class User < ActiveRecord::Base
  validates_email_veracity_of :email, :mx_only => true,
                                      :invalid_domains => ["example.com", "example.co.jp"]
end

オプションをいろいろと指定できる。

オプション
オプション 内容 デフォルト
:message エラーメッセージ “is invalid.”
:domain_check ドメインの存在チェックを行うかどうか true
:mx_only ドメインの存在に加えてMXレコードまでチェックする false
:timeout ドメインルックアップのタイムアウトまでの時間(秒) 2
:fail_on_timeout タイムアウトした場合にバリデーション失敗とするかどうか false
:timeout_message タイムアウトでバリデーション失敗した場合のエラーメッセージ “domain is currently unreachable, try again later.”
:invalid_domains 許可しないドメインのリストを配列で指定する []
:invalid_domain_message 許可しないドメインのためにバリデーション失敗した場合のエラーメッセージ “provider is not supported, try another email address.”

正規表現比較

自作すると記号入りのアドレスとかへの考慮を忘れがちだったりするメールアドレスの正規表現。
微妙に決定版がないので困る。
二つのプラグインも別々のものを使っている。

validates_email_format_ofのコード。

  Regex = /
    ^(
      ([A-Za-z0-9]+_+)|
      ([A-Za-z0-9]+-+)|
      ([A-Za-z0-9]+.+)|
      ([A-Za-z0-9]+++)
    )*[A-Za-z0-9]+@
    ((w+-+)|(w+.))*w{1,63}.[a-zA-Z]{2,6}$
  /ix

validates_email_veracity_ofのコード。

/A([^@s]+)@((?:[-a-z0-9]+.)+[a-z]{2,})Z/i

ちなみに今まで使ってたコード。

/^[x01-x7F]+@(([-a-zA-Z0-9]+.)*[a-zA-Z]+|[d{1,3}.d{1,3}.d{1,3}.d{1,3}])$|^$/

メールアドレスに一致する正規表現より拝借。

Google先生に聞くと他にもいろいろ見つかるけど、どんな正規表現が一番使われているんだろうな。

2008-02-27

Rails WidgetsのTabnavでさくっとタブナビゲーション

またネタもとがつくるぶなわけですが。

つくるぶガイドブログ: Rails で Yahoo 検索 API + Widgets Tabnav でタブナビゲーション

タブナビゲーションほしいぜタブナビゲーション、とちょっと前から思っていたので早速試してみたらあっさりタブナビゲーションができてしまった。スゲー。
世の中どんどん便利になr

で、元記事がわかりやすいので感動を伝える以外何も書くことはないわけですが、せっかくなのでひとつだけ。

タブナビゲーション用の部分テンプレートのサンプルコードを一部抜粋。

<%
render_tabnav :yahoo_search,
              :generate_css => true do 

ここで :generate_css を true にすることで出力されるHTMLソース内にタブの外観を整えるためのCSSが吐かれる。
そのCSSをコピペして別のcssファイルに保存してやるとタブの外観をカスタマイズするのに便利。

そうしたらもうCSSを吐いてもらう必要はないので :generate_css を true にする必要はないのでそのオプションは削ってよい。
デフォルトが false のようなので、指定をなくすだけでおk。

<%
render_tabnav :yahoo_search do 

以上でした。

Tabnavの他にもいくつか機能があるようなので暇を見て試してみようかな。

参考

SeeSaw | Rails Widgets

2008-02-22

will_paginateに移行

will_paginateのアップデートによってインストール方法など一部の情報が古くなっています。

関連:will_paginateもgithubに行ってた

この間Rails2.0に移行したときにいったん放置したwill_paginateへの移行を行ったのでメモ。
それほど手間ではなかった。

ぐーぐる先生が教えてくれたページやREADMEを読みつつ作業。

プラグインのインストール

さくっと。

$ script/plugin install svn://errtheblog.com/svn/plugins/will_paginate

コントローラの修正

以下のようにコードを修正。コメントアウトしたものは修正前のもの。

def list
  # @pages, @items = paginate(:items, :conditions => ["deleted = 0"], :order => 'created_at desc', :per_page => 10)
  @items = Item.paginate(:page => params[:page], :conditions => ["deleted = 0"], :order => 'created_at desc', :per_page => 10)
end

モデルにpaginateというメソッドがつくっぽい。
引数の :page で表示するページ数を指定して、:per_page でページあたりの表示件数を指定する。
上記のコードでは表示するページ数はクエリのpageというパラメータから取るかんじ。

またpaginate_by_xxxというメソッドも使えるようになるので、

@items = Item.paginate_by_user_id @user.id, :page => params[:page]

のような使い方もできるようだ。

ビューの修正

ヘルパーを使うとそこにページネーションが表示される。

<%= will_paginate(@items) %>

これで終わり。

will_paginateってのはまあクラシックなページネーションのpagination_linksみたいなもんですな。

従来のページ管理オブジェクトが持っていたようなメソッドもActiveRecordオブジェクトのメソッドに付加される、あるいはもともとあるメソッドで代用できる模様。
互換性は意識されてないようなので、ページ管理オブジェクトを使ってがんばってオリジナルのページネーションを作っていた人はいろいろ書き換える必要がありそう。

メソッド 内容
size 総件数。
page_count 総ページ数。
per_page 1ページあたりの表示件数。
previous_page 現在のページの前のページ番号。存在しない場合はnil
next_page 現在のページの次のページ番号。存在しない場合はnil
offset 現在のページの最初のレコードのオフセット。

例えば以下のようにビューの中で使ったりできる。

<%= @items.page_count %>

will_paginateのオプション

オプションを与えると外観をカスタマイズできる。

オプション 内容 デフォルト値
:class ページネーションを囲むdivタグ(コンテナ)につくCSSクラス名 “pagination”
:prev_label 前のページへのリンクのラベル “≪ Previous”
:next_label 次のページへのリンクのラベル “Next ≫”
:inner_window 現在のページをの両隣何ページ分までリンクを表示するか 4
:outer_window 1ページ目と最後のページからそれぞれ何ページ分までリンクを表示するか 1
:separator ページとページの間のセパレータ ” ” (半角スペース)
:param_name ページ数を表すパラメータの名前。例えば “p” にすると “p=1″ とか “p=3″ とか言うクエリパラメータになる。 “page”
:params 各ページへのリンクにつける追加パラメータ。例えば :controller => “foo”, :action => nil など nil
:renderer リンクを描画するレンダラークラスを指定できる WillPaginate::LinkRenderer
:page_links 各ページへのリンクを表示するか否か。falseにすると各ページへのリンクは表示されず、前と次のページへのリンクのみ表示される。 true
:container ページネーションをdivタグ(コンテナ)で囲むか否か。 true
:id ページネーションを囲むdivタグ(コンテナ)のidを指定できる。
文字列でなくtrueを指定するとモデルのクラス名を小文字化したものに “_pagenation” がついたものになる。
nil

:inner_windowと:outer_windowの挙動が若干おかしいような気がしないでもない・・・

以下サンプルコード。

< %= will_paginate(@items, :prev_label => '前', :next_label => '次', :params_name => 'p') %>

前後のページに進むリンクのラベルを変更し、クエリストリングのページ番号を表すパラメータを「p」に変更している。
なお:params_nameを変えたらコントローラ側でも変更が必要になるので注意。

@items = Item.paginate(:page => params[:p], :per_page => 10)

スタイルを当てる

READMEに載っている次のスタイルを当てるとちょっとかっこよくなる。

.pagination {
  padding: 3px;
  margin: 3px;
}
.pagination a {
  padding: 2px 5px 2px 5px;
  margin: 2px;
  border: 1px solid #aaaadd;
  text-decoration: none;
  color: #000099;
}
.pagination a:hover, .pagination a:active {
  border: 1px solid #000099;
  color: #000;
}
.pagination span.current {
  padding: 2px 5px 2px 5px;
  margin: 2px;
  border: 1px solid #000099;
  font-weight: bold;
  background-color: #000099;
  color: #FFF;
}
.pagination span.disabled {
  padding: 2px 5px 2px 5px;
  margin: 2px;
  border: 1px solid #eee;
  color: #ddd;
}

paginationというクラス名はヘルパーのオプションで変更してたらそれにあわせること。

参考

- will_paginate README
- Ruby on Rails 2.0.1に対応 – idesaku blog

copyright brass.to | powered by WordPress ME