ActiveRecord::Extensions

去年Twitterで教えてもらったActiveRecord::Extensions。すごく良いです。
日本語で書かれた記事が見つからなかったので、書いてみます。

ActiveRecord::Extensionsは文字通り、ActiveRecordの拡張です。わりと便利な機能がたくさんあるのでmysql使っている人は入れても損は無いと思う。postgresqlは未対応です。

インストール

gem install ar-extensions

もしくは

script/plugin install http://arext.rubyforge.org/svn/tags/ar-extensions-0.7.0

gemとpluginどちらも用意されてるのが嬉しい。

あとはenvironment.rbとかにrequire 'ar-extensions'しとく。

(注:ar-extensions-0.7.0はActiveRecordの1.14.1以上が必要です。)


主だったところの使い方

独断と偏見で、便利そうなところを抽出しました。詳しい使い方はrdoc読むかソースをここから読むと良いです。


Hashをサポートしたfindが可能

Postっていうモデルがあったとして

class Post < ActiveRecord::Base ; end

Post.find( :all, :conditions=>{ 
  :title => "Title",                           # title='Title'
  :author_contains => "Zach",                  # author like '%Zach%'
  :author_starts_with => "Zach",               # author like 'Zach%'
  :author_ends_with => "Dennis",               # author like '%Zach'
  :published_at => (Date.now-30 .. Date.now),  # published_at BETWEEN xxx AND xxx
  :rating => [ 4, 5, 6 ],                      # rating IN ( 4, 5, 6 )
  :rating_not_in => [ 7, 8, 9 ]                # rating NOT IN( 4, 5, 6 )
  :rating_ne => 4,                             # rating != 4
  :rating_gt => 4,                             # rating > 4
  :rating_lt => 4,                             # rating < 4
  :content => /(a|b|c)/                        # REGEXP '(a|b|c)'
)
to_sqlをメソッドのように使う
class InsuranceClaim < ActiveRecord::Base ; end

class InsuranceClaimAgeAndTypeQuery
  def to_sql
    "age_in_days BETWEEN 1 AND 60 AND claim_type IN( 'typea', 'typeb' )"
  end
end

↑のように定義しておけば

claims = InsuranceClaim.find( :all, InsuranceClaimAgeAndTypeQuery.new )

claims = InsuranceClaim.find( :all, :conditions=>{
  :claim_amount_gt => 30000,
  :age_and_type => InsuranceClaimAgeAndTypeQuery.new } 
)

こんな風に、複雑な条件のfindがすっきり書ける。



たくさんのデータのインポート

'create'や'save'メソッドはシングルINSERTなので、たくさんのデータをインポートするときはクエリが大量に発生する。データが数千件とかになると明らかにボトルネックなので、普通はマルチプルINSERTするメソッドを独自に作るわけだけど、ActiveRecord::ExtensionsはマルチプルINSERTするクエリを発行するメソッドを定義してくれている。

columns = [ :author_name, :title ]
values = [ [ 'yoda', 'test post' ] ]
BlogPost.import columns, values

素晴らしい。ちなみに、importにはActiveRecord::Base#reloadに似たsynchronizeっていうメソッドがあって、こいつを使うとインポートした内容が指定したオブジェクトに入る。

post = BlogPost.find_by_author_name( 'zdennis' )
columns = [ :author_name, :title ]
values = [ [ 'yoda', 'test post' ] ]
BlogPost.import columns, values, :synchronize=>[ post ]
post.author_name # => 'yoda'
to_csvの拡張

to_csvを様々に拡張している。標準ライブラリよりも速いCSVライブラリのfaster_csv使っているからスピードも安心。


指定したカラムだけcsv吐き出し。

book = Book.find( 1 ) 
book.to_csv( :only=>%W( title isbn ) )


belong_toのassociationを使っているモデルのcsv吐き出し。

class Book < ActiveRecord::Base 
  belongs_to :author
end

book = Book.find( 1 )
book.to_csv( :include=>:author )


has_manyのassociationを使っているモデルのcsv吐き出し。

class Book < ActiveRecord::Base 
  has_many :tags
end

book = Book.find( 1 )
book.to_csv( :include=>:tags )


ネストしたassociationを使ってcsv吐き出し。

class Book < ActiveRecord::Base 
  belongs_to :author
  has_many :tags
end

book = Book.find( 1 )
book.to_csv( :includes=>{ 
             :author => { :only=>%W( name ) },
             :tags => { :only=>%W( tagname ) } )

おわりに

例にあげた機能はほんの一部です。
詳しくは公式サイト
http://www.continuousthinking.com/tags/arext
を見てください。

みんなもっとActiveRecord::Extensions使えば良いと思うよ。

追記

 NoMethodError: undefined method `to_csv'

と出てto_csvができないときは

 class YourModel < ActiveRecord::Base
   include ActiveRecord::Extensions::FindToCSV
 end

includeしてあげると出来るよ。