`

一个简单的link_to,ROR到底在背后做了些什么?(未完)

    博客分类:
  • RoR
阅读更多

滥用link_to会造成ror程序性能下降,其中原因是什么?一个简单的link_to背后ROR到底都作了些什么?不如追随着ror的代码让我们去看个究竟。

我们通常通过如下形式调用link_to方法 <%= link_to "action_name", {:controller=>"some_controller",:action=>"some_action",:id=>xx} %>

Link_to 代码
 
  1. def link_to(name, options = {}, html_options = nil)    
  2.     url = options.is_a?(String) ? options : self.url_for(options)    
  3.     if html_options    
  4.        html_options = html_options.stringify_keys    
  5.        href = html_options['href']    
  6.        convert_options_to_javascript!(html_options, url)    
  7.        tag_options = tag_options(html_options)    
  8.     else    
  9.        tag_options = nil    
  10.     end    
  11.     href_attr = "href=\"#{url}\"" unless href    
  12.     "    
  13. end    

可以看到,link_to内部是通过url_for这个helper方法来转换hash为url路径的,让我们去看看url_for的代码

ruby 代码
 
  1. 65       def url_for(options = {})    
  2. 66         case options    
  3. 67         when Hash    
  4. 68           options = { :only_path => true }.update(options.symbolize_keys)    
  5. 69           escape  = options.key?(:escape) ? options.delete(:escape) : true    
  6. 70           url     = @controller.send(:url_for, options)    
  7. 71         when String    
  8. 72           escape = true    
  9. 73           url    = options    
  10. 74         when NilClass    
  11. 75           url = @controller.send(:url_fornil)    
  12. 76         else    
  13. 77           escape = false    
  14. 78           url    = polymorphic_path(options)    
  15. 79         end    
  16. 80     
  17. 81         escape ? escape_once(url) : url    
  18. 82       end    

我们只关注options为hash的情况,68行将:only_path参数加入options,然后从options中去掉:escape参数,然后调用ActionController中同名的url_for方法,再调出ActionController的url_for方法

ruby 代码
 
  1. 592       def url_for(options = nil#:doc:    
  2. 593         case options || {}    
  3. 594           when String    
  4. 595             options    
  5. 596           when Hash    
  6. 597             @url.rewrite(rewrite_options(options))    
  7. 598           else    
  8. 599             polymorphic_url(options)    
  9. 600         end    
  10. 601       end    

在这个方法中,再次调用了@url的rewrite方法,rewrite_options只是空走了一遭,@url定义在方法initialize_current_url中

 
  1. 1082       def initialize_current_url    
  2. 1083         @url = UrlRewriter.new(request, params.clone)    
  3. 1084       end    

跳啊跳,再跳到UrlRewriter中,位于actionpack/lib/action_controller/url_rewriter.rb,rewrite内部调用了UrlRewrite的私有方法rewrite_url,此时我们options中因该包含:controller,:action,:id以及后来加入的:only_path=>true

ruby 代码
 
  1. 92       def rewrite_url(options)    
  2. 93         rewritten_url = ""    
  3. 94     
  4. 95         unless options[:only_path]    
  5. 96           rewritten_url << (options[:protocol] || @request.protocol)    
  6. 97           rewritten_url << "://" unless rewritten_url.match("://")    
  7. 98           rewritten_url << rewrite_authentication(options)    
  8. 99           rewritten_url << (options[:host] || @request.host_with_port)    
  9. 100           rewritten_url << ":#{options.delete(:port)}" if options.key?(:port)    
  10. 101         end    
  11. 102     
  12. 103         path = rewrite_path(options)    
  13. 104         rewritten_url << @request.relative_url_root.to_s unless options[:skip_relative_url_root]    
  14. 105         rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)    
  15. 106         rewritten_url << "##{options[:anchor]}" if options[:anchor]    
  16. 107     
  17. 108         rewritten_url    
  18. 109      end  

调用了rewrite_path来把hash转换成url

ruby 代码
 
  1. 112       def rewrite_path(options)    
  2. 113         options = options.symbolize_keys    
  3. 114         options.update(options[:params].symbolize_keys) if options[:params]    
  4. 115     
  5. 116         if (overwrite = options.delete(:overwrite_params))    
  6. 117           options.update(@parameters.symbolize_keys)    
  7. 118           options.update(overwrite.symbolize_keys)    
  8. 119         end    
  9. 120     
  10. 121         RESERVED_OPTIONS.each { |k| options.delete(k) }    
  11. 122     
  12. 123         # Generates the query string, too    
  13. 124         Routing::Routes.generate(options, @request.symbolized_path_parameters)    
  14. 125       end    

RESERVED_OPTIONS是一个数组,包含一些控制用的参数,从options中删除它们,仅仅留下:controller,:action,:id

ruby 代码
 
  1. RESERVED_OPTIONS = [:anchor:params:only_path:host:protocol:port:trailing_slash:skip_relative_url_root]  

最后,调用Routing::Routes中的generate方法将:controller,:action,:id拼接成url

ruby 代码
 
  1.  448       # Write the real generation implementation and then resend the message.   
  2. 123         # Generates the query string, too    
  3. 124         Routing::Routes.generate(options, @request.symbolized_path_parameters)    
  4. 125       end    
  5.  
  6. 449       def generate(options, hash, expire_on = {})    
  7. 450         write_generation    
  8. 451         generate options, hash, expire_on    
  9. 452       end    

这个generate是一个递归调用?先不管,看看write_generation

ruby 代码
  1. def write_generation  
  2.   # Build the main body of the generation  
  3.   body = "expired = false\n#{generation_extraction}\n#{generation_structure}"  
  4.   
  5.   # If we have conditions that must be tested first, nest the body inside an if  
  6.   body = "if #{generation_requirements}\n#{body}\nend" if generation_requirements  
  7.   args = "options, hash, expire_on = {}"  
  8.   
  9.   # Nest the body inside of a def block, and then compile it.  
  10.   raw_method = method_decl = "def generate_raw(#{args})\npath = begin\n#{body}\nend\n[path, hash]\nend"  
  11.   instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"  
  12.   
  13.   # expire_on.keys == recall.keys; in other words, the keys in the expire_on hash  
  14.   # are the same as the keys that were recalled from the previous request. Thus,  
  15.   # we can use the expire_on.keys to determine which keys ought to be used to build  
  16.   # the query string. (Never use keys from the recalled request when building the  
  17.   # query string.)  
  18.   
  19.   method_decl = "def generate(#{args})\npath, hash = generate_raw(options, hash, expire_on)\nappend_query_string(path, hash, extra_keys(options))\nend"  
  20.   instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"  
  21.   
  22.   method_decl = "def generate_extras(#{args})\npath, hash = generate_raw(options, hash, expire_on)\n[path, extra_keys(options)]\nend"  
  23.   instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"  
  24.   raw_method  
  25. end  

 


首先动态的生成一个语句块,分别调用了generation_extraction和generation_structure,第六行根据传入参数决定是否需要给刚才定义的语句块加上条件。generation_extraction的代码如下:

ruby 代码
 
  1. # Build several lines of code that extract values from the options hash. If any  
  2. # of the values are missing or rejected then a return will be executed.  
  3. def generation_extraction  
  4.   segments.collect do |segment|  
  5.     segment.extraction_code  
    1.   end.compact * "\n"  
  6. end  

 

segments是一个数组,其中可能存放不同种类的segment,例如segement,dynamic segment, static segment等。对segments中的元素依次调用各自的extraction_code方法,并将每次调用结果收集起来结果包装成一个数组。segments是一个数组,对其中每一个元素调用extraction_code方法,在segment类中定义了这个方法,不过方法体为空

ruby 代码
  1. def extraction_code  
  2.   nil  
  3. end  

 

在segment的子类DynamicSegement中重定义了此方法

 

java 代码
 
  1. #相关代码  
  2.       def extraction_code  
  3.         s = extract_value  
  4.         vc = value_check  
  5.         s << "\nreturn [nil,nil] unless #{vc}" if vc  
  6.         s << "\n#{expiry_statement}"  
  7.       end  
  8.   
  9.       def extract_value  
  10.         "#{local_name} = hash[:#{key}] && hash[:#{key}].to_param #{"|| #{default.inspect}" if default}"  
  11.       end  
  12.   
  13.       def value_check  
  14.         if default # Then we know it won't be nil  
  15.           "#{value_regexp.inspect} =~ #{local_name}" if regexp  
  16.         elsif optional?  
  17.           # If we have a regexp check that the value is not given, or that it matches.  
  18.           # If we have no regexp, return nil since we do not require a condition.  
  19.           "#{local_name}.nil? || #{value_regexp.inspect} =~ #{local_name}" if regexp  
  20.         else # Then it must be present, and if we have a regexp, it must match too.  
  21.           "#{local_name} #{"&& #{value_regexp.inspect} =~ #{local_name}" if regexp}"  
  22.         end  
  23.       end  
  24.   
  25.       def expiry_statement  
  26.         "expired, hash = true, options if !expired && expire_on[:#{key}]"  
  27.       end  

 

根据上面几个方法的源代码,可以推断出write_generation中共动态生成了三个方法,列出方法如下:

ruby 代码
  1. def generate_raw(options, hash, expire_on = {})  
  2.    path = begin  
  3.      expired = false  
  4.      key_value = hash[:key] && hash[:key].to_param (|| option[:default] )  
  5.      return [nilnilunless (key_value.nil? ||) /\A#{regexp.source}\Z/.inspect =~key_value  
  6.      expired, hash = true, options if !expired && expire_on[key]  
  7.    end  
  8.    [path, hash]  
  9. end  
  10.   
  11. def generate(options, hash, expire_on={})  
  12.   path, hash = generate_raw(options, hash, expire_on)  
  13.   append_query_string(path, hash, extra_keys(options)  
  14. end  
  15.   
  16. def generate_extras(options, hash, expire_on = {})  
  17.   path, hash = generate_raw(options, hash, expire_on)  
  18.   [path, extra_keys(options)]  
  19. end  

注意在值钱的generate方法中曾经调用国generate方法本身,之前我以为是递归,现在看来,是在generate方法中调用write_generate方法重新生成了一个generate方法,然后调用此方法,也就是上面生成的三个方法之一。看来已经一步步接近真相了,在新生成的方法中调用了append_query_string方法,并且接受被extra_keys方法处理过的options作为参数。


 

ruby 代码
 
  1. def append_query_string(path, hash, query_keys=nil)  
  2.   return nil unless path  
  3.   query_keys ||= extra_keys(hash)  
  4.   "#{path}#{build_query_string(hash, query_keys)}"  
  5. end  

 


 

ruby 代码
 
  1. def extra_keys(hash, recall={})  
  2.   (hash || {}).keys.map { |k| k.to_sym } - (recall || {}).keys - significant_keys  
  3. end  
  4.   
  5. def build_query_string(hash, only_keys = nil)  
  6.   elements = []  
  7.   
  8.   (only_keys || hash.keys).each do |key|  
  9.     if value = hash[key]  
  10.       elements << value.to_query(key)  
  11.     end  
  12.   end  
  13.   
  14.   elements.empty? ? '' : "?#{elements.sort * '&'}"  
  15. end  

 


 

ruby 代码
 
  1. def significant_keys  
  2.   @significant_keys ||= returning [] do |sk|  
  3.     segments.each { |segment| sk << segment.key if segment.respond_to? :key }  
  4.     sk.concat requirements.keys  
  5.     sk.uniq!  
  6.   end  
  7. end  

 

to_query方法定义在activesupport/lib/active_support/core_ext/hash/conversions.rb 中

 

分享到:
评论
1 楼 lnj888 2010-05-27  
很是有用 不错
Powerpoint converter

相关推荐

    RoR性能优化经验谈

    总之,RoR性能优化是一个全面的过程,涵盖从操作系统到Web服务器配置,再到代码本身的改进。每个环节的优化都能显著提升网站的运行效率,使RoR应用能够更好地应对高负载和大规模用户的需求。通过学习和实践这些经验...

    RoR,十分钟做Blog

    【RoR,十分钟做Blog】这篇教程主要介绍了如何使用Ruby on Rails(RoR)框架在NetBeans IDE上快速创建一个简单的博客程序。RoR是一个基于MVC(模型-视图-控制器)架构的Web开发框架,它使得开发过程更加高效且简洁。...

    ror中文资料

    Ruby on Rails(RoR)是一个基于Ruby编程语言的开源Web应用框架,遵循MVC(Model-View-Controller)架构模式,旨在简化Web开发过程,提高开发效率。RoR强调“约定优于配置”,提供了一套完整的工具链,使得开发者...

    神经网络ror resenet模型

    ResNet的核心是引入了“残差块”(Residual Block),通过引入一个恒等映射(Identity Mapping)的跳跃连接(Skip Connection),使得网络能够更轻松地学习到输入数据的微小变化。在每个残差块中,输入信号可以直接...

    ror

    NULL 博文链接:https://xuxiangpan888.iteye.com/blog/266696

    ROR安装必备所有架包

    在Ruby on Rails(ROR)开发环境中,安装和配置正确的依赖包是至关重要的。这个压缩包包含了一系列用于ROR框架的基础组件,但不包括Ruby本身。让我们深入了解一下这些包的作用和重要性。 首先,`actionpack`是Rails...

    FusionCharts_RoR

    标题"FusionCharts_RoR"涉及的是FusionCharts公司的一款产品在Ruby on Rails(RoR)框架中的应用。FusionCharts是一款流行的JavaScript图表库,它提供了丰富的交互式图表和图形,用于将数据可视化。Ruby on Rails是...

    [转]完美的Tree

    这个标题可能是指一个关于如何高效、优雅地实现或使用树结构的教程或者代码分享。 【描述】:虽然描述部分为空,但我们可以推测这篇博文可能详细介绍了如何创建一个“完美”的树结构,可能包括了优化树的操作,如...

    初探ROR

    Ruby on Rails(简称ROR)是一个基于Ruby编程语言的开源Web应用程序框架,它遵循MVC(模型-视图-控制器)架构模式,旨在促进开发过程的简洁性和效率。Ruby on Rails的核心理念是“Don't Repeat Yourself”(DRY,...

    RoR选题方向—源代码

    Ruby on Rails(RoR)是一种基于Ruby语言的开源Web应用程序框架,它遵循MVC(Model-View-Controller)架构模式,旨在简化Web开发过程。在这个选题方向中,我们主要探讨的是与RoR相关的源代码分析和学习。源代码是...

    ror实例

    "ror实例"可能指的是在学习或实践中,通过创建一个具体的Rails应用程序来理解和掌握RoR的工作原理和最佳实践。 在Ruby on Rails中,重要知识点包括: 1. **Gemfile与Gemfile.lock**:项目中的Gemfile用于指定项目...

    ROR 文件的上传与下载

    在给定的示例中,使用MySQL数据库创建了一个名为`records`的表,其中包含两个关键字段:`name`和`person`。`name`字段用于存储上传文件的名称,而`person`字段则是一个`MEDIUMBLOB`类型,用于保存上传文件的二进制流...

    Gibberish:一个 Python Translator Gibberish 模块,它是在我睡前写的,灵感来自 www.reddit.comrswedencomments301sqrdodetot_äror_foföror_lolitote

    一个 Python 的翻译器/乱码模块,它是在我睡前写的,灵感来自 示例用法 - 从命令行运行 #Turn a sentence into gibberish in Swedish &gt;python Gibberish.py -g "My hovercraft is full of eels." "Swedish" ...

    RoRBlog 基于RoR的博客系统

    基于RoR的博客系统,代码风格简单清晰,前后太完善,适合初学者。

    blog_gcm_and_ror

    GCM是Google提供的一个推送通知服务,允许开发者向Android应用发送消息,而Ruby on Rails则是一个流行的开源Web应用程序框架,用于构建后端服务器。 **描述分析:** 描述中提到"Google Cloud Messaging:Android ...

    intimate_web:一个 ROR 应用程序

    "intimate_web" 是一个基于 Ruby on Rails (ROR) 框架开发的应用程序。Ruby on Rails 是一种流行的开源 Web 开发框架,它遵循 Model-View-Controller (MVC) 设计模式,用于构建数据库驱动的 web 应用程序。这个应用...

    shape_bender形体弯曲工具ror sketchup2015

    shape_bender形体弯曲工具ror sketchup2015

    Windows 上搭建 ROR环境

    虽然这是一个较老的版本,但在当时背景下,它能很好地支持Rails 3.0.9。 - **安装过程**:Ruby的安装相对简单,只需访问官方提供的下载页面,选择适合Windows系统的安装包进行下载。安装过程中,默认选项即可,如有...

    我的ror的第一天

    标题 "我的ror的第一天" 暗示了这是一个关于Ruby on Rails(简称RoR)的初学者经验分享,RoR是基于Ruby语言的开源Web应用框架,它遵循MVC(模型-视图-控制器)架构模式,使得开发过程更加高效。 在描述中提到的...

    基于sqlite的ror例子

    描述中提到的是一个完整的程序示例,这可能包括了RoR应用的创建、配置、模型定义、迁移以及数据操作等步骤。通常,在RoR中设置SQLite数据库涉及以下步骤: 1. **初始化项目**:使用`rails new`命令创建一个新的RoR...

Global site tag (gtag.js) - Google Analytics