Templates and Contexts differentiate between different sources of assigns

This commit is contained in:
James MacAulay
2009-08-06 17:49:44 -04:00
parent a4d7c80ce0
commit c792c29066
3 changed files with 73 additions and 17 deletions

View File

@@ -14,13 +14,15 @@ module Liquid
# context['bob'] #=> nil class Context
class Context
attr_reader :scopes
attr_reader :errors, :registers
attr_reader :errors, :registers, :environment
def initialize(assigns = {}, registers = {}, rethrow_errors = false)
@scopes = [(assigns || {})]
@registers = registers
@errors = []
def initialize(environment = {}, instance_assigns = {}, registers = {}, rethrow_errors = false)
@environment = environment
@scopes = [(instance_assigns || {})]
@registers = registers
@errors = []
@rethrow_errors = rethrow_errors
squash_instance_assigns_with_environment
end
def strainer
@@ -61,9 +63,9 @@ module Liquid
end
# push new local scope on the stack. use <tt>Context#stack</tt> instead
def push
def push(new_scope={})
raise StackLevelError, "Nesting too deep" if @scopes.length > 100
@scopes.unshift({})
@scopes.unshift(new_scope)
end
# merge a hash of variables in the current local scope
@@ -86,9 +88,9 @@ module Liquid
# end
# context['var] #=> nil
#
def stack(&block)
def stack(new_scope={},&block)
result = nil
push
push(new_scope)
begin
result = yield
ensure
@@ -96,6 +98,10 @@ module Liquid
end
result
end
def clear_instance_assigns
@scopes[0] = {}
end
# Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
def []=(key, value)
@@ -156,9 +162,14 @@ module Liquid
# fetches an object starting at the local scope and then moving up
# the hierachy
def find_variable(key)
scope = @scopes[0..-2].find { |s| s.has_key?(key) } || @scopes.last
variable = scope[key]
variable = scope[key] = variable.call(self) if variable.is_a?(Proc)
scope = @scopes.find { |s| s.has_key?(key) } || environment
if scope[key].is_a?(Proc)
variable = scope[key] = scope[key].call(self)
else
variable = scope[key]
end
variable = variable.to_liquid
variable.context = self if variable.respond_to?(:context=)
return variable
@@ -217,5 +228,14 @@ module Liquid
object
end
def squash_instance_assigns_with_environment
scopes[0].each_key do |k|
if environment.has_key?(k)
scopes[0][k] = environment[k]
end
end
end
end
end

View File

@@ -67,6 +67,10 @@ module Liquid
@assigns ||= {}
end
def instance_assigns
@instance_assigns ||= {}
end
def errors
@errors ||= []
end
@@ -84,16 +88,17 @@ module Liquid
#
def render(*args)
return '' if @root.nil?
context = case args.first
when Liquid::Context
args.shift
when Hash
a = args.shift
assigns.each { |k,v| a[k] = v unless a.has_key?(k) }
Context.new(a, registers, @rethrow_errors)
environment = args.shift
environment.merge!(assigns) {|k,v1,v2| v1}
Context.new(environment, instance_assigns, registers, @rethrow_errors)
when nil
Context.new(assigns.dup, registers, @rethrow_errors)
environment = assigns.dup
Context.new(environment, instance_assigns, registers, @rethrow_errors)
else
raise ArgumentError, "Expect Hash or Liquid::Context as parameter"
end

View File

@@ -23,4 +23,35 @@ class TemplateTest < Test::Unit::TestCase
assert_equal [' ', '{% comment %}', ' ', '{% endcomment %}', ' '], Template.new.send(:tokenize, " {% comment %} {% endcomment %} ")
end
def test_instance_assigns_persist_on_same_template_object_between_parses
t = Template.new
assert_equal 'from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render
assert_equal 'from instance assigns', t.parse("{{ foo }}").render
end
def test_instance_assigns_persist_on_same_template_parsing_between_renders
t = Template.new.parse("{{ foo }}{% assign foo = 'foo' %}{{ foo }}")
assert_equal 'foo', t.render
assert_equal 'foofoo', t.render
end
def test_custom_assigns_do_not_persist_on_same_template
t = Template.new
assert_equal 'from custom assigns', t.parse("{{ foo }}").render('foo' => 'from custom assigns')
assert_equal '', t.parse("{{ foo }}").render
end
def test_custom_assigns_squash_instance_assigns
t = Template.new
assert_equal 'from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render
assert_equal 'from custom assigns', t.parse("{{ foo }}").render('foo' => 'from custom assigns')
end
def test_persistent_assigns_squash_instance_assigns
t = Template.new
assert_equal 'from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render
t.assigns['foo'] = 'from persistent assigns'
assert_equal 'from persistent assigns', t.parse("{{ foo }}").render
end
end