最近は、自分用にRailsの挙動を拡張するために書いた lib/xxxx_ext.rbが増えてきました。 こういったファイルはconfig/initializer/の直下に 置いても良いのですが、ON/OFFを切り替えやすいので、 config/initializer/libs.rbの中から、require "xxxx_ext" を呼ぶようにしています。

さて、今回はActiveRecordをさらに便利に使うために、 僕が行っている拡張を紹介します。

lib/active_record_ext.rb

   1  module ActiveRecord
   2    class Base
   3      def self.[](arg, *args)
   4        case arg
   5        when Range
   6          find arg.to_a
   7        when Hash
   8          find :all, arg
   9        when :first, :all
  10          find arg, *args
  11        when :last
  12          find(:all, *args).last
  13        when Symbol
  14          send "find_#{arg}", *args
  15        else
  16          find arg, *args
  17        end
  18      end
  19  
  20      def update_counters(counters)
  21        self.class.update_counters id, counters
  22      end
  23    end
  24  end

どうでしょう、お分かりだと思いますが、ARクラスに インデクサ (rb_aref) を定義しています。こんな感じに動作します。

   1  >> Site[1].uri
   2  => "http://www.google.com/"
   3  >> Site[1,2].map &:uri
   4  => ["http://www.google.com/", "http://www.wikipedia.org/"]
   5  >> Site[1..2].map &:uri
   6  => ["http://www.google.com/", "http://www.wikipedia.org/"]
   7  >> Site[:last].title
   8  => "Ruby/Tk チュートリアル"
   9  >> Site[:by_uri, "http://www.google.com/"].uri
  10  => "http://www.google.com/"

実際のアプリケーションコードの中ではあまり使っていないですが、 script/consoleの中で使うのには大変便利です。

最後に、update_countersは、インスタンスメソッドとして 欲しかったので追加しています。 Rails 2.0から仕様が変わった counter_cache については、 日比さんがこちらに記事 「既存のモデル/テーブルでcounter_cacheを使う:update_counters」 を書いているので参照してみてください。

posted by Png genki on Tue 15 Jan 2008 at 06:38

(追記) 経緯:attr_readonlyを追加しました。

はじめに、counter_cacheの使い方

例えば、Blogテーブルにarticles_countカラムを持つことで、blog.articles.sizeをキャッシュし、増減はrailsが自動でやってくれます。 こんな感じ。

   1  class Article < ActiveRecord::Base
   2    belongs_to :blog, :counter_cache => true

経緯

Rails2.0からカウンターキャッシュカラム(ex Blog#articles_count)がどうやらattr_readonlyになったようで、migration内でupdate_attributeで数が更新できなったので、困った。エラーもでなくて、データで確認すると更新できてない状態。

   1  NG for Rails2.0
   2   blog.update_attribute :articles_count, total 

本題、migrationでの注意

  • カラム名は複数形_count(ex. articles_count)がデフォルト
  • カラム名のカスタマイズは、:counter_cache => :my_custom_counter
  • カラムはdefault => 0
  • 既存のテーブルでcounterカラムを追加する場合はupdate_countersを使う

   1    def self.up
   2      add_column :blogs, :articles_count, :integer, :default => 0
   3  
   4      Blog.find(:all).each do |blog|
   5        Blog.update_counters(blog.id, :articles_count => blog.articles.count)
   6      end

reference

http://josh.the-owens.com/archives/2007/11/03/rails-edge-change-how-to-add-a-counter-cache-to-an-existing-db-table/

posted by Png satoko on Tue 15 Jan 2008 at 00:40 with 2 comments

あすなろとダブルポストです。+最後の感想ちょこっと変更しました。

まとめ

CtonrollerやActionを指定して処理を実行できます

after_contoroller_action
before_contoroller_action

ex. after_blog_save
→ BlogController#save終了後処理をする。

流れをわかるためのもがき

  • ぐぐってヒントを見つける。
    [Rails][cache] Sweeper
    http://d.hatena.ne.jp/meritdemerit/20070607/p2
    なんとなくはわかるけど、やっぱりよくわからないのでソースを見ることにする。

  • Rails Referenceで検索する。右上にファイルの場所があるので確認。
    vendor/rails/actionpack/lib/action_controller/caching.rb
    http://api.rubyonrails.org/classes/ActionController/Caching/Sweeping.html

  • ソースを読む:cache_sweeperキーワード

       1    def cache_sweeper(*sweepers)
       2      return unless perform_caching
       3      configuration = sweepers.extract_options!
       4      sweepers.each do |sweeper|
       5        ActiveRecord::Base.observers << sweeper if defined?(ActiveRecord) and defined?(ActiveRecord::Base)
       6        sweeper_instance = Object.const_get(Inflector.classify(sweeper)).instance
       7        if sweeper_instance.is_a?(Sweeper) 
       8         around_filter(sweeper_instance, :only => configuration[:only])
       9        else
      10          after_filter(sweeper_instance, :only => configuration[:only])
      11        end
      12      end
      13    end
    

  • ソースを読む:after処理のエントリポイント

       1   def after(controller)
       2      callback(:after)
       3      # Clean up, so that the controller can be collected after this request
       4      self.controller = nil
       5    end
    

  • ソースを読む:after_controller_action処理の呼び出し

       1    def callback(timing)
       2      controller_callback_method_name = "#{ timing}_#{controller.controller_name.underscore}"
       3      action_callback_method_name     = "#{controller_callback_method_name}_#{controller.action_name}"
       4      send!(controller_callback_method_name) if respond_to?(controller_callback_method_name, true)
       5      send!(action_callback_method_name)     if respond_to?(action_callback_method_name, true)
       6    end
    

最後の、"send!(action...:自分にメソッドが定義されていたら送る"ってところが実際の呼び出し。
愛が足りないながんばろ。

posted by Png satoko on Mon 14 Jan 2008 at 06:32

lighttpd-1.5.xから導入される予定のmod_deflateは、 static/dynamicを問わず、レンダリング出力を 圧縮してくれる便利なモジュールですが、 残念ながらlighttpd-1.4.x系では使うことができません。

しかし、非常に便利な機能なので、1.5.x系から バックポートするための様々な試みがなされています。 今回は、lighttpd-1.4.8にパッチを当てて mod_deflateを使う方法を紹介します。

以下のようにlighttpd-1.4.8のソースと、パッチを ダウンロードし、パッチを適用します。

   1  # cd /usr/local/src
   2  # wget http://www.lighttpd.net/download/lighttpd-1.4.18.tar.bz2
   3  # tar xvjf lighttpd-1.4.18.tar.bz2
   4  # cd lighttpd-1.4.18
   5  # curl http://poocs.net/files/lighttpd-1.4.18.mod_deflate.scoop.patch.gz  | gzip -d | patch -p1

あとは、通常通りソースからインストールすれば完了です。

   1  # ./configure --with-bzip2
   2  
   3  ~ (snip) ~
   4  
   5  enabled:
   6    auth-crypt
   7    compress-bzip2
   8    compress-deflate
   9  
  10  ~ (snip) ~
  11  
  12  # make
  13  # make install

mod_deflateの使い方は、こちらのサイトをご覧ください。

posted by Png genki on Sun 13 Jan 2008 at 07:33

javascript_include_tagstylesheets_link_tag:cache => trueオプションは、複数のassetsファイルを ひとつのファイルにまとめる事で、サーバへのリクエスト 数を減らすことができる機能です。

何度か実験してみたところ、生成されたキャッシュファイルを 削除して更新するためには、単純にファイルを削除するだけ ではだめで、APサーバのプロセスを再起動する必要があるようです。

このためには、./script/process/reaper -a graceful を使う必要があります。タイプするのが面倒なので、 Rakeタスクを作ってみました。

lib/tasks/cache.rake

   1  namespace :tmp do
   2    namespace :cache do
   3      desc 'Delete cached asset files'
   4      task :update do
   5        patterns = [
   6          'public/stylesheets/cached_*.css',
   7          'public/javascripts/cached_*.js']
   8        Dir.glob(patterns).each do |path|
   9          rm_rf path
  10          puts "deleted: #{path}"
  11        end
  12        `./script/process/reaper -a graceful 2>&1 > /dev/null`
  13        puts "script/process/reaper -a graceful"
  14      end
  15    end
  16  end

生成されるキャッシュファイルの名前を :cache => "cached_#{controller.controller_name}" と指定しているため、こんな感じになります。 デフォルトでは、all.cssall.jsとなりますので 適宜読みかえてください。

posted by Png genki on Sat 12 Jan 2008 at 23:17

比嘉さんからciteされたみたいなので、取り急ぎ新しい情報を吐き出しておこうと思います。

そろろろRailsについて本音を書いてみるか

後、デバッグの環境は、Javaに比べて貧弱だと思う。Railsでデバッグをする7つの方法を見てほしい。IDEでソースにブレークポイントを設定(ソースコードを書き換えるのではなく)して、ステップイン、ステップオーバー、メモリの状態を見たりなんてのに慣れているJavaから比べると、すっごく大変に見える。

喜ばしいことに、Rails 2.0ではruby-debugを使ったdebuggerが正式に採用されました。 これの使い方は非常に簡単です。

まずは、以下のようにブレークポイントをコード中に書き込みます。

   1  def some_method
   2    debugger # breakpoint
   3  end

続いて、Webサーバをデバッグモードで実行します。

   1  % ./script/server -u

これだけです。あとは、実際にブレークポイントを仕掛けた 場所が実行されるようにすれば、gdbライクなruby-debugの セッションが開始されます。 ruby-debugは、gdbと同じように、ステップ実行、ステップオーバー等の逐次実行を行うインターフェイスがあるので、 コードの動作を確認しながらデバッグを行うことができます。

gdbになれていない人には、irbコマンドを実行することで、 その場でirbを立ち上げることができます。これは非常に便利ですね。

参考までに、debuggerから実行できるコマンドのリストを 載せておきます。

   1  backtrace break catch cont delete display down eval exit finish frame help irb list method next p pp quit reload restart save script set step thread trace undisplay up var where

Have a good debugging life!

See Also

更新履歴

  • 2008/01/21 See Alsoに1点追加
posted by Png genki on Fri 11 Jan 2008 at 22:21

cache_fuやmemcachedを使うときに、cacheメソッドがオプションを 受け取ってくれないので不便だなと思っていたのですが、 EdgeRailsでは以下のようなパッチが当たっていて この問題は解決されているようです。

[PATCH] Rails have poor support to work with memcached

Let's assume we have a caching installation with Nginx used as a frontend. Nginx attempts to get page body by key from memcached. If it succeeds (meaning that the page is in memcached), Rails doesn't get control, making this schema extremely fast, up to 3K requests per second. In case memcached doesn't contain cached page by url as a key, Rails receive control, render page and cache it in memcached in order for the next request to be retrieved from memcached.

現状のリリースバージョンのコードでは、以下のようになっています。

vendor/rails/actionpack/lib/action_view/helpers/cache_helper.rb

   1  def cache(name = {}, &block)
   2    @controller.cache_erb_fragment(block, name)
   3  end

これが、EdgeRailsでは以下のように変更されています。

vendor/rails/actionpack/lib/action_view/helpers/cache_helper.rb

   1  def cache(name = {}, options = nil, &block)
   2    template_extension = first_render[/\.(\w+)$/, 1].to_sym
   3   
   4    case template_extension
   5    when :erb, :rhtml
   6      @controller.cache_erb_fragment(block, name, options)
   7    when :rjs
   8      @controller.cache_rjs_fragment(block, name, options)
   9    when :builder, :rxml
  10      @controller.cache_rxml_fragment(block, name, options)
  11    else
  12      # do a last ditch effort for those brave souls using
  13      # different template engines. This should give plugin
  14      # writters a simple hook.
  15      unless @controller.respond_to?("cache_#{template_extension}_fragment")
  16        raise "fragment caching not supported for #{template_extension} files."
  17      end
  18   
  19      @controller.send!("cache_#{template_extension}_fragment", block, name, options)
  20    end
  21  end

posted by Png genki on Fri 11 Jan 2008 at 05:48

あけましておめでとうございます。
年始にダラダラと仕事を開始してしまったので、ついつい時間が空いてしまいました。今年もよろしくお願い致します。

今年の目標はRailsのプラグインを作ることです。小さい目標ですが、去年の初めくらいから思っているのにプラグインを利用する側ばかりでなかなか達成できていません。

Adobe AIR、Firefox拡張は作ってみたいと思っていたら、割とすんなり作ることができて、そしてとても勉強になりました。そのうちに公開できると思います。

+αとしては、英語で物を書くというのと、体を動かすでしょうか。
英語の方は日本に住んでいるのでなかなか使う機会もありませんが、iKnowでtypeする楽しさを知ったため積極的に使う方向で行こうと思います。英語の記事も書いていきたいです。
体を動かすことついては、お手軽にWiiFitで週に二回15分から20分の運動をしています。1回の時間が短いのが目下悩みです。

まだありました!トップ5%に近づくための努力をするです。もっと生産的にならなくては。時間は増えないので、効果的/効率的をハード/ソフトの両面で実践するようにします。

最後にもう一度、今年もよろしくお願い致します。

posted by Png satoko on Wed 9 Jan 2008 at 10:50

RailsのCacheSweeperは非常に便利なのですが、 callbackからコントローラのインスタンス変数にアクセスできると もっと便利になる気がします。

ソースコードを読んでみたら、やっぱりみんなそう思うようで、 assignsメソッドが用意されていました。

   1  class PostsSweeper < ActionController::Caching::Sweeper
   2    observe Post
   3  
   4    def after_posts_rating
   5      expire_fragment "posts/show/#{assigns(:post).id}"
   6    end
   7  end

こんな風に書けます。これは便利。

posted by Png genki on Tue 8 Jan 2008 at 07:48

helpコマンドを使うと、script/consoleから非常に簡単に ドキュメントを参照することができます。 使い方は以下の通り。

   1  >> help CGI
   2  ------------------------------------------------------------- Class: CGI
   3       Wrapper around the CGIMethods that have been secluded to allow
   4       testing without an instantiated CGI object
   5  
   6  ------------------------------------------------------------------------
   7       TODO: document how this differs from stdlib CGI::Cookie
   8  
   9  ------------------------------------------------------------------------
  10       TODO: document how this differs from stdlib CGI::Cookie
  11  
  12  ------------------------------------------------------------------------
  13       Wrapper around the CGIMethods that have been secluded to allow
  14       testing without an instantiated CGI object
  15  
  16  ------------------------------------------------------------------------
  17       TODO: document how this differs from stdlib CGI::Cookie
  18  
  19  ------------------------------------------------------------------------
  20  (END)

引数としてクラスや文字列を渡すと、riと同じようにドキュメントを 照会することができます。ちょっと便利ですね。

posted by Png genki on Mon 7 Jan 2008 at 05:57