[sinatra] 开一贴,专门讨论tilt

花花公子 2010-03-28
sinatra 1.0出来了,这里面最让我激动的是tilt,因为我前两天刚刚用过erb生成文本。才发现模板的渲染取值也是一门学问。

比如下面这段简单的erb代码
irb(main):001:0> require 'erb'
=> true
irb(main):002:0> e = ERB.new(<%= x %>)
irb(main):003:1" ^C
irb(main):003:0> e = ERB.new('<%= x %>')
=> #<ERB:0xb7861ef8 @safe_level=nil, @src="_erbout = ''; _erbout.concat(( x ).to_s); _erbout", @filename=nil>
irb(main):004:0> e.result
NameError: undefined local variable or method `x' for main:Object
	from (erb):1
irb(main):005:0> x = 1
=> 1
irb(main):006:0> e.result
NameError: undefined local variable or method `x' for main:Object
	from (erb):1

需要改成如下代码才可以成功
irb(main):009:0> e.result(binding)
=> "1"

erb的设计存在缺陷,result只能接受binding或者Proc作为参数,需要多加hack才能用的舒服。Tilt里面的操作我就不太理解。

haml和erb相比就好了一些,因为Haml::Engine的render方法接受第二个参数传入locals。而且Haml也直接提供了precompiled方法。所以如果你需要自己生成html就用haml,写起来方便。
花花公子 2010-03-28
下面该轮到Ryan Tomayko的tilt登场了。下面这段代码我在回ns的时候也贴过
require 'tilt'

template = Tilt::ERBTemplate.new('foo.erb')

# Slow. Uses Object#instance_eval to process template
class Scope
end
scope = Scope.new
template.render(scope)

# Fast. Uses compiled template and Object#send to process template
class Scope
  include Tilt::CompileSite
end
scope = Scope.new
template.render(scope)

# Also fast, though a bit a slower due to having to extend each time
scope = Object.new
scope.extend Tilt::CompileSite
template.render(scope)

花花公子 2010-03-28
这个CompileSite甚为神奇,因为它的代码只有两行
  module CompileSite
    def __tilt__
    end 
  end 

看后,我甚为震惊。“什么!这是在做什么?“
在继续介绍之前,我先回顾一下ruby的module
irb(main):001:0> class A;include B;end
NameError: uninitialized constant A::B
	from (irb):1
	from :0
irb(main):002:0> module B;def a;puts "a";end;end
=> nil
irb(main):003:0> class A;include B;end
=> A
irb(main):004:0> a = A.new
=> #<A:0xb7873e8c>
irb(main):005:0> a.a
a
=> nil
irb(main):006:0> a.b
NoMethodError: undefined method `b' for #<A:0xb7873e8c>
	from (irb):6
	from :0
irb(main):007:0> module B;def b;puts "b";end;end
=> nil
irb(main):008:0> a.b
b
=> nil
花花公子 2010-03-28
基于ruby的方法链,include以后只是增加了查找方法链的环节,并不是在新建对象的时候复制方法。所以你也想到了,CompileSite会持续的加入新方法。这里面的代码如下:
    def compile_template_method(method_name, locals)
      source, offset = precompiled(locals)
      offset += 1
      CompileSite.module_eval <<-RUBY, eval_file, line - offset
        def #{method_name}(locals)
          #{source}
        end
      RUBY

      ObjectSpace.define_finalizer self,
        Template.compiled_template_method_remover(CompileSite, method_name)
    end 

tilt原本还支持CoffeeScript这个Javascript template,但是人家已经用node.js重写了,估计下一版就可以放弃支持了。
night_stalker 2010-03-28
这段代码
require 'erb'
e = ERB.new '<%= x %>'
x = 1
e.result


的问题是:局部变量的可见域是从声明开始,而不是语句块内,e 声明的时候,x 还没出现呢。

但实例变量就不一样,实例变量是当前 object 内,可见域比局部变量要广:
require 'erb'
e = ERB.new '<%= @x %>'
@x = 1
e.result


所以用 object scope 的 binding 比 local scope 的 binding 更接近人的习惯 ……
花花公子 2010-03-28
多谢提醒,原来在生成ERB的时候binding就传入了的。一直以为是result的时候再去取的。
以后尽量用object scope,不用local scope,
比如
get '/items' do
  @items.map{ |item| haml :item, :locals => { :item => item } }
end

就可以写成
get '/items' do
  @items.map{ |item| @item = item; haml :item }
end

mccxj 2010-04-22
CompileSite module的确很神亚,一开始也看傻瓜了,调用的过程原来是
render -> evaluate -> compile_template_method,回去再详细看看是怎么处理的
Global site tag (gtag.js) - Google Analytics