delayed_jobを使う時の注意

delayed_jobを使う時、非同期に実行したいメソッドの前にdelayというメソッドを入れます。
例えば、TestBatchクラスのexecというクラスメソッドがあり、それを非同期に実行させたい場合、

TestBatch.delay.exec

となります。
ただここで注意が必要です。ここでいうexecというメソッドをsendというメソッドにするとエラーをおこします。何故でしょうか?エラーログを見て原因を見た時にdelayed_jobのコードの以下の部分をみて納得しました。

module Delayed
  class PerformableMethod
    attr_accessor :object, :method_name, :args

    delegate :method, :to => :object

    def initialize(object, method_name, args)
      raise NoMethodError, "undefined method `#{method_name}' for #{object.inspect}" unless object.respond_to?(method_name, true)

      if object.respond_to?(:persisted?) && !object.persisted?
        raise(ArgumentError, "job cannot be created for non-persisted record: #{object.inspect}")
      end

      self.object       = object
      self.args         = args
      self.method_name  = method_name.to_sym
    end

<途中省略>

    def perform
      object.send(method_name, *args) if object
    end

delayed_job内にもsendメソッドというのがあり、それでエラーに・・・。
sendというメソッド名にするなという話ではありますが、、、
ちょっと戸惑いましたが、原因がわかえればなるほどなーという感じです。

HerokuのpostgresqlのDBをリセットする

以下のコマンドを入れるとHerokuのpostgresqlがリセットされる


heroku pg:reset DATABASE
確認を求めるメッセージが表示されるので、heroku上のアプリの名前を入力する

これでデータベースが初期状態(テーブルがない状態)になる。
テーブルを再度作り直せば、データもまっさらな状態になる

heroku run rake db:migrate

rubotyを使って簡単ボット作成

botと言えばhubotが有名ですが、このhubotをと同じbotrubyで作れるライブラリがあります。
それがrubotyです。

このページに行くとサンプルをHerokuにディプロイして動かすというのが予め用意されています。
実に親切ですね!
サンプルではslackを使っているので、私も試しに使ってみました。
で、まー、ディプロイしてちょっと設定書くだけで動くので手軽だなーという感じでした。
サンプルがあるから当たり前なんですが、でもこの手軽に動くのが見れるというのは結構大事な気がします。やっぱりまずは動いているのを見れた方が良いですからね。

RailsでHTMLメール実装するときにHelperメソッドを使いたい場合

RailsのHTMLメールのテンプレートないでApplicationHelperのメソッドを使おうと思ったが、デフォルトの状態では使えません。
じゃ、どうするかと言えば、app/mailers/以下のクラスに以下の一行を追加すれば良いのです。

HogeMailerというクラスがあるとして

class HogeMailer < ActionMailer::Base
  add_template_helper(ApplicationHelper)

  def send
    ~~~~
  end
end

ActiveRecordのrewhereメソッド

ちょっとどのような状況で使うのか、まだピンと来ないけどActiveRecordにはrewhereっていうメソッドがある。
これを使うと前に宣言していたwhere条件をoverrideすることができる。

#rewhereを使ってもその前にpointっていうカラムで条件指定してないからoverrideされない
Todo.where(title: 'test').rewhere(point: 10)
 -> SELECT "job_cards".* FROM "job_cards"  WHERE "job_cards"."type" IN ('Todo') AND "job_cards"."title" = 'test' AND "job_cards"."point" = 10

#rewhereでtitleの指定を上書き
Todo.where(title: 'test').rewhere(title: 'hoge')
  Todo Load (0.1ms)  SELECT "job_cards".* FROM "job_cards"  WHERE "job_cards"."type" IN ('Todo') AND "job_cards"."title" = 'hoge'

ActiveRecordのmergeメソッドについて

ActiveRecordでテーブルのjoinをする場合以下のように書くと思います。

User.joins(:auth_providers).all.where(auth_providers: { provider: 'facebook' })

発行されるSQLは次のようになります。


SELECT "users".* FROM "users" INNER JOIN "auth_providers" ON "auth_providers"."user_id" = "users"."id" WHERE "auth_providers"."provider" = 'facebook'

これとは別にmergeメソッドを使う書き方もあります。
まずはモデルにscropを定義します。

class AuthProvider < ActiveRecord::Base
  belongs_to :user

  scope :facebook, -> { where(provider: 'facebook') }
  scope :developer, -> { where(provider: 'developer') }
end

でこのscopeとmergeメソッドを使って書くと次のようになります。

 User.joins(:auth_providers).all.merge(AuthProvider.facebook)

で、発行されるSQLは次のようになります


SELECT "users".* FROM "users" INNER JOIN "auth_providers" ON "auth_providers"."user_id" = "users"."id" WHERE "auth_providers"."provider" = 'facebook'

同じになりますね。
最初に書いた方のやり方で「.where(auth_providers: { provider: 'facebook' })」と書いているところをmergeメソッドで「.merge(AuthProvider.facebook)」で書くかどうかとういう話ですが、mergeメソッドを使った方が短く書けるから良い気もします。
皆様はどうでしょか?

rubyでメソッドの処理速度をはかるとき

rubyで処理速度をはかる時は以下のようにbenchmarkモジュールを使います。
で、この前Railsのコミットログを眺めていたら、gsubメソッドをtrに置き換えたというのがあったので、
試しに以下のように処理の計測を行ってみました。

require 'benchmark'

hoge = 'aaaaaabbbbbceerwrrweqgaga123rgurwfkja'

n = 50
Benchmark.bm do |x|
  # fast
  x.report { n.times { hoge.tr('aa', 'xx') } }
  # slow
  x.report { n.times { hoge.gsub('aa', 'xx') } }
end

で、何回か流したのですが、結果は以下のようにtrの方が確かに早いですね。


1回目
user system total real
0.000000 0.000000 0.000000 ( 0.000039)
0.000000 0.000000 0.000000 ( 0.000166)
2回目
user system total real
0.000000 0.000000 0.000000 ( 0.000041)
0.000000 0.000000 0.000000 ( 0.000153)
3回目
user system total real
0.000000 0.000000 0.000000 ( 0.000038)
0.000000 0.000000 0.000000 ( 0.000134)
4回目
user system total real
0.000000 0.000000 0.000000 ( 0.000037)
0.000000 0.000000 0.000000 ( 0.000113)

一番左の値が実際の計測時間になります。
で、それ以外にも書き方によっては速い遅いがあって以下のようにコードを書き換えて幾つか検証してみました

require 'benchmark'

hoge = 'aaaaaabbbbbceerwrrweqgaga123rgurwfkja'

n = 50
Benchmark.bm do |x|
  # fast
  x.report('fast') { n.times { hoge.tr('aa', 'xx') } }
  # slow
  x.report('slow') { n.times { hoge.gsub('aa', 'xx') } }
end

Benchmark.bm do |x|
  # fast
  x.report('fast') { n.times { hoge.sub('aa', 'xx') } }
  # slow
  x.report('slow') { n.times { hoge.gsub('aa', 'xx') } }
end

h = { a: 1, b: 2, c: 3 }
Benchmark.bm do |x|
  # fast
  x.report('fast') { n.times { h.each_key { |k| k } } }
  # slow
  x.report('slow') { n.times { h.keys.each { |k| k } } }
end

Benchmark.bm do |x|
  # fast
  x.report('fast') { n.times { a = 1; b =  2  } }
  # slow
  x.report('slow') { n.times {  a, b = 1, 2  } }
end

結果は


user system total real
fast 0.000000 0.000000 0.000000 ( 0.000092)
slow 0.000000 0.000000 0.000000 ( 0.000132)
user system total real
fast 0.000000 0.000000 0.000000 ( 0.000058)
slow 0.000000 0.000000 0.000000 ( 0.000134)
user system total real
fast 0.000000 0.000000 0.000000 ( 0.000018)
slow 0.000000 0.000000 0.000000 ( 0.000019)
user system total real
fast 0.000000 0.000000 0.000000 ( 0.000005)
slow 0.000000 0.000000 0.000000 ( 0.000034)

なるほど、勉強になります。