诗歌rails之动态find_by方法

  1. class TasksController < ApplicationController  
  2.   
  3.   def incomplete  
  4.     @tasks = Task.find(:all, :conditions => ['complete = ?'false])  
  5.   end  
  6.   
  7. end  
class TasksController < ApplicationController def incomplete @tasks = Task.find(:all, :conditions => ['complete = ?', false]) end end 
很类似Hibernate的数据库查询hql语句,但显然我们的Rails不可能这么逊,看看改良的方法:
  1. class TasksController < ApplicationController  
  2.   
  3.   def incomplete  
  4.     @tasks = Task.find_all_by_complete(false)  
  5.   end  
  6.   
  7. end  
class TasksController < ApplicationController def incomplete @tasks = Task.find_all_by_complete(false) end end 
我们的Task这个Model类没有定义find_all_by_complete啊,我们为什么可以调用这个方法呢?

请看active_record/base.rb中的一段代码:
  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  
def method_missing(method_id, *arguments) if match = /^find_(all_by|by)_([_a-zA-Z]\w*)$/.match(method_id.to_s) finder = determine_finder(match) attribute_names = extract_attribute_names_from_match(match) super unless all_attributes_exists?(attribute_names) attributes = construct_attributes_from_arguments(attribute_names, arguments) case extra_options = arguments[attribute_names.size] when nil options = { :conditions => attributes } set_readonly_option!(options) ActiveSupport::Deprecation.silence { send(finder, options) } when Hash finder_options = extra_options.merge(:conditions => attributes) validate_find_options(finder_options) set_readonly_option!(finder_options) if extra_options[:conditions] with_scope(:find => { :conditions => extra_options[:conditions] }) do ActiveSupport::Deprecation.silence { send(finder, finder_options) } end else ActiveSupport::Deprecation.silence { send(finder, finder_options) } end else raise ArgumentError, "Unrecognized arguments for #{method_id}: #{extra_options.inspect}" end elsif match = /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/.match(method_id.to_s) instantiator = determine_instantiator(match) attribute_names = extract_attribute_names_from_match(match) super unless all_attributes_exists?(attribute_names) if arguments[0].is_a?(Hash) attributes = arguments[0].with_indifferent_access find_attributes = attributes.slice(*attribute_names) else find_attributes = attributes = construct_attributes_from_arguments(attribute_names, arguments) end options = { :conditions => find_attributes } set_readonly_option!(options) find_initial(options) || send(instantiator, attributes) else super end end def extract_attribute_names_from_match(match) match.captures.last.split('_and_') 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返回最后一个元素,如:
  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"  
/^(a)_(b)_(\w*)$/.match("a_b_c_d_e_f_g").captures # => ["a", "b", "c_d_e_f_g"] /^(a)_(b)_(\w*)$/.match("a_b_c_d_e_f_g").captures.last # =>"c_d_e_f_g" /^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等动态方法。

补充几点:
1. 动态查询支持nil, array 以及range作为参数
比如要查询上海或者北京, 年龄在18~38之间的用户,就可以这样用:

  1. User.find_all_by_city_and_age(['Shanghai','Beijing'], (18...38))  
User.find_all_by_city_and_age(['Shanghai','Beijing'], (18...38)) 

2. 动态查询不支持like以及单边范围的查找,比如查询名字中包括"read",年龄大于30的老头用户:
name like '%read%' and age > 30
就无法用动态查询来做了

通过查询源代码,可以看到这样一段在做转换:
  1. def attribute_condition(argument)  
  2.   case argument  
  3.     when nil   then "IS ?"  
  4.     when Array then "IN (?)"  
  5.     when Range then "BETWEEN ? AND ?"  
  6.     else            "= ?"  
  7.   end  
  8. end  
  def method_missing(method_id, *arguments, &block)
if match = DynamicFinderMatch.match(method_id)
attribute_names = match.attribute_names
super unless all_attributes_exists?(attribute_names)
if match.finder?
finder = match.finder
bang = match.bang?
# def self.find_by_login_and_activated(*args)
# options = args.extract_options!
# attributes = construct_attributes_from_arguments(
# [:login,:activated],
# args
# )
# finder_options = { :conditions => attributes }
# validate_find_options(options)
# set_readonly_option!(options)
#
# if options[:conditions]
# with_scope(:find => finder_options) do
# find(:first, options)
# end
# else
# find(:first, options.merge(finder_options))
# end
# end
self.class_eval %{
def self.#{method_id}(*args)
options = args.extract_options!
attributes = construct_attributes_from_arguments(
[:#{attribute_names.join(',:')}],
args
)
finder_options = { :conditions => attributes }
validate_find_options(options)
set_readonly_option!(options)

#{'result = ' if bang}if options[:conditions]
with_scope(:find => finder_options) do
find(:#{finder}, options)
end
else
find(:#{finder}, options.merge(finder_options))
end
#{'result || raise(RecordNotFound, "Couldn\'t find #{name} with #{attributes.to_a.collect {|pair| "#{pair.first} = #{pair.second}"}.join(\', \')}")' if bang}
end
}, __FILE__, __LINE__
send(method_id, *arguments)
elsif match.instantiator?
instantiator = match.instantiator
# def self.find_or_create_by_user_id(*args)
# guard_protected_attributes = false
#
# if args[0].is_a?(Hash)
# guard_protected_attributes = true
# attributes = args[0].with_indifferent_access
# find_attributes = attributes.slice(*[:user_id])
# else
# find_attributes = attributes = construct_attributes_from_arguments([:user_id], args)
# end
#
# options = { :conditions => find_attributes }
# set_readonly_option!(options)
#
# record = find(:first, options)
#
# if record.nil?
# record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) }
# yield(record) if block_given?
# record.save
# record
# else
# record
# end
# end
self.class_eval %{
def self.#{method_id}(*args)
guard_protected_attributes = false

if args[0].is_a?(Hash)
guard_protected_attributes = true
attributes = args[0].with_indifferent_access
find_attributes = attributes.slice(*[:#{attribute_names.join(',:')}])
else
find_attributes = attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args)
end

options = { :conditions => find_attributes }
set_readonly_option!(options)

record = find(:first, options)

if record.nil?
record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) }
#{'yield(record) if block_given?'}
#{'record.save' if instantiator == :create}
record
else
record
end
end
}, __FILE__, __LINE__
send(method_id, *arguments, &block)
end
elsif match = DynamicScopeMatch.match(method_id)
attribute_names = match.attribute_names
super unless all_attributes_exists?(attribute_names)
if match.scope?
self.class_eval %{
def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args)
options = args.extract_options! # options = args.extract_options!
attributes = construct_attributes_from_arguments( # attributes = construct_attributes_from_arguments(
[:#{attribute_names.join(',:')}], args # [:user_name, :password], args
) # )
#
scoped(:conditions => attributes) # scoped(:conditions => attributes)
end # end
}, __FILE__, __LINE__
send(method_id, *arguments)
end
else
super
end
end

转载于:https://www.cnblogs.com/orez88/articles/1518212.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值