From c792c29066e246da4ba342c038d2562c9d7e66db Mon Sep 17 00:00:00 2001 From: James MacAulay Date: Thu, 6 Aug 2009 17:49:44 -0400 Subject: [PATCH] Templates and Contexts differentiate between different sources of assigns --- lib/liquid/context.rb | 44 ++++++++++++++++++++++++++++++------------ lib/liquid/template.rb | 15 +++++++++----- test/template_test.rb | 31 +++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 17 deletions(-) diff --git a/lib/liquid/context.rb b/lib/liquid/context.rb index 2d739d2..4c9b9b4 100644 --- a/lib/liquid/context.rb +++ b/lib/liquid/context.rb @@ -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 Context#stack 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 Liquid::Drop 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 diff --git a/lib/liquid/template.rb b/lib/liquid/template.rb index fae8ae3..a9ab882 100644 --- a/lib/liquid/template.rb +++ b/lib/liquid/template.rb @@ -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 diff --git a/test/template_test.rb b/test/template_test.rb index d96d274..6f49a07 100644 --- a/test/template_test.rb +++ b/test/template_test.rb @@ -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 \ No newline at end of file