Rails2.1.2でActsAsReadonlyableが動かないときの対処

Tsukasa OISHI

Railsのバージョンを2.1.2にあげたらvalidates_uniquness_ofでActiveRecord::StatementInvalid例外が出るようになった。

TypeError: wrong argument type Hash (expected String): .......

vendor/rails/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb:147:in `log'
vendor/rails/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb:302:in `execute'
vendor/rails/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb:537:in `select'
vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb:7:in `select_all_without_query_cache'
vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb:59:in `select_all'
vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb:80:in `cache_sql'
vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb:59:in `select_all'
vendor/rails/activerecord/lib/active_record/validations.rb:651:in `validates_uniqueness_of'

vendor/rails/activerecord/lib/active_record/validations.rbの651行目を見てみる。

results = finder_class.with_exclusive_scope do
  connection.select_all(
    construct_finder_sql(
      :select     => "#{connection.quote_column_name(attr_name)}",
      :from       => "#{finder_class.quoted_table_name}",
      :conditions => [condition_sql, *condition_params]
    )
  )
end

どうも、select_allに文字列のSQLを渡さなきゃいけないのに、Hashが渡っていることでエラーになっている。
なので、construct_finder_sqlメソッドを見てみる。

        def construct_finder_sql(options)
          scope = scope(:find)
          sql  = "SELECT #{options[:select] || (scope && scope[:select]) || ((options[:joins] || (scope && scope[:joins])) && quoted_table_name + '.*') || '*'} "
          sql << "FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "

          add_joins!(sql, options, scope)
          add_conditions!(sql, options[:conditions], scope)

          add_group!(sql, options[:group], scope)
          add_order!(sql, options[:order], scope)
          add_limit!(sql, options, scope)
          add_lock!(sql, options, scope)

          sql
        end

それぞれのメソッドもたどって見たけど、このメソッドはちゃんと文字列のSQLを返していた。どこかで悪さをしているやつがいるらしい。
と思ってプラグインを調べたらActsAsReadonlyableが犯人だった。

      def construct_finder_sql(options)
        options.merge(:sql => super)
      end

こんなことしている。なので、select_allをオーバライドして仮対処してみた。

module ActiveRecord
  module ConnectionAdapters
    class AbstractAdapter
      def select_all(sql, name = nil)
        sql = sql[:sql] if sql.is_a?(Hash)
        select(sql, name)
      end
    end
  end
end