2007-07-25
Rails宝典之第二式: 动态find_by方法
关键字: find_by 正则表达式
Rails宝典之第二式: 动态find_by方法
忘了声明了,这个系列主要是Rails入门教学。
今天Rails宝典教大家的是动态find_by方法,我们先看一段代码:
很类似Hibernate的数据库查询hql语句,但显然我们的Rails不可能这么逊,看看改良的方法:
我们的Task这个Model类没有定义find_all_by_complete啊,我们为什么可以调用这个方法呢?
请看active_record/base.rb中的一段代码:
看看第一行代码: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返回最后一个元素,如:
这样,第一行代码所匹配的方法名具体为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还有很大潜力可挖。
忘了声明了,这个系列主要是Rails入门教学。
今天Rails宝典教大家的是动态find_by方法,我们先看一段代码:
class TasksController < ApplicationController
def incomplete
@tasks = Task.find(:all, :conditions => ['complete = ?', false])
end
end
很类似Hibernate的数据库查询hql语句,但显然我们的Rails不可能这么逊,看看改良的方法:
class TasksController < ApplicationController
def incomplete
@tasks = Task.find_all_by_complete(false)
end
end
我们的Task这个Model类没有定义find_all_by_complete啊,我们为什么可以调用这个方法呢?
请看active_record/base.rb中的一段代码:
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返回最后一个元素,如:
/^(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等动态方法。
参考Rails源码研究之ActiveRecord:一,基本架构、CRUD封装与数据库连接
总结:
动态语言可以做出很强大的框架,完成很多有意思的工作,这是Java、C#这种静态语言望尘莫及的。
本例中的find_by方法就是利用Ruby的method_missing语法特性来为Model动态生成方法,我们可以称其为POJO增强。
JavaScript也是一门大家可能忽略了但又十分强大的动态语言,参考Ruby和Python,JavaScript还有很大潜力可挖。
评论
yuwang
2007-11-06
那上面1分不到,hideto讲的好,怎么详细,把上面没有的都弄出来了,谢谢
hideto
2007-08-11
是的是的
rainlife
2007-08-11
楼主,请问这些是否都是根据www.railscasts.com上面的视屏教程写出来的?
blackanger
2007-08-05
引用
我们的Task这个Model类没有定义find_all_by_complete啊,我们为什么可以调用这个方法呢?
这个complete是database里已经定义好的列了吧?
super unless all_attributes_exists?(attribute_names)
这行代码里的all_attributes_exists?方法是否会去数据库中检查相对应的列有没有存在?
如果存在就初始化类的实例,否则就抛出错误。类似于这样的错误:
NoMethodError: undefined method `find_all_by_name'?
因为在database里没有name这个column
blackanger
2007-07-29
google不是要用javascript来搞一套ROR吗。。。infoq上看到的消息。。。
Readonly
2007-07-25
这个不错,可以结合起来写,
又学了一招
hideto
2007-07-25
我仔细看了上面的源码中的method_missing方法,其中有这么一段:
这就意味着使用动态find_by后仍然可以使用:conditions参数,额外的条件会使用with_scope来约束,如:
或者也可以直接使用with_scope,不过代码没上面这样写简单。
使用动态find_by结合:conditions即可做非等号的查询,我试验了一下,结果是可以的。
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
这就意味着使用动态find_by后仍然可以使用:conditions参数,额外的条件会使用with_scope来约束,如:
Book.find_by_title('title', :conditions => ['price > ?', price])
或者也可以直接使用with_scope,不过代码没上面这样写简单。
使用动态find_by结合:conditions即可做非等号的查询,我试验了一下,结果是可以的。
Readonly
2007-07-25
hideto的系列文章很不错啊,偶再来补充几点:
1. 动态查询支持nil, array 以及range作为参数
比如要查询上海或者北京, 年龄在18~38之间的用户,就可以这样用:
2. 动态查询不支持like以及单边范围的查找,比如查询名字中包括"read",年龄大于30的老头用户:
name like '%read%' and age > 30
就无法用动态查询来做了
通过查询源代码,可以看到这样一段在做转换:
偶的想法是:如果多加一个Struct的支持来实现非等号单操作符的查询,是不是可行呢,比如:
1. 动态查询支持nil, array 以及range作为参数
比如要查询上海或者北京, 年龄在18~38之间的用户,就可以这样用:
User.find_all_by_city_and_age(['Shanghai','Beijing'], (18...38))
2. 动态查询不支持like以及单边范围的查找,比如查询名字中包括"read",年龄大于30的老头用户:
name like '%read%' and age > 30
就无法用动态查询来做了
通过查询源代码,可以看到这样一段在做转换:
def attribute_condition(argument)
case argument
when nil then "IS ?"
when Array then "IN (?)"
when Range then "BETWEEN ? AND ?"
else "= ?"
end
end
偶的想法是:如果多加一个Struct的支持来实现非等号单操作符的查询,是不是可行呢,比如:
find_all_by_name_and_age(like('%read%'), greater(30))
- 浏览: 681836 次
- 性别:

- 来自: BJ

- 详细资料
搜索本博客
我的相册
screenshot
共 1 张
共 1 张
最近加入圈子
最新评论
-
Mnesia用户手册:三,构建 ...
要想创建disc_copies和disc_only_copies类型的表有两个前 ...
-- by hideto -
翻译www.djangobook.com之 ...
有个问题问一下: 我先配置了一个urlpatterns是这样的: r'^myd ...
-- by lyhapple -
Why OO sucks
gigix 写道lyl0035 写道为啥就没人想想,其实在面向对象的代码中也流露 ...
-- by hurd -
Why OO sucks
貌似又回到当年java vs c的年代。两种方式,不管是OO还是FP,仅是人处理 ...
-- by python -
大家可以抛弃Java踹死Djan ...
to phoenixup:1,你还别说,你举的什么Struts,Tapestry ...
-- by hideto






评论排行榜