`
hozaka
  • 浏览: 38120 次
  • 性别: Icon_minigender_1
  • 来自: 上海
最近访客 更多访客>>
社区版块
存档分类
最新评论

ActionView 的魔术:ERB & Binding

阅读更多

Rails 作为一个 MVC 框架,其核心包括三个模块:ActiveRecord,ActionController 和 ActionView。今天这篇博文的主角是 ActionView,解开模板系统的魔术。

通常情況下,通过 scaffold 已经能够建立简单的、包含CRUD基本功能的页面,完全不需要手动修改 view 的代码。即使不使用 scaffold ,Rails 也提供了众多的辅助方法,创造一个功能丰富的动态页面简直是易如反掌。但是,会用不代表深入理解,最近有朋友问我这些问题:

  1. 为什么编辑一个对象需要在 Controller 创造一个实例变量
  2. View 通过什么方式访问这些实例变量的
  3. 那么多表单辅助方法,都是需要提供 object_name, method 两个参数,怎么就变成实例变量的值了

相信大部分的 Rails 程序员手边的书都是《Agile Web Development with Rails》,书中提到这一点的时候一笔带过,只是说 Rails 在这里用了一个小魔术。这里,我们就来揭开这个魔术吧!

 

Part 1. Template Files - 模板文件

当一个 action 需要返回一段 html 片段的时候,我们需要建立一个模板文件。根据不同的版本、请求类型,模板文件的文件名也各不相同,从早期版本的 action_name.rhtml ,到现在的 action_name.text.html.erb ,以及扩展的 rjs,在扩展名中都包含了一个关键字:r / erb。它就是 Rails 模板系统的关键:ERb。

 

Part 2. ERb - Ruby Templating

ERb - 嵌入式 Ruby (http://ruby-doc.org/stdlib/libdoc/erb/rdoc/index.html ),是 Ruby 语言提供的一个基本扩展。它支持在字符串中嵌入 ruby 代码片段。看上去似乎很神秘,其实我们每天都用到,下面这种形式一定不陌生吧?

<p>
  <%= Time.now %>
</p>

没错,正是因为 ERb 的存在,使得模板中可以动态地引用对象的属性。

 

Part 3. Instance Variables of Ruby - Ruby 的实例变量

让我们回顾一下 Ruby 语言的基本要素之一:实例变量。通常我们通过

@time = Time.now

的形式创造一个实例变量。这里我们不重新解释对于“实例变量”的定义,但是必须牢记一点,正如字面所见,实例变量的作用域是当前实例内,也就是说,只有在实例的内部,才可以直接对实例变量进行读写操作(扩展的访问子方法等等不在讨论范围之内)。那么,为什么 Rails Controller 里创建的实例变量能够在 View 里面访问呢,不是自相矛盾吗?

 

Ruby 作为一种动态语言,因为其“开放”的特点,使很多原本不可能的编程模式变为可能。比如,通过 Open Class 的特性,你可以动态的为对象注入新的方法定义,或者改写方法的逻辑,或者,通过不同的方法可以在对象的外部访问对象的内部的实例变量。经常使用 console 的朋友可能会了解其中的一种方式:Object#instance_variable_get / Object#instance_variable_set 方法。举个简单的例子:

class User
  def initialize( name )
    @name = name
  end
end

user = User.new( "Jack" )
user.name #=> raise NoMethodError
user.instance_variable_get('@name') #=> "Jack"
user.instance_variable_set('@name', 'Tom')
user.instance_variable_get('@name') #=> "Tom"

可以看到,在没有任何访问子的情况下,我们用这种方式对一个实例变量进行读写操作。

 

除了这种简单的方式,还有另外一种进阶的方式,也是 ERb 常用的一种方式:Binding

 

Part 4. Binding

Binding (http://ruby-doc.org/core/classes/Binding.html ) 是 Ruby 语言的自身的一个特性,在任何对象内,self.binding 方法都会返回一个当前对象关联的 binding 实例。不精确的说,binding 对象可以理解成为当前对象的完整的上下文环境。文档中已经包含了一些示例代码,帮助大家理解 Binding 对象的作用。最重要的一点是:既然是当前对象的完整上下文环境,自然就包括了对象的实例变量。

 

那么 Binding 在 ERb 中扮演一个什么样的角色?是一个运行环境的提供者。

 

回到我们最初的问题,Rails 在 ActionView 中使用了什么样的魔法?答案就是 ERb 和 Binding。首先,获得当前实例的 binding,自然,binding 内也包括了实例变量;紧接着,ERb 允许将模板的内容动态地,绑定到另一个环境中运行。我们依然用刚才的 User 的例子来说明这一点:

# A simple template string
template = "Hello, <%= @user.name %>"

@user = User.new( "Jack" )

# Get binding
binder = self.send( :binding ) # calling a private method

# Rendering template
puts ERB.new( template ).result( binder )
#=> "Helo, Jack"

虽然例子不是非常恰当,但是足以展示 Binding 和 ERB 的用法。我们可以看到,ERB#result 方法将模板字符串绑定在另外一个环境中运行,而这个环境包含了我们创建的 @user 实例变量,因此,模板中的 @user.name 得到了正确的值。

 

这就是 ERB 的真面目,也是为什么在 ActionView 中能够访问到 Controller 里实例变量的原因。感叹一下动态语言的强大吧!以上只是非常粗糙的讲述ERB的使用,Rails 所做的魔法远不止如此,如果有兴趣,可以查看 Rails 源代码,对于深入学习 Rails 框架也有很大的好处。

 

最后,补充一下 ERB 的应用场景。虽然在普通的需求中,ActionView 所做的已经足够,但是某些情况还是需要创造独立的模板系统。比如某个场景,客户要求提供一个完全自定义的模板系统,这个时候 ERB 就大显身手了。只需要将数据源载入实例变量中,并且在使用手册里列出可以访问的方法,即使完全不懂 Ruby 语言也可以写出使用这套简单的模板系统了。

分享到:
评论
8 楼 hozaka 2008-08-19  
基本的思路都差不多的,view 并不一定要保存为一个文件,也可能是一个模块的计算结果,活着是数据库一条记录的某个字段
7 楼 shaquan6776 2008-08-19  
rubynroll 写道
我在上一个项目中基于Rails定制了一个框架,其中采用的方案是View部分以module形式出现,mixin到controller,然后ERB就是在controller的binding里面了。

感觉这么做比较简单,目前似乎也没有碰到什么问题。

楼上的,你好。对你的方案很感兴趣,可否再说详细点.
6 楼 hozaka 2008-08-18  
我的项目是基于自定义模板的邮件系统,用ERB是我觉得最合适的一个方案了
5 楼 rubynroll 2008-08-18  
我在上一个项目中基于Rails定制了一个框架,其中采用的方案是View部分以module形式出现,mixin到controller,然后ERB就是在controller的binding里面了。

感觉这么做比较简单,目前似乎也没有碰到什么问题。
4 楼 liusong1111 2008-08-14  
ERB说到底是个text template.
我一直期望的是有个dom template/renderer,也知道不太现实.
终极目标是组件化,至少需要
1. 对组件做抽象. 像extjs,flex那样,搞复杂的继承体系,事件,属性.
2. markup language,还要能与组件类无缝映射,IDE支持.
3. 消除服务器端和客户端概念,像JSF,.NET一样,破坏REST规约.

探索过markaby,haml,觉得一般.
看了看hpricot,觉得在它上面做手脚有点意思:
http://www.beyondrails.com/blogs/47

3 楼 liusong1111 2008-08-14  
是的,ERB是个独立的部份. 看到你上面的介绍才知道obj.private_methods.include?(:binding) == true,对于ERB来说很有用. 我只是不理解rails为什么不像你的方案,直接在controller的binding里搞.
ERB是个很清爽的东西,有两个地方不习惯:
1. result只接收binding,不接收某个obj(它内部调binding就成了)或Hash(它打成local variable)
2. 分界符不强.没找到如何把<% %> 换成类似${}的配置,只有<%- -%>.

楼上有没有什么建议?

同意你对merb的评论,我前几天扫了几眼merb源码,感觉很好. 上面的话是对俺自己说的,对付rails的坏毛病,希望从它上面能取到经.

2 楼 hozaka 2008-08-14  
说实话,我也知道 Rails 从1.16开始就是直接用复制实例变量的方式来做的,这篇博文中提到的 ActionView 也只是作为一个引子,真正想讲的是 ERB。

当然,我也知道 Merb 甚至还有其他模板系统的存在,只是有些时候,用户的需求并不为技术所左右,单纯的考虑技术因素很难让自己的工作做的更好。大部分情况下,类似 ERB 这种简单实用的语法就是用户所青睐的。

最后还解答一下你的问题,即使 Rails 用的是我所说的方式,也不是让 ERB 在 controller 的上下文环境中运行。从层次结构上说,ERB 独立在 controller 和 view 之外。可能会类似这样的代码

class Render::Base
  def render( controller, view )
    ERB.new( view.template_body ).result( controller.send(:binding) )
  end
end


代码有点粗糙,凑合看吧
   
1 楼 liusong1111 2008-08-14  
引用
这就是 ERB 的真面目,也是为什么在 ActionView 中能够访问到 Controller 里实例变量的原因。

我期待的方式,就是楼主所描述的,可惜rails不是这样干的。

ActionController::Base和ActionView::Base是两个类,controller和view分别是这两个类的实例,所以action的代码和view/erb里的代码分别在这两个实例上下文中运行的,框架把controller的全部instance_variable复制给了view,而不是让ERB直接在controller上下文跑。
http://www.iteye.com/post/268223
刚查了下rails2.1,还保持着老的实现。 -- 这个简直不可理喻~
有空查查merb,让自己保持一颗年轻的心,呵呵~

相关推荐

    task-jst:将模板编译为 JST 文件

    默认情况下,使用 ERB 样式的模板分隔符,更改以下模板设置以使用替代分隔符。 { evaluate : /&lt;&#37;([\s\S]+?)%&gt;/g, interpolate : /&lt;&#37;=([\s\S]+?)%&gt;/g, escape : /&lt;&#37;-([\s\S]+?)%&gt;/g, ...

    erb-action:适用于Github Actions的ERB Linter

    适用于Github Action的ERB Linter Github操作,可在项目上运行erb-lint gem。 目录 用法 将以下内容添加到您的Githun操作工作流程中以使用ERB操作: name : Run ERB Linter on : push : branches-ignore : - ' ...

    ERB-EspWebServer:ERB的Esp8266实验Web服务器

    ERB-EspWebServer /* * This Program Provides the following for the Esp8266 as a WebServer: * But CAUTION, some functionallity may not have been throughly tested. * * The code and effort is always ...

    EmbeddedPowerShell:ERB到PowerShell的端口。 没有明显的原因

    嵌入式PowerShell ERB到PowerShell的端口。 没有明显的原因。 如在所述: EPS1是带有嵌入式PowerShell的纯文本该脚本以类似于嵌入式Ruby模板化(由Puppet使用)的方式处理“ eps1”文件。 所涉及的逻辑并不多-任何...

    cells-erb:对单元格的ERB支持

    单元格:: Erb ERB支持使用。 安装 将此行添加到您的应用程序的Gemfile中: gem 'cells-erb' 这将为.erb文件注册Erbse::Engine 。 这就是您需要做的。 厄布斯 是ERB的下一代实现,带有一些不错的新语义和显式...

    easy-recycler-binding:ERB是一个库,其中包含一些有用的Kotlin Android数据绑定适配器,用于带有乘以ViewHolders视图的RecyclerView

    易于回收的装订 ERB是一个库,其中包含一些有用的Kotlin Android数据绑定适配器,用于带有乘以ViewHolders视图的RecyclerView

    erb-lint:检查您的ERB文件中是否有结束标签,缩进,不良属性等

    ERB ::棉绒 可以很容易地在ERB文件中引入错误,未关闭的标签,错误的属性名称,错误的缩进。 ERB :: Lint可以通过将ERB标签变成HTML-ish标签并在结果上运行适当HTML linter来检查这些错误: 原始ERB &lt;&#37;...

    ers:ers - 类似 ERb 的 Rust 模板引擎

    ers - 类似 ERb 的 Rust 模板引擎 ers 是 Rust 的一种 ERb 风格的模板语言。 ers 模板将根据您的意愿转换为静态编译的 Rust 函数,允许您将它们链接到您的其他项目。 图书馆基础设施很大程度上受到了ego启发。 ...

    armadillo:使用 ERB 模板继承模板

    一个适用于 ERB 小型库。 用法 要渲染 Armadillo 模板,您需要调用Armadillo.render方法。 此方法接受以下任何选项: :scope - 要绑定到模板范围的任何对象。 :base_path - 要搜索模板的目录的路径。 注意:假定...

    herbie:与ERB一起使用的可爱HTML视图助手(可能有点bug)

    与ERB一起使用的可爱HTML视图帮助器。 一组轻量级的助手,非常适合诸如类的微框架。 在Sinatra应用程序中的安装示例 将gem "herbie" herbie gem "herbie"添加到您的Gemfile中 $ bundle install 在myapp.rb中添加...

    rubbish-erb-parser:一个非常糟糕的erb解析器

    这是一个 erb 解析器,它只支持 erb 的一个很小的子集。 我们在 Lost My Name 的组件开发中使用它,但这个库基本上没有太多用处。 这是一个子集,因为您编写的 Ruby 必须是有效的 JavaScript。 以下内容不会呈现:...

    erb_with_hash

    安装将此行添加到您的应用程序的Gemfile中: gem 'erb_with_hash'然后执行: $ bundle或将其自己安装为: $ gem install erb_with_hash用法require 'set'class ERB include ERBWithHashendmy_erb = ERB . new ( &lt;...

    prettier-plugin-erb:漂亮的ERB插件

    漂亮的ERB插件 如何安装? yarn add -D prettier @prettier/plugin-ruby prettier-plugin-erb @prettier/plugin-ruby用于格式化多行表达式 对于插件,Ruby可用的配置选项可 如何处理错误? 您的代码格式不正确或...

    simplest_view:SimplestView使我们能够从模板中拆分Rails视图

    这是通过用您自己的视图类替换从ActionView :: Base继承的匿名类来实现的。 该视图类成为现有Rails模板中的上下文。 安装 将此行添加到您的应用程序的Gemfile中: gem 'simplest_view' 然后执行: $ bundle 或将...

    herbalizer:转换HAML到ERB

    用Haskell编写的HAML到ERB转换器。 该程序将模板转换为 。 为什么 很多人更喜欢HAML而不是ERB。 但不是每个人,也不是每个情况都如此。 ERB更简单,更容易记住规则,并且对HTML熟悉的人都可以访问。 ERB模板更易于...

    msfupdate.erb

    metasploit 一件安装脚本 源自于 https://raw.githubusercontent.com/rapid7/metasploit-omnibus/master/config/templates/metasploit-framework-wrappers/msfupdate.erb

    gerb:受 erb 启发的 Go 模板引擎

    格布Gerb 是一个受 erb 启发的 Go 模板引擎。用法 template , err := gerb . ParseString ( true , "...." )if err != nil { panic ( err )}data := map [ string ] interface {}{ "name" : ... .}template . Render...

    safemode:一个基于ParseTreeRubyParser和Ruby2Ruby的用于安全评估Ruby代码的库。 提供ERB和Haml的Rails ActionView模板处理程序

    为ERB和Haml提供Rails ActionView模板处理程序。警告语该库仍处于高度试验阶段。 仅在实验和游戏之外使用任何东西,风险自负。 尽管如此,请不要打它,读取和运行单元测试并提供反馈,以帮助它防水,最终适用于严重...

    ERBAutocomplete:该软件包可帮助使用erb模板的用户轻松快捷地完成erb标签

    ERB自动完成 该软件包可帮助使用erb模板的用户轻松快捷地完成ERB标签。 如何安装 带: 运行“程序包控制:安装程序包”命令,找到并安装ERB Autocomplete程序包。 重新启动您的崇高文本编辑器。 手动: git repo...

    erb_sql_templates:Ruby Gem 使用 ERB 自定义大型 SQL 查询

    Ruby 的 ERB SQL 模板 当您不希望将大型复杂 SQL 查询作为字符串嵌入到 Ruby 中时,请使用erb_sql_templates 。 gem 允许将 SQL 存储在它们自己的目录和文件中,并且可以使用 ERB 自定义查询。 例子 示例 SQL 模板...

Global site tag (gtag.js) - Google Analytics