- 浏览: 1250078 次
- 性别:
- 来自: 北京
文章分类
- 全部博客 (729)
- Java_about (144)
- Spring_Hibernate_Struts_OpenSource (27)
- linux_unix (62)
- life_sth (21)
- js_css_html_xml_nodejs (69)
- design_pattens (1)
- Perl (8)
- php_ecshop (4)
- DB_Mysql_Oracle_Informix_SqlServer (43)
- JSTL (8)
- Testing_自动化测试 (42)
- DB_ID_UUID (4)
- SEM_SEO (1)
- english_study_improvement (4)
- SVN_Git (9)
- WebService_SOA_CloudComputing (3)
- E-Commerce (1)
- Lucene_Solr (7)
- others (2)
- Regex (2)
- tomcat_jetty (8)
- zeroc-ice (1)
- java_excel (5)
- ant_maven_gradle (5)
- Unity_VR_AR_C# (2)
- jmeter (1)
- XPath_dom4j (1)
- Ruby_and_Rails (68)
- write_a_rails (17)
- manage_and_team (1)
- getting_real (1)
- ubuntu (20)
- git_and_git_flow (7)
- TODO (1)
- PM_design (2)
- Python_and_Django (8)
- NoSql_mongo_redis (24)
- C/C++ (3)
- vi_vim_gvim (0)
- c#_.Net_windows编程_dll (10)
- Php_and_Yii (9)
- Android_IOS (31)
- Mysql (5)
- sa_运维_network_硬件 (37)
- lua (2)
- c_cpp_VisualStudio (21)
- 硬件-RM-Arduino (6)
最新评论
-
shenkun58:
...
NoClassDefFoundError: Could not initialize springframework.BeanCreationException -
liaojia1:
正解,感谢
NoClassDefFoundError: Could not initialize springframework.BeanCreationException -
flingfox63:
谢谢分享,电脑上有IPV6,导致了Guard启动不了……
ruby错误解决: Address family not supported by protocol - connect(2) -
c39274936:
s = "hello_world_ruby" ...
驼峰格式和下划线格式转换_translation between camel and snake format -
yfj300:
学习了学习了学习了学习了
硬盘基本知识(磁道、扇区、柱面、磁头数、簇、MBR、DBR)
Rails源码阅读(九)ActionView::Base_用户请求在rails中的处理流程(4)
衔接
ActionController中使用来@template.render来生成页面内容。这个@template就是ActionView::Base.new出来的实例。
ActionController中的具体代码:
response.template = ActionView::Base.new(self.class.view_paths, {}, self)
分析:
#1 controller持有view的一个实例(@template)
根据new方法和ActionView::Base的new参数可以,view也持有controller一个实例:
ActionView::Base的初始化方法:
def initialize(view_paths = [], assigns_for_first_render = {}, controller = nil)#:nodoc: @assigns = assigns_for_first_render @assigns_added = nil @controller = controller #这里持有!!! @helpers = ProxyModule.new(self) self.view_paths = view_paths @_first_render = nil @_current_render = nil end
#2 self,即当前controller的实例传入来view中
这样有个结论:view和controller互相持有对方的实例,难怪说VC不分家呢(放在一起的action_pack)
#3 ActionView里的view_paths是controller传入的,而且传入的是类的view_paths方法,即公共路径。
因为controller里持有view的一个实例,因此可以在controller里面修改view的view_paths所包含的路径。
例如Controller的实例方法:
def prepend_view_path(path) @view_paths = superclass.view_paths.dup if !defined?(@view_paths) || @view_paths.nil? @view_paths.unshift(*path) end
过程是复制一份class的view_paths,成为本实例的view文件寻找路径,这里例子只显示了怎么用:加入view_paths路径的顶端。
ActionView::Base中render的执行
也就是erb页面的生成过程。这个过程比较复杂的地方在于:参数多;套用模板;使用了helper;等。
render的代码分析:
#返回值字符串 #options里面的参数是Controller中传入的,例如:参数file的类型是ActionView::ReloadableTemplate。 #可见控制都是在Controller做的。 def render(options = {}, local_assigns = {}, &block) #:nodoc: local_assigns ||= {} case options when Hash #主要的入口开始=> options = options.reverse_merge(:locals => {}) if options[:layout] #:layout 入口 _render_with_layout(options, local_assigns, &block) elsif options[:file] #:file 文件入口 template = self.view_paths.find_template(options[:file], template_format) template.render_template(self, options[:locals]) elsif options[:partial] #:partial 局部模板入口 render_partial(options) elsif options[:inline] #:inline 入口 InlineTemplate.new(options[:inline], options[:type]).render(self, options[:locals]) elsif options[:text] #不需要处理 options[:text] end when :update #ajax用的 update_page(&block) else #可以看出来,在render的时候不写:partial => "xxx"则默认使用partial。 #从团队合作和代码管理的角度来说,不建议这么写。直接抛异常算不算好呢? render_partial(:partial => options, :locals => local_assigns) end end
不论渲染什么,最终都要调用template的render_template方法。
而这个方法,会去找到相应的view模板处理器handler(例如ERB的handler),调用compile(template)方法,返回需要的字符串,再处理后,返回最终的结果。
详细分析ActionView::Base#render,如何使用layout等
1)#:file
template = self.view_paths.find_template(options[:file], template_format)
template.render_template(self, options[:locals])
调用了ActionView::Template的render_template方法,这个方法又调
用了ActionView::Renderable的render(view, local_assigns),使用了compile(local_assigns),
继而使用了compile!(render_symbol, local_assigns)
在compile!(render_symbol, local_assigns)这里做了很多事情(略),结果是找到了正确的handler(拿erb来说)处理了view文件的内容,
把结果的erb.src生成的代码+local_assigns代码合在一起作为source,
用作ActionView::Base::CompiledTemplates.module_eval(source, filename, 0)
ActionView::Base::CompiledTemplates是在ActionViewBase中定义的过度模块,包含入这个模块的方
法(上面module_eval)会被include进ActionViewBase中
module CompiledTemplates #:nodoc: # holds compiled template code end include CompiledTemplates
这样,local_assigns中的内容最终用作了方法内变量,避免全局变量的干扰。
compile!的细节:
生成了一个方法字符串,包括:local_assigns的变量等;包括erb解析的文件的src字符串内容,等。
例子:
实验的一个例子1:
render :file => "users/index", :locals => {:xxx_xxx_a => 300, :xxx_xxx_b => 400}
生成的一个source的内容,字符串:
" def _run_erb_app47views47users47index46html46erb_locals_xxx_xxx_a_xxx_xxx_b(local_assigns) old_output_buffer = output_buffer;xxx_xxx_a = local_assigns[:xxx_xxx_a];xxx_xxx_b = local_assigns[:xxx_xxx_b];;@output_buffer = ''; __in_erb_template=true ; @output_buffer.concat "users list"; @output_buffer ensure self.output_buffer = old_output_buffer end "
整理一下,去掉字符串包装,最终好看的形式是:
def _run_erb_app47views47users47index46html46erb_locals_xxx_xxx_a_xxx_xxx_b(local_assigns)
old_output_buffer = output_buffer
;xxx_xxx_a = local_assigns[:xxx_xxx_a]
;xxx_xxx_b = local_assigns[:xxx_xxx_b]
;
;@output_buffer = ''
; __in_erb_template=true
; @output_buffer.concat "users list"
; @output_buffer
ensure
self.output_buffer = old_output_buffer
end
我自己实验的一个例子2:
render :file => "layout/application_layout"
生成的一个source的内容:
大致同上,就不贴了
整理一下,最终好看的形式是:
def _run_erb_app47views47layouts47application_layout46html46erb(local_assigns) old_output_buffer = output_buffer ; ;@output_buffer = '' ; __in_erb_template=true ; @output_buffer.concat "<html> \ n \ n<head> \ n <title> \ n " ; @output_buffer.concat(( " #{params[:controller]}##{params[:action]}" ).to_s) ; @output_buffer.concat "\n </title>\n</head>\n\n\n<dody>\n\n <div style=\"background-color: #ffebcd;\">\n <h2>PAGE HEAD</h2>\n </div>\n\n <div>\n " ; @output_buffer.concat(( yield ).to_s) ; @output_buffer.concat "\n </div>\n\n <div style=\"background-color: #f5f5dc;\">\n <h2>PAGE FOOT</h2>\n </div>\n\n</dody>\n\n\n</html>" ; @output_buffer ensure self.output_buffer = old_output_buffer end
生成的方法名,是怎么规定的呢?(47表示/,46表示.)
def method_name(local_assigns) if local_assigns && local_assigns.any? method_name = method_name_without_locals.dup method_name << "_locals_#{local_assigns.keys.map { |k| k.to_s }.sort.join('_')}" else method_name = method_name_without_locals end method_name.to_sym end
生成的方法什么时候使用呢?在ActionView::Renderable#render方法:
#ActionView::Renderable#render def render(view, local_assigns = {}) compile(local_assigns) view.with_template self do view.send(:_evaluate_assigns_and_ivars) view.send(:_set_controller_content_type, mime_type) if respond_to?(:mime_type) view.send(method_name(local_assigns), local_assigns) do |*names| #在这里使用!!! ivar = :@_proc_for_layout if !view.instance_variable_defined?(:"@content_for_#{names.first}") && view.instance_variable_defined?(ivar) && (proc = view.instance_variable_get(ivar)) view.capture(*names, &proc) #如果定义了proc的layout,会执行这里 elsif view.instance_variable_defined?(ivar = :"@content_for_#{names.first || :layout}") #会走这里 view.instance_variable_get(ivar) end end end end
view.send(method_name(local_assigns), local_assigns) do |*names| #在这里使用!!!
这里用send调用了生成的方法。
注意这里有个&block,如果生成的方法有yield的话,这个block就会执行,否则就不执行了。
看上面的例子,例子1没有yield,这样block不会被击中,仅仅执行方法。
例子2中有yield,不仅方法被执行,block也会执行。
进一步分析
如果在layout中使用的yield没有参数的话(yield),这样block接收的参数*names为空;
ivar = :"@content_for_#{names.first || :layout}" #ivar默认使用layout字符串生成@content_for_layout
这时会默认返回@content_for_layout这个实例变量的值,返回值插入buffer中:
; @output_buffer.concat(( yield ).to_s)
如果yield使用了参数,例如使用了yield :body_left,这个时候erb生成的src会类似这个样子:
; @output_buffer.concat(( yield :body_left ).to_s)
这样block中的参数个数是1,names.first会是:body_left #这样ivar为@content_for_body_left
这个时候会执行view.instance_variable_get(ivar)并返回,返回值插入yield :body_left的位置。
小结:
#1 :file的内容是在compile!里面用erb处理并返回了src代码,之后在module_eval执行,得到了的结果存入了@output_buffer中。
#2 没有使用layout。其实render :file就是去解析view模板并执行,作用是单一的,并不区分layout这个东西,layout属于控制端的事情,根据需要来定制。
#3 layout中,yield(:xxx = :layout)的位置,会用实例变量@content_for_xxx来代替
例如:如果页面有内容<% @content_for_body_left = "I am Fantaxy!" %>,
那么"I am Fantaxy!"会插入yield :body_left的位置。(注意是实例变量)
2)#:layout
当渲染的页面有layout的时候,options会包含这两个参数:
:file => view文件的路径
:layout => 模板的路径
_render_with_layout(options, local_assigns, &block)方法:
def _render_with_layout(options, local_assigns, &block) #:nodoc: partial_layout = options.delete(:layout) #得到了layout的名字,删除了layout参数 if block_given? begin @_proc_for_layout = block concat(render(options.merge(:partial => partial_layout))) ensure @_proc_for_layout = nil end else begin # 暂且关注主要逻辑,从这里开始 original_content_for_layout = @content_for_layout if defined?(@content_for_layout) @content_for_layout = render(options) #1 当删除了layout参数后,这里变成了渲染:file,并得到内容保存在了实例变量@content_for_layout if (options[:inline] || options[:file] || options[:text]) @cached_content_for_layout = @content_for_layout render(:file => partial_layout, :locals => local_assigns) #2 这里把模板当成普通的:file渲染 else render(options.merge(:partial => partial_layout)) end ensure @content_for_layout = original_content_for_layout end end end
#1 渲染过程
@content_for_layout = render(options) #1 当删除了layout参数后,options里剩下了:file,
这样去渲染:file,得到渲染的内容保存在了实例变量@content_for_layout,这个值用处见上面。
render(:file => partial_layout, :locals => local_assigns) #2 这里把模板当成普通的:file渲染
渲染的中间,因为有yield,需要用到@content_for_xxx,把@content_for_xxx插入yield的位置(默认使用@content_for_layout)
#2 begin 和 ensure的使用是为了保持原来环境
比如,原来就有个变量就叫@content_for_xxx,这里的hack不会影响其他使用。很好很强大!
3)render :partial => "xxx_file_path"
代码:render_partial(options) 调用链:
调用:ActionView::Partials模块的render_partial(options = {})
核心调用:_pick_partial_template(partial_path).render_partial(self, options[:object], local_assigns)
调用:RenderablePartial#render_partial(view, object = nil, local_assigns = {}, as = nil)方法
调用:render_template(view, local_assigns)
调用:render
这里主要是用了Template的render方法,这个方法调用compile!,这个方法详细见前面。
4)render :inline => 'xxx_ERB_string'
代码:InlineTemplate.new(options[:inline], options[:type]).render(self, options[:locals])
因为InlineTemplate中include了Renderable
最终调用的还是ActionView::Renderable#render方法
这里有点不同,InlineTemplate.new里面,把source hack进去了。代码:
#ActionView::InlineTemplate#initialize
def initialize(source, type = nil)
@source = source #这里!!!
@extension = type
@method_segment = "inline_#{@source.hash.abs}"
end
这样compile!方法从erb模板中去解析source这一步,在ActionView::TemplateHandlers::ERB#compile,就跳过去了。
所以,:inline方法,比:text好的地方在于,:inline的值可以是erb格式的字符串。(谁这么用啊,是不是rails该好好精简了,花哨的不要)
总结:
#1 两个render方法位置不同,前者主导,前者调用后者
ActionController::Base#render
ActionView::Base#render
#2 ActionView::Base#render杂性和脉络
view是给用户看的,需求变化差异很大,属于web中最杂乱的地方,不容易形成统一的模式。
rails中为了使用上的简单,一致等,把erb,js,ajax等都柔和到一起;
支持了layout;支持了rjs,xml,erb等模板;
但复杂是慢慢填上去的,脉络一致,基本符合ERB模板渲染的过程。
可以参考原理介绍:动手写rails(二)Rails_Ruby_ERB使用_模板_定制
||
| |
| |
====结束====
=== ===
== ==
= =
| |
- rails_stuty_ActionViewBase_and_more.txt.tar.gz (5 KB)
- 下载次数: 0
发表评论
-
Rails外如何启动rails的类自动加载_activates autoloading using ActiveSupport 3.x
2016-06-22 12:08 554The following cod ... -
Rails源码阅读(13)rails中的autoload和ruby的autoload
2014-07-30 17:13 1854Rails源码阅读(13)rails中的autoload和 ... -
Rails源码阅读(12)叫Rails的模块module_Rails常量使用
2014-07-02 09:35 991The module nams "Rail ... -
Rails源码阅读(11)Rails使用bundle保持多机器环境gem版本的一致性
2013-09-05 19:21 1391Rails源码阅读(11)Rails使用bundle ... -
Rails源码阅读(十)在console 使用ActionController::Integration::Session
2012-05-05 14:48 1703Rails源码阅读(十)在console 使用Actio ... -
Rails源码阅读(八)ActionController::Base_用户请求在rails中的处理流程(3)
2012-04-06 23:01 1659Rails源码阅读(八)Actio ... -
Rails源码阅读(七)ActionController::Dispatcher_用户请求在rails中的处理流程(2)
2012-04-06 22:25 1299Rails源码阅读(七)Actio ... -
动手写rails(二)Rails_Ruby_ERB使用_模板_定制
2012-04-07 20:46 2483动手写rails(二)Rails_Ruby_ERB使用_模板_ ... -
Rails源码阅读(六)ActionController::Dispatcher_用户请求在rails中的处理流程(1)
2012-03-28 00:17 1638Rails源码阅读(六)ActionController::D ... -
Rails源码阅读(零)_config/boot
2012-03-15 11:56 1714不论是script/console 还是 script/ser ... -
动手写rails(一)_Rack标准和HttpServer之WEBrick
2012-03-15 07:22 2023无论如何,最终的结果是要启动一个server来接受请求, ... -
Rails源码阅读(五)with_scope 和 named_scope
2012-02-02 15:24 1363Rails源码阅读(四)with_scope and name ... -
Rails源码阅读(四)gem_rubygems之require_Rails_require_深入理解(一)
2011-11-16 10:39 2057Rails源码阅读(四)rubygems之require_Ra ... -
Rails源码阅读(三)Rails::Initializer
2011-10-14 10:58 2102启动的落脚点 不论启动console还是启动serve ... -
Rails源码阅读(二)_script/server
2011-09-17 18:51 1388Rails源码阅读(二)_script/server ... -
Rails源码阅读(一)_script/console
2011-09-05 11:13 2084Rails源码阅读_script/console启动 ...
相关推荐
Rails::API 移除了 ActionView 和其他一些渲染功能,不关心Web前端的开发者可更容易、快速地开发应用程序,因此运行速度比正常的 Rails 应用程序要快。 Rails::API 可以用来创建只提供API服务(API-Only)的 Rails ...
For-Rails-Beginners::Japanese_symbol_for_beginner:Ruby on Rails的初学者有福了
基本CSS框架base_css-rails是一个将添加到应用程序的资产管道的瑰宝安装将此行添加到您的应用程序的Gemfile中: gem 'base_css-rails'然后执行: $ bundle或将其自己安装为: $ gem install base_css-rails用法使用...
Rails Admin枚举4 Rails Admin插件,用于正确查看Rails 4.1枚举 安装 gem 'rails_admin_enum4', github: 'sibext/rails_admin_enum4' bundle install 用法 通过生成器生成新模型: rails g model Sibext service:...
延迟加载整个血腥路由,因此应用程序可以快速启动:sign_of_the_horns:route_lazy_routes route_lazy_routes是一个邪恶的Rails插件,它延迟加载整个血腥路由,直到服务器获得第一个请求,因此应用程序可以旋转快起来...
在您的config/initializers/rails_admin.rb初始化程序中添加配置: RailsAdmin . config do | config | config . model Post do list do sort_by :position # Add Default sorting sort_reverse false # sort p
持续移动且不破坏事物 :factory: :building_construction: :construction: :construction_worker: 正在建设中-请稍后再回来!
主存储库已移至gitlab,所有新代码将在其中: 它的外观和感觉完全类似于但是使用了jQuery Nested Sortable和或而不是Nestable和Ancestry 。 与和awesome_nested_set一起使用。 奖励功能: 对带有rails_admin_...
允许您使用为Rails应用程序的前端供电。 是将前端工具像Ruby一样进行编程,纯属喜悦! :smiling_face_with_heart-eyes: 或在运行的检查。 产品特点 :high_voltage: :light_bulb: 即时服务器启动 :high_voltage: ...
在Rails应用程序的根目录中,运行: rails_best_practices . 或用于HTML输出: rails_best_practices -f html . 默认情况下,rails_best_practices将解析vendor , spec , test和features目录中的代码。 排除目录...
其余都是可选的) 更简单的提示修改(类似于你已经熟悉的默认提示) 无需担心配置(因为反正没有太多选择)安装宝石档案: gem 'awesome_rails_console' 在终端: bundlerails g awesome_rails_console:install # ...
自述文件 该自述文件通常会记录启动和运行应用程序所需的所有步骤。 您可能要讲的内容: Ruby版本 系统依赖 配置 数据库创建 数据库初始化 如何运行测试套件 服务(作业队列,缓存服务器,搜索引擎等) ...
Rails管理员状态机 主存储库已移至gitlab,所有新代码将在其中: 从rails_admin正确管理状态 允许从Rails Admin轻松地将state_machine事件发送到模型,包括对ActiveRecord \ Mongoid和自定义状态字段名称的支持以及...
这个 gem 修复了 Rails 生成的ActionView::MissingTemplate错误,当页面被请求标题为Accept: */*;q=0.9 (或任何其他数字)时。 Google bot 会执行此类请求。 安装 将此行添加到应用程序的 Gemfile 中: gem '...
rails_autolink 描述: 这是从rails中提取的auto_link方法。 `auto_link`方法已从Rails 3.1版本的Rails中删除。 该宝石旨在弥合移民人群的鸿沟。 特点: 默认情况下,auto_link返回已清理的html_safe字符串。...
使用Ruby on Rails中的Kickbox.io API验证电子邮件,如果Kickbox.io API由于任何原因失败,则退回到基本语法电子邮件验证 安装 将此行添加到您的应用程序的Gemfile中: gem 'kickbox_rails' 然后执行: $ bundle ...
routes_lazy_routes routes_lazy_routes是一个邪恶的Rails插件,它将延迟加载整个流血的路由,直到服务器收到第一个请求为止,因此该应用程序可以快速启动。 :sign_of_the_horns: 这款巫毒宝石是专为维护大型遗留...
:dog_face: MiAudota :cat_face: 该应用程序显示可从用户最近的庇护所获取的动物,并允许将其应用到收养过程中,从而完成一份调查表,庇护所将评估可能采用者的概况。 宠物预览这个怎么运作? :thinking_face: 用户...
最早是因为在使用Google安卓架构组件的时候,存在一些涉及网络请求的demo,自己不上手写,总感觉不踏实。又不想重新去搞java ee,一回想,去年的rails还有点印象,要不这下自己来试试。买好书,一点一点来看。 书是...
它提供了一系列用于处理多个数据库的帮助程序,以及一些用于处理这些数据库的其他Rails任务。 它是从“话语”中提取的。 安装将此行添加到您的应用程序的Gemfile中: gem 'rails_multisite'然后执行: $ bundle或将...