プレースホルダの展開を String#gsub でやってるところがキレイだなーと思っただけなんだけど、せっかく読んだので少しだけどメモしとく。全部 rails/activerecord/lib/active_record/base.rb のコードでコピペしてあるのは v2.3.8 のやつ。
def find_by_sql(sql) connection.select_all(sanitize_sql(sql), "#{name} Load").collect! { |record| instantiate(record) } end
sanitize_sql を読む
def sanitize_sql_for_conditions(condition, table_name = quoted_table_name) return nil if condition.blank? case condition when Array; sanitize_sql_array(condition) when Hash; sanitize_sql_hash_for_conditions(condition, table_name) else condition end end alias_method :sanitize_sql, :sanitize_sql_for_conditions
alias_method :new_method_name :old_method_name らしい。参考 Rubyのaliasとalias_method - walf443's blog
Model.find_by_sql(['select * from table where id = ?', id]) とかした場合 condition は Array なので sanitize_sql_array を読む
def sanitize_sql_array(ary) statement, *values = ary if values.first.is_a?(Hash) and statement =~ /:\w+/ replace_named_bind_variables(statement, values.first) elsif statement.include?('?') replace_bind_variables(statement, values) else statement % values.collect { |value| connection.quote_string(value.to_s) } end end
statement, *values = ary とかカッコイイと思う。プレースホルダの有無は String#include? で '?' を探してる。で replace_bind_variables を読む
def replace_bind_variables(statement, values) #:nodoc: raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size) bound = values.dup statement.gsub('?') { quote_bound_value(bound.shift) } end
String#gsub('?') { } でブロック内で置換と。その前で raise するコードも String#count と values.size を比べるとか、見やすいなーと思う。
上の行を Perl で書くとするとこんな感じかな?
$statement =~ s/\?/quote_bound_value(shift @bound)/ge;
俺は正規表現に苦手意識があって、特に /e は読むのも書くのもてんでダメなので、余計に gsub が気に入ったのかもしれない。ただ、 pattern が文字列でも正規表現リテラルでもいいというのは結構戸惑う。 Array#reject! と Array#delete_if のように違いがあるのかと思って少し不安になる。