Posts Tagged ‘Ruby on Rails’

    はてなブックマーク - [rails] config.cache_classes = trueでも特定のクラスだけアクセスの度にloadする
    このエントリーをはてなブックマークに追加

    Ruby技術者認定試験に合格してほっとして倒れそうな山本ですこんにちは。

    (Rails2.xの話になります)
    さて、Railsでは起動時に環境の指定ができますが、productionモードの時はある程度のクラスがキャッシュ化されて読み込みが早くなります。
    通常であれば、config/environments/production.rbに以下の記述があると思います。

    config.cache_classes = true

    これにより、クラスは起動時の一回のみの読み込みとなり高速化が図られてます。

    話はちょっと変わりますが、modelクラスでバリデーションを定義するとき、メッセージを個別に指定したい場合は以下のように書くと思います。

    validates_format_of :hoge, :with => /moge/,  :message => "mogeと書いてください。"

    この時、以下のように

    validates_format_of :hoge, :with => /moge/,  :message => "(#{Time.now}に発生)mogeと書いてください。"

    にして、クラスが読まれたときの時間を表示するといった場合、
    developmentモードでは、その時の時間が表示されるのですが、productionモードでは初回の起動時の時間が表示されたまま、変わることはありません。
    これはproductionモードだとmodelクラスが一度しか読まれないからです。

    そもそも、productionモードでは何が起こっているのか

    ひとえにproductionモードと言っても、ウラでは何が起きているのか。
    その情報を見つけることができなかったため、ソースを追ってみました。

    config.cache_classesは、どこで何らかの処理をされているのか。
    config/environments~以下の各クラスで定義されている、
    config.cache_classesは、どこで判定がされ処理されているのでしょうか、ってことで探してみると以下の場所にありました。

    # Rails2.3.5 rails-2.3.5/lib/initializer.rb
    def initialize_dependency_mechanism
      ActiveSupport::Dependencies.mechanism = configuration.cache_classes ? :require : :load
    end

    ここで、クラスを読み込むために、requireを使うかloadを使うかのか判定を行っています。
    requireは一度しか読み込みませんが、loadは何回でも読みます。

    特定のクラスだけ読み込む術はないものか

    “指定したクラスだけは、load読み込みにする” なんてのができればいいのでしょうが、一括で起動時にrequireしてしまっているので、どうしようもないのかなとか思いつつ、いろいろやってみました。
    ActiveSupportのDependenciesクラスが色々と鍵を握ってるみたいです。

    まず、controllerにbefore_filterを仕込みその中で、以下のように書いてみました。

    ActiveSupport::Dependencies.load_file("Modelクラスへのパス")

    いかにもなメソッドです。loadし直してくれるのではと、やってみましたが表示時間は変わらず結果は×。

    次に、

    ActiveSupport::Dependencies.load_with_new_constant_marking("Modelクラスへのパス")

    これもいかにもな名前です。が、あえなく×。

    で、

    modelクラスに "unloadable" と宣言する

    ついに見つけました! これを書けば起動時にこのクラスが読まれることはない! はず!

    と意気揚々と試しましたが、×。

    こうなったらと、手当たり次第作戦。
    すると、以下のメソッドをbefore_filterに仕込むと、アクセスの度に、表示する時間が変わりました。

    ActiveSupport::Dependencies.remove_unloadable_constants!

    ついにやったと思いつつ、ソースを見てみます。

    def remove_unloadable_constants!
      autoloaded_constants.each { |const| remove_constant const }
      autoloaded_constants.clear
      explicitly_unloadable_constants.each { |const| remove_constant const }
    end

    どうやら、起動時に読み込んだクラスをリムーブしてるみたいですね。
    これ自体を読んだのであれば、全てが消えてしまう感じでこわいので、ここで使われているremove_constantメソッドだけを使って、該当のクラスを読み込み直しました。

    ActiveSupport::Dependencies.remove_constant "Modelのクラス名"
    load "Modelクラスへのパス"

    すると、晴れてアクセスする度に表示する時間が変わりました。
    #:nodoc:となっていますのでこのメソッドに解説がないのですが、このメソッドは以下のとおり。

    def remove_constant(const)
      return false unless qualified_const_defined? const
     
      const = $1 if /\A::(.*)\Z/ =~ const.to_s
      names = const.to_s.split('::')
      if names.size == 1 # It's under Object
        parent = Object
      else
        parent = (names[0..-2] * '::').constantize
      end
     
      log "removing constant #{const}"
      parent.instance_eval { remove_const names.last }
      return true
    end

    引数で渡したクラス名をチェックしたのち、
    remove_constという記述があり、クラスの定数を削除しているようです。

    一応、他のページ表示や処理にも問題ないようです。

    最適解か

    バリデーションのメッセージを動的に変えたい、という動機からここまでの調査に至りましたが、もっと簡単な方法があるのかもしれません。
    そもそも、どうしてもと言うならcontroller側でエラーメッセージを定義してもいいでしょうし。

    いかんともしがたい理由でmodelをアクセスの度にloadしたい方はご参考に~。

    { 2010.12.4 }

    rails 日付TIPS

      はてなブックマーク - rails 日付TIPS
      このエントリーをはてなブックマークに追加

      昨日まで続いた師走の嵐から一変、ど晴天の休日の今日、皆様いかがお過ごしでしょうか。

      昨日エントリーを書きました安元の結婚式である本日も、Advent Calendarのバトンを引き継いでおります。
      休日なので、軽めのtipsを書かせていただきます。

      皆さんは日付を和暦で表示する時、どのような処理をしているでしょうか。
      日付なんてDBに入ってる値を、多少整形する形で済めばいいのですが、案件によっては和暦表示にせざるを得ないことは、往々にしてあるかと思います。
      私がメインで扱っているruby(rails)では、和暦表示用のプラグインがあるので、それを導入すれば済むのですが、
      それを知らずに自前で作っていた時は以下のようにメソッドを作ってました。

      def get_wm_y(ymd)
          return "" if ymd.blank? 
          case ymd.year
            when 0..1925
              return "#{ymd.year}年"
           when 1868..1911
               return '明治' + "#{ymd.year - 1867}年"
            when 1912..1925
               return '大正' + "#{ymd.year - 1911}年"
            when 1926..1988 
               return '昭和' + "#{ymd.year - 1925}年"
            else 
              return '平成' + "#{ymd.year - 1988}年"
          end
        end

      とても泥臭い感じはしますが、和暦計算の法則に則ったかたちになります。
      ひとつ難点は、「元年」表示ができないことですが、これも条件分岐で処理すればいいでしょう。

      日付に関してもう一つ。
      これは日本特有かもしれませんが、「年度」という概念があります。
      2010年1月は、2009年度扱いにしないといけないわけですが、これは以下のようにしてます。

      def get_nend_y(ymd)
          return "" if ymd.blank?
          case ymd.month
          when 1..3
            (ymd.year - 1).to_s
          else
            ymd.year.to_s
          end
        end

      これはすごくシンプルですかね。

      大したことは書いてませんが、ちょっとしたtipsとしてお役に立てればと思います。
      あと、こうしたほうがいいというのがあれば、ぜひご意見お寄せください。

      では、めでたき結婚式に行って参ります。