Templates and Contexts differentiate between different sources of assigns

This commit is contained in:
James MacAulay
2009-08-06 18:33:41 -04:00
parent a4d7c80ce0
commit c792c29066
3 changed files with 73 additions and 17 deletions
+32 -12
View File
@@ -14,13 +14,15 @@ module Liquid
# context['bob'] #=> nil class Context # context['bob'] #=> nil class Context
class Context class Context
attr_reader :scopes attr_reader :scopes
attr_reader :errors, :registers attr_reader :errors, :registers, :environment
def initialize(assigns = {}, registers = {}, rethrow_errors = false) def initialize(environment = {}, instance_assigns = {}, registers = {}, rethrow_errors = false)
@scopes = [(assigns || {})] @environment = environment
@registers = registers @scopes = [(instance_assigns || {})]
@errors = [] @registers = registers
@errors = []
@rethrow_errors = rethrow_errors @rethrow_errors = rethrow_errors
squash_instance_assigns_with_environment
end end
def strainer def strainer
@@ -61,9 +63,9 @@ module Liquid
end end
# push new local scope on the stack. use <tt>Context#stack</tt> instead # 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 raise StackLevelError, "Nesting too deep" if @scopes.length > 100
@scopes.unshift({}) @scopes.unshift(new_scope)
end end
# merge a hash of variables in the current local scope # merge a hash of variables in the current local scope
@@ -86,9 +88,9 @@ module Liquid
# end # end
# context['var] #=> nil # context['var] #=> nil
# #
def stack(&block) def stack(new_scope={},&block)
result = nil result = nil
push push(new_scope)
begin begin
result = yield result = yield
ensure ensure
@@ -97,6 +99,10 @@ module Liquid
result result
end end
def clear_instance_assigns
@scopes[0] = {}
end
# Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt> # Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
def []=(key, value) def []=(key, value)
@scopes[0][key] = value @scopes[0][key] = value
@@ -156,9 +162,14 @@ module Liquid
# fetches an object starting at the local scope and then moving up # fetches an object starting at the local scope and then moving up
# the hierachy # the hierachy
def find_variable(key) def find_variable(key)
scope = @scopes[0..-2].find { |s| s.has_key?(key) } || @scopes.last scope = @scopes.find { |s| s.has_key?(key) } || environment
variable = scope[key]
variable = scope[key] = variable.call(self) if variable.is_a?(Proc) if scope[key].is_a?(Proc)
variable = scope[key] = scope[key].call(self)
else
variable = scope[key]
end
variable = variable.to_liquid variable = variable.to_liquid
variable.context = self if variable.respond_to?(:context=) variable.context = self if variable.respond_to?(:context=)
return variable return variable
@@ -217,5 +228,14 @@ module Liquid
object object
end 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
end end
+9 -4
View File
@@ -67,6 +67,10 @@ module Liquid
@assigns ||= {} @assigns ||= {}
end end
def instance_assigns
@instance_assigns ||= {}
end
def errors def errors
@errors ||= [] @errors ||= []
end end
@@ -89,11 +93,12 @@ module Liquid
when Liquid::Context when Liquid::Context
args.shift args.shift
when Hash when Hash
a = args.shift environment = args.shift
assigns.each { |k,v| a[k] = v unless a.has_key?(k) } environment.merge!(assigns) {|k,v1,v2| v1}
Context.new(a, registers, @rethrow_errors) Context.new(environment, instance_assigns, registers, @rethrow_errors)
when nil when nil
Context.new(assigns.dup, registers, @rethrow_errors) environment = assigns.dup
Context.new(environment, instance_assigns, registers, @rethrow_errors)
else else
raise ArgumentError, "Expect Hash or Liquid::Context as parameter" raise ArgumentError, "Expect Hash or Liquid::Context as parameter"
end end
+31
View File
@@ -23,4 +23,35 @@ class TemplateTest < Test::Unit::TestCase
assert_equal [' ', '{% comment %}', ' ', '{% endcomment %}', ' '], Template.new.send(:tokenize, " {% comment %} {% endcomment %} ") assert_equal [' ', '{% comment %}', ' ', '{% endcomment %}', ' '], Template.new.send(:tokenize, " {% comment %} {% endcomment %} ")
end 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 end