`
fantaxy025025
  • 浏览: 1329316 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类

Rails源码阅读(九)ActionView::Base_用户请求在rails中的处理流程(4)

 
阅读更多

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的精简版本Rails::API.zip

    Rails::API 移除了 ActionView 和其他一些渲染功能,不关心Web前端的开发者可更容易、快速地开发应用程序,因此运行速度比正常的 Rails 应用程序要快。 Rails::API 可以用来创建只提供API服务(API-Only)的 Rails ...

    rails-exporter-源码.rar

    在 Rails 应用中,模型负责数据操作,视图负责展示,控制器则作为两者之间的桥梁,处理用户请求并协调数据展示。 二、Exporter 概念 数据导出是 web 应用中常见的需求,例如生成 CSV、Excel 或 PDF 文件供用户下载...

    Rails项目源代码

    在这个图片分享项目中,模型可能包括`User`(用户)、`Image`(图片)等,视图则展示图片和用户界面,控制器处理用户的请求和响应。 3. **Sign标签**: "Sign"可能是项目的登录或注册模块,它涉及用户认证和授权。...

    weixin_rails_middleware, 微信集成 ruby weixin_rails_middleware for integration weixin..zip

    `weixin_rails_middleware` 是基于 Ruby 的 Rack 技术构建的,它能够插入到 Rails 应用的请求处理流程中。当收到微信服务器发来的请求时,中间件会自动处理这些请求,如验证签名、解析XML数据,并提供相应的响应。 ...

    bhl_rails_solr-源码.rar

    4. **错误处理**:在与Solr通信过程中,可能会遇到网络故障、索引错误等问题,源码中应有相应的异常处理机制,确保应用的健壮性。 此外,深入研究源码,我们可以学习到以下几点: 1. **Solr与Rails的交互机制**:...

    Rails上的API:使用Rails构建REST APIAPIs on Rails: Building REST APIs with Rails

    在本篇内容中,我们将深入探讨如何利用Ruby on Rails(简称Rails)这一强大的Web应用程序框架来构建可伸缩且易于维护的RESTful API。Rails以其简洁优雅的语法、高效的开发速度以及良好的社区支持而闻名,这使得它...

    ruby on rails 教程源码

    Ruby on Rails,简称Rails,是基于Ruby语言的开源Web应用框架,它遵循MVC(Model-View-Controller)架构模式,旨在使开发过程更加简洁高效。这个“ruby on rails 教程源码”很可能是为了辅助学习者深入理解Rails的...

    Rails进行敏捷Web开发(所有版本的源码rails3.0-4.0)

    在“Rails进行敏捷Web开发(所有版本的源码rails3.0-4.0)”中,包含了Rails从3.0到4.0各个主要版本的源代码,这些版本的变迁反映了Rails框架在不同阶段的发展和改进。 1. Rails 3.0: Rails 3是重大升级,引入了...

    Rails中应用Ext.tree:以中国的省市地区三级联动选择为例

    在Ruby on Rails(Rails)框架中,开发人员经常需要实现各种用户交互功能,例如三级联动选择,这在处理如中国省市区这样的地理数据时尤其常见。这篇博客文章“Rails中应用Ext.tree:以中国的省市地区三级联动选择为...

    base_css-rails:宝石将基本CSS框架纳入资产管道

    基本CSS框架base_css-rails是一个将添加到应用程序的资产管道的瑰宝安装将此行添加到您的应用程序的Gemfile中: gem 'base_css-rails'然后执行: $ bundle或将其自己安装为: $ gem install base_css-rails用法使用...

    rails_admin_enum4:Rails Admin Rails 4.1枚举

    Rails Admin枚举4 Rails Admin插件,用于正确查看Rails 4.1枚举 安装 gem 'rails_admin_enum4', github: 'sibext/rails_admin_enum4' bundle install 用法 通过生成器生成新模型: rails g model Sibext service:...

    Ruby on Rails的启动时间助推器,可阻止加载整个流血路线,因此应用程序可以快速启动:sign_of_the_horns:-Ruby开发

    延迟加载整个血腥路由,因此应用程序可以快速启动:sign_of_the_horns:route_lazy_routes route_lazy_routes是一个邪恶的Rails插件,它延迟加载整个血腥路由,直到服务器获得第一个请求,因此应用程序可以旋转快起来...

    Ruby on Rails Guides v2 - Ruby on Rails 4.2.5

    - **配置**:在`config/routes.rb`文件中添加新的路由规则,例如`get 'new_route' =&gt; 'controller#action'`。 - **效果**:这将在应用中增加一个新的URL路径,指向指定控制器的动作。 #### 七、渲染视图 - **方法*...

    Rails 4 in Action, Second Edition.pdf

    - **Active Record Query Interface**:Rails 4在Active Record中引入了一个新的查询接口,使数据库操作更加直观和高效。 - **Secure Cookies**:为了提高安全性,Rails 4引入了一种新的安全cookie机制,可以更好地...

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

    在您的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

    component base rails applications

    本书《Component-Based Rails Applications》主要介绍了如何使用Rails引擎(Rails Engine)进行基于组件的Rails应用开发,以及如何对应用程序的大型模块进行拆分和模块化。以下是书中一些核心知识点的详细说明: 1....

    Rails 3 in Action

    《Rails 3 in Action》是2011年由Ryan Bigg撰写的一本关于Ruby on Rails框架的权威指南,专门针对当时最新的Rails 3.1版本进行了深入解析。这本书旨在帮助开发者充分利用Rails 3.1的强大功能,提升Web应用开发的效率...

    shopping_card_rails-源码.rar

    2. **控制器(Controllers)**:控制器处理用户请求,调用模型方法,并向视图发送数据。购物车相关的控制器可能包括`ProductsController`(用于展示商品)、`CartsController`(处理购物车操作)和`...

    todds_blog:Basic Rails博客:man_technologist::railway_track::writing_hand:

    持续移动且不破坏事物 ‍:factory: :building_construction: :construction: :construction_worker: 正在建设中-请稍后再回来!

Global site tag (gtag.js) - Google Analytics