`
虫子樱桃
  • 浏览: 2196 次
  • 性别: Icon_minigender_1
  • 来自: 成都
最近访客 更多访客>>
文章分类
社区版块
存档分类
最新评论

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等动态方法。

补充几点:
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 

  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

分享到:
评论

相关推荐

    Rails101_by_rails4.0

    《Rails101_by_rails4.0》是一本专注于Rails 4.0.0版本和Ruby 2.0.0版本的自学教程书籍,它定位于中文读者,旨在成为学习Rails框架的参考教材。Rails(Ruby on Rails)是一个采用Ruby语言编写的开源Web应用框架,它...

    rails_admin_acts_as_list:rails_admin插件以对记录进行排序

    介绍插件,用于对记录进行排序(使用 gem)安装要启用rails_admin_acts_as_list,请将以下内容添加到您的Gemfile : gem 'rails_admin_acts_as_list'gem 'rails_admin' 重要提示: rails_admin_acts_as_list之前必须...

    rails_semantic_logger, Rails 语义记录器用语义记录器替换 Rails 缺省记录器.zip

    rails_semantic_logger, Rails 语义记录器用语义记录器替换 Rails 缺省记录器 Rails 语义记录器 语义记录器用语义记录器替代 Rails 缺省记录器。http://github.com/rocketjob/rails_semantic_logger文档有关完整文档...

    inspinia admin - v2.5 Rails_Full_Version

    "inspinia admin - v2.5 Rails_Full_Version" 是一个基于Rails框架构建的后台管理系统的完整版本。这个系统采用流行的Inspinia Admin模板,提供了丰富的功能和自定义选项,旨在帮助开发者快速构建高效、现代且用户...

    rails_apps_composer, 一个 gem,为 Rails 启动应用程序创建 Rails 应用程序模板.zip

    rails_apps_composer, 一个 gem,为 Rails 启动应用程序创建 Rails 应用程序模板 Rails 应用编辑器 Rails 应用程序编辑器 gem 安装一个 命令行 工具来从"食谱"的Collection 组装 Rails 应用程序。"你可以使用 rails_...

    RestFul_Rails_Dev_pdf_v_0.1.zip

    本资料“RestFul_Rails_Dev_pdf_v_0.1.zip”包含了《RESTful Rails Development》的翻译版,将深入探讨如何在Rails中实现RESTful的设计模式。 首先,RESTful设计的核心概念是资源(Resources)。在Rails中,资源...

    Api-rails5_api_tutorial.zip

    Api-rails5_api_tutorial.zip,了解如何在michael hartl的rails 5教程上构建一个现代api立即在rails应用程序中构建一个api!(Rails 5版本),一个api可以被认为是多个软件设备之间通信的指导手册。例如,api可用于web...

    rails open_flash_chart

    而"工具"可能是指利用这个库创建图表时所需的辅助工具,比如用于生成JSON数据的Rails助手方法。 虽然现在Flash已经不再被广泛支持,HTML5的Canvas和SVG等技术成为新的趋势,但在过去,Open Flash Chart为Rails...

    InspiniaAdmin 2.6.1 Rails_Full_Version

    在Rails_Full_Version压缩包中,包含了完整的源代码和必要的资源文件,开发者可以通过解压并导入到Rails项目中,按照官方文档进行配置和定制。同时,这个版本可能还包含了升级记录、更改日志和可能的bug修复,以保证...

    agile_web_development_with_rails_3rd_edition.9994652073.pdf

    《敏捷Web开发与Rails》第三版是一本深入探讨Ruby on Rails框架在敏捷软件开发方法论下的应用指南。本书由多位知名作者共同编写,包括Sam Ruby、Dave Thomas、David Heinemeier Hansson等,他们都是在Ruby社区内享有...

    rails3 和 thinking_sphinx安装

    Rails3 是 Ruby on Rails 框架的第三个主要版本,它在灵活性、性能和社区支持方面都有显著提升。Thinking Sphinx 是一个为 Rails 应用程序提供全文搜索功能的插件,它将数据库中的数据与 Sphinx 搜索引擎紧密结合,...

    Rails_3_Cheat_Sheets.pdf

    Rails_3_Cheat_Sheets.pdf

    Ruby-on-Rails-rails.zip

    Ruby_on_Rails_rails.zip Ruby_on_Rails_rails.zip Ruby_on_Rails_rails.zip Ruby_on_Rails_rails.zipRuby_on_Rails_rails.zip Ruby_on_Rails_rails.zip Ruby_on_Rails_rails.zip Ruby_on_Rails_rails.zipRuby_on_...

    rails_best_practices:Rails项目的代码度量工具

    rails_best_practices rails_best_practices是用于检查Rails代码质量的代码度量工具。 它支持以下ORM / ODM: 活动记录 蒙古型 mongomapper 以及以下模板引擎: erb 哈姆 减肥 拉布尔 rails_best_practices...

    Rails_Recipes_with_Source_Code

    9. **AJAX与JavaScript**:Rails与jQuery和CoffeeScript等JavaScript库有良好的集成,书中可能探讨如何创建异步请求和实现动态更新。 10. **部署(Deployment)**:书中可能会介绍如何将Rails应用部署到Heroku、...

    inspinia_admin_v2.5_Rails_Full_Version

    该模板包含的文件列表“inspinia_admin_v2.5_Rails_Full_Version”很可能包括以下组成部分: 1. **静态资源**:CSS样式文件、JavaScript脚本和图像资源。这些文件用于构建用户界面,包括响应式布局、图表、表单元素...

    rails_admin_content_builder:使用rails_admin创建内容的简单方法

    gem 'rails_admin_content_builder' 运行生成器并进行迁移 rails g rails_admin_content_builder rake db:migrate 在app / assets / application.scss中添加样式 * = require rails_admin_content_builder 用法 ...

    InspiniaAdmin 2.5 Rails_Seed_Project

    InspiniaAdmin 2.5 Rails_Seed_Project是一款基于Bootstrap框架的高级管理模板,专为Ruby on Rails开发者设计,旨在加速Web应用程序的开发过程。这个项目作为种子启动器,提供了完整的后台管理界面,包括丰富的UI...

    inspinia admin - v2.5 Rails_Seed_Project

    本文将详细探讨其在Rails框架下的种子项目(Rails_Seed_Project),旨在帮助开发者更好地理解和运用这一强大的工具。 首先,我们来了解一下“Inspinia Admin”。这是一款基于Bootstrap 3构建的响应式后台模板,提供...

    redmine-find_by_sql

    总结起来,`find_by_sql`是Redmine(以及许多其他使用ActiveRecord的Rails应用)中的一个强大工具,它允许开发人员执行自定义SQL查询以获取数据。然而,使用时需谨慎,确保SQL语句的安全性和性能,同时在JavaScript...

Global site tag (gtag.js) - Google Analytics