[Rails][非同期処理]delayed_job使い方 その3

[Rails][非同期処理]delayed_job使い方 その3


[関連記事]
[Rails][非同期処理]delayed_job使い方
[Rails][非同期処理]delayed_job使い方 その2


今回は非同期実行させるための方法について簡単にまとめます。
環境周りの設定は[Rails][非同期処理]delayed_job使い方 その2を見てください。(以前の記事をちょっと修正してあります。)


テスト環境について


以下の非同期処理の実行テストは以下の環境で行っております。


  1. Windows XP Pro SP3
  2. Rails 2.3.8
  3. Ruby 1.8.6
  4. Railsの起動モード ... development

非同期処理[rake jobs:work]でプロセスを立ち上げての実行となります。


delayed_jobで非同期実行させるには


delayed_jobで非同期実行の指定には3つのパターンがあります。


  1. send_laterで指定する
  2. send_atで指定する
  3. handle_asynchronouslyで指定する

それぞれ細かく解説していきます。


send_laterで指定する


ClassName.send_later(:method_name, *args)

*argsにはそのメソッドの引数を指定します。ハッシュで渡すこともできますし、好きな形式で渡すことが出来ます。


例1)
UserMailer.send_later(:send_user_password, hoge, hoge2, hoge3 )
=> UserMailer内のsend_user_paswordにhoge, hoge2, hoge3という引数が渡されて実行されます。
例2)
UserMailer.send_later(:send_user_password, { :hoge => 'hoge1', :hoge2 => 'hoge2' })
=> UserMailer内のsend_user_passwordにHashで引数を渡されて実行されます。
※send_user_password( a )というメソッドであればa[:hoge1]で値を取得できます

[注意点]
send_laterで非同期実行する場合は、該当のメソッドをself.send_user_passwordのようにselfメソッドで作成する必要があります。
つまり、実行する際には必ずClassName.send_later(:method_name)という指定の仕方になります。
これをしないとundefined methodが表示されます。


send_atで指定する


ClassName.send_later(time, :method_name, *args)

実行時刻を明示的に指定して実行する際に利用します。
timeには実行して欲しい時間を指定します。delayed_jobはtimeの時刻を超えているJobが登録されていた場合はそのJobを実行します。


handle_asynchronouslyで指定する


[注意点]
若干癖があるのがhandle_asynchronouslyです。
handle_asynchronouslyで非同期実行する場合は、該当のメソッドは[self]を指定してはいけません。
self.send_user_passwordのようにselfメソッドで作成してある場合において
起動時に`alias_method': undefined method `method_name' for class `ClassName' (NameError)
といわれて起動できません。※enviromentにhandle_asynchronouslyを指定した場合。


development.rbの末尾に以下のconfig.after_initializeを追加します。


config.after_initialize do
HogeWriter.handle_asynchronously(:file_write)
end

悪い例


class HogeWriter
def self.file_write(file_name)
File.open("#{RAILS_ROOT}/tmp/#{file_name}",'w') do |f|
f.puts "=============================="
f.puts Time.now
f.puts "=============================="
end
end
end

この記述方法でdevelopmentモードでサーバ起動はできません。


良い例


class HogeWriter
attr_accessor :my_file_name

def file_write
File.open("#{RAILS_ROOT}/tmp/#{self.my_file_name}",'w') do |f|
f.puts "=============================="
f.puts Time.now
f.puts "=============================="
end
end
end

上記を呼び出すときは以下のようにします。
※実験時には適当なコントローラに設定しましょう。


hoge_writer = HogeWriter.new
hoge_writer.my_file_name = "hogehoge.txt"
hoge_writer.file_write

これで自動的にfile_writeが非同期実行されるようになります。


引数付きのメソッドをhandle_asynchronouslyで実行したい場合


こちらも可能です。まずは通常通り以下のようにプログラムを作成します。


class HogeWriter

def file_write(file_name)
File.open("#{RAILS_ROOT}/tmp/#{file_name}",'w') do |f|
f.puts "=============================="
f.puts Time.now
f.puts "=============================="
end
end
end

以下のようにして、非同期実行メソッドを呼び出します。


hoge_writer = HogeWriter.new
hoge_writer.file_write("hogehoge.txt")

handle_asynchronouslyの指定方法について


本来であれば、productionモード時のみに非同期にしたいと思われますので、
RAILS_ROOT/config/enviroments/production.rbの末尾に以下のように追加します。


config.after_initialize do
ClassName.handle_asynchronously(:method_name)
...
end

なお、非同期実行させない場合は上手くいくのに、非同期実行させた場合はコケるといった場合が多々ありますので、developmentでも一度は非同期実行させるといいと思います。ローカル内にVMとかでLinux系環境が動かせる場合はそっちで実行させるのがオススメです。


また、handle_asynchronouslyは各種Class内に記述することも出来ます。
その場合はどのような環境でも常に非同期実行されるようになります。
※if ENV['RAILS_ENV']=='production'という風に書いてもいいかもしれませんが...


class HogeWriter

def file_write(file_name)
File.open("#{RAILS_ROOT}/tmp/#{file_name}",'w') do |f|
f.puts "=============================="
f.puts Time.now
f.puts "=============================="
end
end

handle_asynchronously(:file_write)
end

この場合はClassNameの指定は不要です。


その他


実行時刻の指定などはできません。
当たり前だけれどもソースいじればできますので、いじる方は
delayed_jobのインストールディレクトリ/lib/delayed/message_sending.rb
を変更してみてください。


気になる点


priority(優先度)に関して


ソースコードを一通り見たんだけれども、message_sending.rbなどにもpriority(優先度)指定が
できる箇所はなかった。send_atもpriority(優先度)が0でハードコードされていたので、
うーむという感じ。Jobの起動などはしっかりとpriority(優先度)の部分のコードが書かれていたので
未実装機能なだけなのだろうか...
あと、現時点でgitに2.1系のpreがアップされていましたので、興味のある方は見てみてください。
ざっと見た感じだとpriority(優先度)指定が実装されている感じです。
http://github.com/collectiveidea/delayed_job/downloads


delayed_jobのプロセスがストーンと落ちる現象


たまに何も吐かずに落ちることがある様子。プロセス監視して落ちたら自動で再起動するようにするしかないかな...

スポンサーサイト

[Rails][ActionMailer]ActionMailerを使う上で便利な設定

[Rails][ActionMailer]ActionMailerを使う上で便利な設定


メールの設定をRailsアプリを作るたびにするのは凄くメンドクサイ。
そんな思いから、色々漁った結果以下のような設定をするようになってきています。
Redmineの設定をちょっと弄ったパクリものですがね...


email.ymlでメール設定を一元管理する


各enviroment.rbに環境ごとに設定をいれるのは非常にめんどうなので、
以下のようにemail.ymlでメール設定を管理するようにします。
各詳細はRedmineプロジェクトのメール設定を参照するといいと思います。
以下はRAILS_ROOT/config/以下にemail.ymlという名前で保存します。


production:
delivery_method: :smtp
smtp_settings:
address: 127.0.0.1
port: 25
domain: localhost
# authentication: :plain
# user_name: "hogehoge"
# password: "hogehoge"

staging:
delivery_method: :smtp
smtp_settings:
address: 127.0.0.1
port: 25
domain: localhost
# authentication: :plain
# user_name: "hogehoge"
# password: "hogehoge"

development:
delivery_method: :smtp
smtp_settings:
address: [your mail server domain]
port: 25
domain: [domain here]
authentication: :login
user_name: "hogehoge"
password: "hogehoge"

test:
delivery_method: :test
smtp_settings:
address: localhost
port: 25
domain: localhost
authentication: :none

initializersへのファイルの設置


RAILS_ROOT/config/initializersへ以下のソースをmail_setting.rbという名前で設置します。


# Email Settingファイル読み込み用
filename = File.join(File.dirname(__FILE__), '..', 'email.yml')
if File.file?(filename)
mailconfig = YAML::load_file(filename)

if mailconfig.is_a?(Hash) && mailconfig.has_key?(Rails.env)
# Enable deliveries
ActionMailer::Base.perform_deliveries = true
ActionMailer::Base.deliveries = [] if Rails.env == "test"
mailconfig[Rails.env].each do |k, v|
v.symbolize_keys! if v.respond_to?(:symbolize_keys!)
ActionMailer::Base.send("#{k}=", v)
end
end
end

これで設定は完了。後は環境に合わせてemail.ymlを設定するだけです。
以下によく調べる情報をまとめておきます。


Iso2022jpMailerの設置


まぁ日本語メールを送る際には有名なIso2022jpMailerをapp/model以下に設置します。


require 'nkf'
class Iso2022jpMailer < ActionMailer::Base
@@default_charset = 'iso-2022-jp'
@@encode_subject = false

def base64(text, charset="iso-2022-jp", convert=true)
if convert
if charset == "iso-2022-jp"
text = NKF.nkf('-j -m0', text)
end
end
text = [text].pack('m').delete("\r\n")
"=?#{charset}?B?#{text}?="
end

# 本文(body)をiso2022jpへ変換
# タイトル(subject)は自分でやること
def create! (*)
super
@mail.body = NKF::nkf('-j', @mail.body)
return @mail
end
end

Iso2022jpMailerを継承したクラスの作成


クラス名は適当に設定します。ruby script/generate mailerで自動生成した後に修正するのが楽っぽい。
また、あんまりよくわかってないがinitialize_defaultsで初期化するとメール送るときに楽。


class SampleMailer < Iso2022jpMailer
DEFAULT_MAIL_FROM = "namakesugi.com"

def hoge(data)
set_subject("hogeほげ")
@recipients = "メール送信先"
@body[:data] = data
end

private
def initialize_defaults(method_name)
super
@initial_language = 'ja'#current_language
@from = DEFAULT_MAIL_FROM
@sent_on = Time.now
end

def set_subject(data)
@subject = base64(data)
end
end

app/views/sample_mailer以下にhoge.erbを設置すればOK。
hoge.erb内では@dataで引数dataを引っ張ってこれる。@body[:xxx] = dataとすれば@xxxで引っ張ってこれる。
@body[:***]を複数指定することも可能。


Unitテストを書く


テスト内のsetupで以下を実行するようにする。
一応mail_setting.rbで初期化はしているが、蓄積されていくので、必ず初期化が必要っぽい。


require 'test_helper'
require 'nkf'

class SampleMailerTest < ActionMailer::TestCase
def setup
# テスト時に配送したメールの配列を保存する。
# +初期かも兼ねている
ActionMailer::Base.deliveries = []
end

test "hoge" do
data = "xxxxx" # 仕様に合わせてdataを設定する
AlertMailer.deliver_hoge(data)
assert_equal 1, ActionMailer::Base.deliveries.length
# メールの文面を取得
mail_data = ActionMailer::Base.deliveries.first
# 取得した文面のチェックを適当にする
# タイトルのチェック
assert_equal NKF.nkf('-j -m0', "hogeほげ"), mail_data.subject
# その他必要な項目のチェックをする
end
end

チェックできる項目の例


  • mail_data.subject => メールのタイトル(NKF.nkf('-j -m0')等で変換することutf変換でも良い)

  • mail_data.body => メールの本文(NKF.nkf('-j -m0')等で変換することutf変換でも良い)

  • mail_data.from => メールの送信元(Arrayなので注意)

  • mail_data.to => メールの送信先(Arrayなので注意)

[Flex4] NumericStepperのプロパティ

[Flex4] NumericStepperのプロパティ


spark.components.Spinnerというクラスを継承して作られた
NumericStepper。その名前の通り数値を上下ボタンで変更できる機能。
意外に便利だが、プロパティを間違えると思わぬ罠に嵌る機能です。
また、最大値を変化量の倍数にしないと動き的に見ていておかしい感じになるので注意です。


継承関係


主要部分だけ NumericStepper => Spinner => Range
つまり、わからないところがあればSpinnerを見ればいいし、SpinnerでわからなければRangeを見ればいいのです。


Package : spark.components.NumericStepper


概要は上記の通り。サンプルはFlex4のリファレンスであるここを見るといいです。


このページの末尾にあるサンプルをみると数値の動きが1.5 => 3.0 => 4.5 => 6.0 => 7.5 => 9.0 => 10.0 => 0.0と変化していきます。
これはこのサンプルのカウントの上がり幅が1.5だからなんだけれども、spinnerクラスのデフォルトが最小値0, 最大値10で設定されているため、上がり幅よりも最大値が優先されるため、9.0の次が10.0になってしまっている。
まぁ当たり前の制御なんだけれども、エンドユーザにとっては?な動きになりそう。


9.0の次を10.5にしたければ、最大値を10.5に設定しておけばいいといえばいい話なんだけれどもね...


NumericStepperのパラメータ


maximum:Number ... 数値の最大値(デフォルト10)


minimum:Number ... 数値の最小値(デフォルト0)


value:Number ... 数値の初期値(デフォルト0)


stepSize:Number ... 数値の変化量を指定します


snapInterval:Number ... 有効な値を指定します(後述)


allowValueWrap:Boolean ... 数値が最大値又は最小値に達したときの制御方法(デフォルトfalse)

trueを指定すると最大値の次は最小値に変化するようになります。
falseを指定すると最大値・最小値に達した場合は数値が変化しなくなります。
上記サンプルはこの指定がtrueになっているため、10の次が0になるように動作しています。


発生するイベント


Changeイベントが数値が変化するたびに発生します。
その為、下記のようにEventListenerを登録するとChangeイベントごとに
実行するメソッドを指定できます。


hoge.addEventListener(Event.CHANGE, numericStepperChanged);


snapIntervalに関して


特に意図がなければ、ここの値はかならずstepSizeと合わせるようにしてください。
例えば、stepSizeを1.1にしてsnapIntervalを0に指定した場合、3.3と表示して欲しいところは
[3.3000000000000003]となります。
また、7.7にしたいところは7.6999999になります。
2進数の世界では常識ですかね...
なんだろ、このプロパティいるのかな...?


その他使い方について


maximum, minimumなどの上に書いたプロパティは必ず指定したほうが
後で見る人にとってもわかりやすいと思います。
デフォルト値なんて覚えてられないしねー


サンプル


初期値5.5で1.1ごと変化します。最大値は11で最小値は0です。最大値・最小値より変化させようとするとそれぞれ最小値・最大値に値が変化します。
※ほとんど丸パクリです。



<?xml version='1.0' encoding='UTF-8'?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx">
<s:Panel title="Spark NumericStepper Control Example"
width="75%" height="75%"
horizontalCenter="0" verticalCenter="0">
<s:VGroup left="10" right="10" top="10" bottom="10">
<s:NumericStepper id="myNS" width="55" value="5.5" stepSize="1.1"
snapInterval="1.1" maximum="11" minimum="0" allowValueWrap="true"/>
<!-- Label that displays the current value of the Spark NumericStepper -->
<s:Label color="purple" text="Current value = {myNS.value}"/>
</s:VGroup>
</s:Panel>

</s:Application>
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。