Rails宝典之第二式: 动态find_by方法

时间:2021-09-24 04:14:03
Rails宝典之第二式: 动态find_by方法 

忘了声明了,这个系列主要是Rails入门教学。 

今天Rails宝典教大家的是动态find_by方法,我们先看一段代码: 
Ruby代码   Rails宝典之第二式: 动态find_by方法
  1. class TasksController < ApplicationController  
  2.   
  3.   def incomplete  
  4.     @tasks = Task.find(:all, :conditions => ['complete = ?'false])  
  5.   end  
  6.   
  7. end  

很类似Hibernate的数据库查询hql语句,但显然我们的Rails不可能这么逊,看看改良的方法: 
Ruby代码   Rails宝典之第二式: 动态find_by方法
  1. class TasksController < ApplicationController  
  2.   
  3.   def incomplete  
  4.     @tasks = Task.find_all_by_complete(false)  
  5.   end  
  6.   
  7. end  

我们的Task这个Model类没有定义find_all_by_complete啊,我们为什么可以调用这个方法呢? 

请看active_record/base.rb中的一段代码: 
Ruby代码   Rails宝典之第二式: 动态find_by方法
  1. def method_missing(method_id, *arguments)  
  2.   if match = /^find_(all_by|by)_([_a-zA-Z]\w*)$/.match(method_id.to_s)  
  3.     finder = determine_finder(match)  
  4.   
  5.     attribute_names = extract_attribute_names_from_match(match)  
  6.     super unless all_attributes_exists?(attribute_names)  
  7.   
  8.     attributes = construct_attributes_from_arguments(attribute_names, arguments)  
  9.   
  10.     case extra_options = arguments[attribute_names.size]  
  11.       when nil  
  12.         options = { :conditions => attributes }  
  13.         set_readonly_option!(options)  
  14.         ActiveSupport::Deprecation.silence { send(finder, options) }  
  15.   
  16.       when Hash  
  17.         finder_options = extra_options.merge(:conditions => attributes)  
  18.         validate_find_options(finder_options)  
  19.         set_readonly_option!(finder_options)  
  20.   
  21.         if extra_options[:conditions]  
  22.           with_scope(:find => { :conditions => extra_options[:conditions] }) do  
  23.             ActiveSupport::Deprecation.silence { send(finder, finder_options) }  
  24.           end  
  25.         else  
  26.           ActiveSupport::Deprecation.silence { send(finder, finder_options) }  
  27.         end  
  28.   
  29.       else  
  30.         raise ArgumentError, "Unrecognized arguments for #{method_id}: #{extra_options.inspect}"  
  31.     end  
  32.   elsif match = /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/.match(method_id.to_s)  
  33.     instantiator = determine_instantiator(match)  
  34.     attribute_names = extract_attribute_names_from_match(match)  
  35.     super unless all_attributes_exists?(attribute_names)  
  36.   
  37.     if arguments[0].is_a?(Hash)  
  38.       attributes = arguments[0].with_indifferent_access  
  39.       find_attributes = attributes.slice(*attribute_names)  
  40.     else  
  41.       find_attributes = attributes = construct_attributes_from_arguments(attribute_names, arguments)  
  42.     end  
  43.     options = { :conditions => find_attributes }  
  44.     set_readonly_option!(options)  
  45.   
  46.     find_initial(options) || send(instantiator, attributes)  
  47.   else  
  48.     super  
  49.   end  
  50. end  
  51.   
  52. def extract_attribute_names_from_match(match)  
  53.   match.captures.last.split('_and_')  
  54. end  

看看第一行代码:if match = /^find_(all_by|by)_([_a-zA-Z]\w*)$/.match(method_id.to_s) 
这是一个正则表达式匹配: 
^匹配一行的开始 
$匹配一行的结束 
\w匹配一个词语,可以是数字、字母、下划线 
[]匹配括号里面符合的一个字符 
*匹配0个或多个它前面的字符或短语 
则([_a-zA-Z]\w*)则匹配以下划线或任意小写字母或任意大写字母开头的一个字符串 
具体参考《Programming Ruby》中的Regular Expressions一章 

而extract_attribute_names_from_match方法也很有意思,match.captures返回匹配的字符串组成的数组,last返回最后一个元素,如: 
Ruby代码   Rails宝典之第二式: 动态find_by方法
  1. /^(a)_(b)_(\w*)$/.match("a_b_c_d_e_f_g").captures # => ["a""b""c_d_e_f_g"]  
  2. /^(a)_(b)_(\w*)$/.match("a_b_c_d_e_f_g").captures.last # =>"c_d_e_f_g"  
  3. /^find_(all_by|by)_([_a-zA-Z]\w*)$/.match("find_by_a_and_b_and_c").captures.last # => "a_and_b_and_c"  


这样,第一行代码所匹配的方法名具体为find_by(或all_by)_aaaBBB形式 
而extract_attribute_names_from_match允许我们匹配形式为find_by(或all_by)_aaaBBB_and_cccDDD_and_eeeFFF_and_...的方法 
即我们可以无限添加查询条件,通过_and_连接字段名即可 

而且可以看出,我们还可以调用find_by_columnName、find_or_initialize_by_columnName、find_or_create_by_columnName等动态方法。 
参考 Rails源码研究之ActiveRecord:一,基本架构、CRUD封装与数据库连接 

总结: 
动态语言可以做出很强大的框架,完成很多有意思的工作,这是Java、C#这种静态语言望尘莫及的。 
本例中的find_by方法就是利用Ruby的method_missing语法特性来为Model动态生成方法,我们可以称其为 POJO增强。 

JavaScript也是一门大家可能忽略了但又十分强大的动态语言,参考Ruby和Python,JavaScript还有很大潜力可挖。