taslamの日記

>>mizincogrammerに移転しました。こちらは、今後更新されません。<<

2008-03-31

[][][] ActiveRecord::Base#to_xmlをキャッシュ

to_xmlが遅いので適当に作ってみた。developmentモードではキャッシュ無効。

ただし

インストール

script/plugin install http://taslam-plugins.googlecode.com/svn/trunk/acts_as_xmlcaching/

設定

# config/environment.rb
memcache_options = {
   :compression => false,
   :debug => false,
   :namespace => "foo-#{ENV['RAILS_ENV']}",
   :readonly => false,
   :urlencode => false
}
memcache_servers = [ '192.168.0.1:11211' ]
ActiveRecord::XmlCaching.servers = memcache_servers
ActiveRecord::XmlCaching.options = memcache_options
ActiveRecord::XmlCaching.prefix = :application_name

モデルに追記

class Entry < ActiveRecord::Base
  # もし#to_xmlをオーバーライド等してたら、そこより後に書く
  acts_as_xmlcaching
end

2008-03-11

[][][]JpMailerプラグイン

これはなに?

ActionMailer:Baseのサブクラス

日本語のPC用のメール及び携帯電話用のメールを送る際に便利かなぁというもの。

動作環境

できること・やりたかったこと

インストール

script/plugin install http:/taslam-plugins.googlecode.com/svn/trunk/jp_mailer/

使い方

モデル

app/models/user_notify.rb

# JpMailer:Baseを継承するほかはかわりません。
class UserNotify < JpMailer::Base

  def signup(user,password,verify_url)
    @recipients = "#{user.name} <#{user.email}>"
    @from       = 'たすらむ <taslam@example.com>'
    @sent_on    = Time.now
    # 件名には絵文字も使えます。
    # ただし携帯だとわかってるときだけか、自前で判別してから付けるべき。
    @subject = "ユーザ登録確認"
    @body["userid"] = user.userid
    @body["password"] = password
    @body["url"] = verify_url
  end

end
ビュー(テンプレート

PC用

app/views/user_notify/signup.erb

ユーザ仮登録完了のお知らせです。
下記URLより、登録処理を完了してください。
<%= @url %>

登録内容
【ユーザID 】<%= @userid %>
【パスワード】<%= @password %>
※この情報は他者に見せないように、厳重に保管してください。

携帯メール用

※なかったらPC用を使う。

app/views/user_notify/mobile/signup.erb

ユーザ仮登録できたよ&#xE6FA;
下記URLから登録処理を続けてね&#xE6F0;
<%= @url %>

登録内容
【ユーザID 】<%= @userid %>
【パスワード】<%= @password %>
※この情報は他者に見せないように、厳重に保管してね。

注意

  • いきあたりばったりで作ったのでいろいろ危ないところがありそう。
  • 自分ではDoCoMoしかテストできないので、AUSoftbankは要注意。
  • 無保証。自己責任でどうぞ。

参考にさせていただいたところ

[http:/jpmobile-rails.org/:title=jpmobile] jpmobileにはいつもお世話になってます。

[http:/wota.jp/ac/?date=20050731:title=ヽ( ・∀・)ノくまくまー Iso2022jpMailer] はじめて買ったRails本はこの方の著書。舞波乙!

[http:/d.hatena.ne.jp/urekat/20071030/1193728474:title=urekatのスカンク日記3] いいかげんな携帯のメールアドレスを無理矢理なんとかパースするぜ] あいまいパーサのコードを流用させていただきました。

追記 2008.07.15

文字コードの判定に失敗するとき、メールの件名が文字化けしていた不具合を修正しました。

追記 2008.07.30

Rails 2.1(TMail 1.2.3)に対応したつもり。

[][][]gettext/railsと共存

gettext/rails.rb内で、ActionMailer::Base#create!を変更している影響でこのままでは正しいメールを送信できない。

とりあえず以下のように、メソッドチェインからgettext/rails.rbでの拡張を外せば動く。

require 'gettext/rails'                                                                       
class ActionMailer::Base #:nodoc:                                                                                               
  if method_defined?(:create_without_gettext!)
    alias :create! :create_without_gettext! #:nodoc:                                                                            
  end
end

2007-12-15

[][]ScopedAccessが動かない

(><;) 便利なのに残念なんです!!!
 (∩∩)
  v v
舞波
>多分 DHH が with_scope に意地悪したからです><
>修正は send にするだけだと思うので時間を見てコミットします>< 
Ruby on Rails入門優しいRailsの育て方

Ruby on Rails入門優しいRailsの育て方

せっかくだから売り上げに貢献するんです><

僕も最初に買ったRails本なんです><

2007-10-31

[][][]STIでのトラブル

acts_as_cachedは便利だが、モデルで継承(STI)を使っている場合、扱いに注意必要。

たとえば、

class RealEstate < ActiveRecord::Base
  acts_as_cached
end

class Land < RealEstate
end

class Building < RealEstate
end

ってモデルがあって、

# インスタンス生成
Land.create(...) # => <Land:0xb71fa45c @attributes={"id"=>"1", "type" => "Land", ...}>
# 取得(キャッシュ)
Land.get_cache(1)  # => <Land:0xb71f4e44 @attributes={"id"=>"1", "type" => "Land", ...}>

# ためしにBuildingから探してみるが無論エラー
Building.find(1)  # => ActiveRecord::RecordNotFound: Couldn't find Building with ID=1
# キャッシュを取得できてしまう
Building.get_cache(1)  # => <Land:0xb71dc524 @attributes={"id"=>"1", "type" => "Land", ...}>

これは、キャッシュのキーを求める際に、以下のように基底クラスの名前を使ってるため。

つまり、Land.get_cache()でも、Building.get_cache()でも、RealEstate.get_cache()でも同じキャッシュを参照するようになっているわけだ。

サブクラスで別にキャッシュしてると、例えばLandインスタンスを変更した際、RealEstateでのキャッシュは破棄されないからかな。あと、Land.get_cache(1)が呼び出された時点で、RealEstate.get_cache(1)でも使えるってのは効率的だし。

module ClassMethods
  def cache_class_name
    @cache_class_name ||= respond_to?(:base_class) ? base_class.name : name
  end
end

個々にキャッシュしておいて、再帰的にsuperを呼び出してbase_classにあたるまで廃棄してくってんじゃだめかなぁ。

(試してない)

2007-10-19

[][][]「クレイジー」なキャッシュ

yoshitetsuの日記:acts_as_cachedを使うを参考に導入してみた。

@categories = Category.get_cache(:all) do
  Category.find(:all, :order => 'index')
end

ブロックを渡すと、ブロックの戻り値をキャッシュします。

# acts_as_cached.rb
# key はget_cacheの引数
data = block_given? ? yield : find_data(id)
set_to_cache(key, data, ttl)
return data

このとき、get_cacheの引数:allはあくまでキャッシュのキーです。

findに渡されることもなく、なんでもよいです。(たぶん)

なので、ブロックを渡すキャッシュのとき、ソートしない:allと区別できるように

@categories = Category.get_cache(:all_with_sorted) do
  Category.find(:all, :order => 'index')
end

とかやってもちゃんとキャッシュします。


ただし、多くの場合キャッシュの廃棄は(TTLもありますが)コールバックを使って

class Category < ActiveRecord::Base
  acts_as_cached
  after_save     :expire_cache  # Category.expire_cache(id)
  before_destroy :expire_cache
end

とかやってると思いますが、これでは、Category.get_cache(:all)など、キーがレコードのid以外のキャッシュは適用されません。

なので、もし、どうしても:allや、ブロックの結果をキャッシュしたいなら

class Category < ActiveRecord::Base
  acts_as_cached
  after_save     :expire_cache_by_id  # Category.expire_cache(id)
  before_destroy :expire_cache_by_id

  def self.all_with_sorted
    self.get_cache(:all_with_sorted) do
      self.find(:all, :order => 'index')
    end
  end

  def expire_cache_by_id
    expire_cache
    self.class.expire_cache(:all)
    self.class.expire_cache(:all_with_sorted)
  end
end

とかやるべきだとおもいます。