在《Routing的载入》中,我大致介绍了一下Rails中最简单的route是如何加载的。这篇文章,我将来讲一讲Rails系统中更为复杂的named
route和与RESTful相关的resource是如何被加载的。为了不重复太多的笔墨,这篇文章将在前文的基础上进行,如果发现单独看此文时,有少许云里雾里,建议先看一看我的前篇文章:rails2.0.2源代码分析-routing的载入
首先,named route的载入全部发生在routing.rb中。其实named
route一点也不比普通的route高深些什么,Rails内部最终也是将named
route解析为一个普通的route保存在RouseSet类的routes数组中(还记得这家伙么?最好牢牢记住他,因为,他还会在后续文章中继续登
台发挥重要作用),之所以我称他进化,是因为named
route既然提供了name,在Rails内部,将会生成一系列的helper方法,当我们在controller或者view中使用link_to,
redirect_to等方法时,不需要指定相应的controller和action,从而简化我们的代码,不用多了,先来看一看我们所熟悉的
routes.rb
ActionController::Routing::Routes.draw do |map|
map.purchase 'products/:id/purchase', :controller => 'catalog', :action => 'purchase'
...
end
这里,我定义了一个purchase的named
route(当然,你完全可以使用connect方法定义普通的route)。前一篇文章提到过,block中的map对象是Mapper类的实例,其实你可以想象到,其实Mapper类并没有定义purchase方法(天知道你要给你的named
route起啥名?翠花?旺财?)。所有的一切,都是通过Mapper类的method_missing方法处理的。具体代码如下:
def method_missing(route_name, *args, &proc) #:nodoc:
super unless args.length >= 1 && proc.nil?
@set.add_named_route(route_name, *args)
end
如果你记性还好,应该还记得@set对象是一个RouteSet类的实例,所以这里Mapper类将这个route的名称,还有所有的参数都传递到了RouteSet类的add_named_route方法。
def add_named_route(name, path, options = {})
# TODO - is options EVER used?
name = options[:name_prefix] + name.to_s if options[:name_prefix]
named_routes[name.to_sym] = add_route(path, options)
end
这里,看到我们前面已经熟悉过了的add_route方法了吧?对此方法不用再过多解释,Rails将生成一个普通的route,保存在RouteSet
的routes数组中,并将这个route返回,赋给named_routes对象,此对象是NamedRouteCollection类的一个实例。在
NamedRouteCollection中有如下定义:
def add(name, route)
routes[name.to_sym] = route
define_named_route_methods(name, route)
end
def get(name)
routes[name.to_sym]
end
alias []= add
alias [] get
所以,接下来似乎应该关心下add方法了。这里,首先将此普通的route保存在NamedRouteCollection类的routes哈希中(注意和RouteSet的routes数组区分开来)。然后,named
route开始其“进化”了----通过define_named_route_methods方法生成自己的一系列helper方法。
def define_named_route_methods(name, route)
{:url => {:only_path => false}, :path => {:only_path => true}}.each do |kind, opts|
hash = route.defaults.merge(:use_route => name).merge(opts)
define_hash_access route, name, kind, hash
define_url_helper route, name, kind, hash
end
end
或者你已经知道了,named
route有name_url,和name_path两类helper方法,从上面这段代码中,我们能看到真正的实现。Rails这里为url和path分别生成hash
access和url helper方法,其实现都利用了ruby强大的动态特性。具体实现分别如下:
def define_hash_access(route, name, kind, options)
selector = hash_access_name(name, kind)
@module.module_eval <<-end_eval # We use module_eval to avoid leaks
def #{selector}(options = nil)
options ? #{options.inspect}.merge(options) : #{options.inspect}
end
protected :#{selector}
end_eval
helpers << selector
end
def define_url_helper(route, name, kind, options)
selector = url_helper_name(name, kind)
# The segment keys used for positional paramters
hash_access_method = hash_access_name(name, kind)
@module.module_eval <<-end_eval # We use module_eval to avoid leaks
def #{selector}(*args)
#{generate_optimisation_block(route, kind)}
opts = if args.empty? || Hash === args.first
args.first || {}
else
options = args.last.is_a?(Hash) ? args.pop : {}
args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)|
h[k] = v
h
end
options.merge(args)
end
url_for(#{hash_access_method}(opts))
end
protected :#{selector}
end_eval
helpers << selector
end
其中@module是NamedRouteCollection类中的一个匿名module,他通过module_eval方法动态的增加了这一系列的
helper方法,并且将方法名保存在helpers数组当中,以供其后controller或者view的使用(link_to,
redirect_to)。至此,named route的加载就全部完成了。十分简单,不是吗?
当然,光把RESTful和resource扯到一起似乎相当狭义,在Rails中,ActionController::Resources抽象了
REST中的Resource,这里,我不谈REST的相关概念,网上资料一大坨。我们就来看看Rails中是如何通过Resource来轻松,简便的完
成RESTful应用的吧。
resources.rb
源代码路径:/actionpack-2.0.2/lib/action_controller/resources.rb
首先,我们也不需要将resource看得多么的高深,你可以把他理解为,当你在routes.rb中定义如下的resource的时候:
map.resources :products
Rails会自动为我们生成众多的named route,这些route通过http
verb和相应的controller中的action对应起来,当然了,众多的helper方法也随即产生。如下表所示:
Named Route
|
Helpers
|
product |
product_url, hash_for_product_url,
product_path, hash_for_product_path
|
new_product
|
new_product_url, hash_for_new_product_url,
new_product_path, hash_for_new_product_path
|
edit_product
|
edit_product_url, hash_for_edit_product_url,
edit_product_path, hash_for_edit_product_path
|
... |
... |
从这个角度来想,你可以把resource想成是众多相关named
route的一个马甲。我们先从宏观的角度来看一看Rails是如何完成Resource到route转换的。
整个流程比较的直观,Rails通过resource按部就班的完成各种route的生成,接下来我们看一看核心代码是如何完成这些功能的。首先,还是在routes.rb中,可能会定义如下的resource:
ActionController::Routing::Routes.draw do |map|
map.resources :products
...
end
resources方法定义在ActionController::Resources这个module中,然后通过mixin进入到Mapper类的。那我们首先来看一看这个方法:
def resources(*entities, &block)
options = entities.extract_options!
entities.each { |entity| map_resource(entity, options.dup, &block) }
end
很简单,将entities和options从参数中分离开来,然后针对每一个entity执行map_resource操作。我们继续进行,看看map_resource方法的真面目:
def map_resource(entities, options = {}, &block)
resource = Resource.new(entities, options)
with_options :controller => resource.controller do |map|
map_collection_actions(map, resource)
map_default_collection_actions(map, resource)
map_new_actions(map, resource)
map_member_actions(map, resource)
map_associations(resource, options)
if block_given?
with_options(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], &block)
end
end
end
有了entity和options,还等什么呢?马上生成我们的Resource对象,Resource对象封装了和此resource相关的collection
method,member method,new method,path prefix,name
prefix,单/复数表示,还有option。生成这个Resource对象无非就是将此对象的相应属性从options中解析出来,保存起来,代码比较简单,这里就不再贴出。
现在,Resource对象有了,从上面代码我们就可以看出来,接下来,就该处理和此resource相关named
route了。具体的处理逻辑都类似,这里将map_member_actions(map,
resource)拿出来作为示意,感兴趣的同学们可以自己查看相关的源代码。
def map_member_actions(map, resource)
resource.member_methods.each do |method, actions|
actions.each do |action|
action_options = action_options_for(action, resource, method)
map.named_route("#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action}", action_options)
map.named_route("formatted_#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action}.:format",action_options)
end
end
show_action_options = action_options_for("show", resource)
map.named_route("#{resource.name_prefix}#{resource.singular}", resource.member_path, show_action_options)
map.named_route("formatted_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}.:format", show_action_options)
update_action_options = action_options_for("update", resource)
map.connect(resource.member_path, update_action_options)
map.connect("#{resource.member_path}.:format", update_action_options)
destroy_action_options = action_options_for("destroy", resource)
map.connect(resource.member_path, destroy_action_options)
map.connect("#{resource.member_path}.:format", destroy_action_options)
end
这里,我们可以很直观的看到,Rails为resource的member相关方法生成了众多的route,我们可以看到Controller中熟悉的
show,update,destroy
action。是的,在这里,Rails就为url到controller的action生成了相应的route。完成了这些基本的route生成外,
Rails还会处理嵌套,关联关系(存在block的处理和map_associations)。当然,基本逻辑都和上面的逻辑类似。
如此云云。。。针对Resource的一张庞大的route表就生成完成了。
相关推荐
在Ruby on Rails框架中,`Rails 2.0.2`是一个较早的版本,而分页功能在那个时期并不像现在的Rails应用那样内置在框架内。为了实现分页,开发者通常需要安装并使用第三方插件,比如"will_paginate"。这个插件允许你在...
学习资源路由(resource routing)和自定义路由规则(custom routing rules)能帮助你更好地规划应用的结构。 6. **测试(Testing)**:Rails内置了全面的测试支持,包括单元测试(unit tests)、集成测试...
通过命令行输入`rails new project_name`即可创建一个新的Rails应用项目,这里`project_name`代表你希望命名的项目名称。该命令会自动初始化一系列的目录结构和基础文件,这些文件是Rails应用程序的骨架。 **知识点...
在本文中,我们将深入探讨如何在您的计算机上安装和配置Ruby 1.8.6、Rails 2.0.2、RadRails 0.7.2 IDE以及MySQL数据库。这是一个适用于初学者和有一定经验的开发者的技术指南,旨在帮助您创建一个稳定的开发环境,...
在"rails-react-components-源码"中,我们可以看到如何定义React组件,以及如何通过props传递数据和方法。组件化开发提高了代码的可读性和可维护性,降低了复杂性。 2. **Webpack与Rails的集成** Rails默认使用...
Rails是Ruby语言的一个著名Web应用框架,以敏捷开发和“约定优于配置”...通过对这些版本的源码进行分析和学习,开发者不仅可以提升对Rails框架的理解,还能在实际项目中运用这些知识,编写出更高效、更安全的Web应用。
rails-hackernews-reddit-producthunt-clone, 黑客 news/reddit/social 链接分享网站 用 Rails 构建 Rails 上的 Reddit-Hackernews-ProductHunt克隆演示 这是一个 readme.md的Ruby on Rails 应用程序,模仿了 Hacker...
通过以上分析,我们可以掌握 Rails Exporter 的核心功能和实现方式,这对于提升我们自己的 Rails 应用开发能力大有裨益。同时,理解源码也有助于我们在遇到问题时进行调试和定制,以满足特定项目的需求。在实际开发...
总之,"depot"压缩包为学习者提供了一个宝贵的实践平台,通过实际操作,学习者可以更好地理解《Agile Web Development with Rails-Second Edition-Beta》中介绍的理论和技巧,从而提升自己的Rails开发技能。
在这个压缩包中,`awesome-rails-gem-zh_CN-master`可能是项目源码或文档的主目录。以下是一些可能包含在列表中的关键Rails Gem及其功能简介: 1. **Devise**:这是一个灵活的身份认证解决方案,提供了一套完整的...
【标题】"rails-yelp-mvp-源码" 指的是一个基于Rails框架开发的类似于Yelp(美国知名餐饮评论网站)的最小可行产品(Minimum Viable Product, MVP)的源代码。Rails是Ruby编程语言的一个流行Web开发框架,以其“约定...
`config/routes.rb`文件定义了所有路由规则,包括资源路由、命名路由和自定义路由。 6. **视图模板**: 视图使用ERB(Embedded Ruby)或更现代的Haml、Slim等模板语言,结合HTML来渲染用户界面。图片的展示、上传...
在"rails-builds-test-源码.rar"这个压缩包中,我们很显然会接触到一个使用Rails框架构建的测试项目。接下来,我们将深入探讨Rails的几个关键知识点,以及如何通过源码来理解其工作原理。 1. **Gemfile与Gemfile....
书中还涉及了如何使用Rails的路由帮助方法,如何在路由中加入条件判断,以及如何使用Rails的路由命名空间和作用域来组织代码。 路由系统与Rails的Action Controller紧密相连,Action Controller是Rails中负责处理...
rails3-mongoid-devise, 示例 Rails 3.2应用,带有数据 Mongoid,用于验证 Rails 4.1有关设计的Rails 4.1示例应用程序,请参见:rails...类似示例和教程这是来自 RailsApps项目的一系列 Rails 示例应用程序和教程中的一
- Rails的路由系统是其强大特性之一,通过routes.rb文件定义URL到控制器方法的映射,允许灵活的URL设计和资源化路由。 3. **视图模板** - ERB(Embedded Ruby)或Haml是常见的Rails视图模板语言,用于创建动态...
- **单例资源路由**: 讲解了如何处理没有复数形式的资源类型。 - **嵌套资源**: 分析了如何在路由中表示嵌套关系的资源。 - **路由关注点**: 提出了在设计复杂应用时考虑的不同路由策略。 - **RESTful路由定制**: ...
通过深入分析`ember-cli-rails-源码.zip`,我们可以学习到如何在Rails项目中高效地构建、测试和部署Ember应用,理解这两个强大框架的协同工作原理,这对于提升前端开发的效率和质量具有重要意义。