diff --git a/lib/liquid/context.rb b/lib/liquid/context.rb index 4c9b9b42..8b5cfd41 100644 --- a/lib/liquid/context.rb +++ b/lib/liquid/context.rb @@ -13,16 +13,15 @@ module Liquid # # context['bob'] #=> nil class Context class Context - attr_reader :scopes - attr_reader :errors, :registers, :environment + attr_reader :scopes, :errors, :registers, :environments - def initialize(environment = {}, instance_assigns = {}, registers = {}, rethrow_errors = false) - @environment = environment - @scopes = [(instance_assigns || {})] + def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false) + @environments = [environments].flatten + @scopes = [(outer_scope || {})] @registers = registers @errors = [] @rethrow_errors = rethrow_errors - squash_instance_assigns_with_environment + squash_instance_assigns_with_environments end def strainer @@ -162,13 +161,17 @@ module Liquid # fetches an object starting at the local scope and then moving up # the hierachy def find_variable(key) - 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] + scope = @scopes.find { |s| s.has_key?(key) } + if scope.nil? + @environments.each do |e| + if variable = lookup_and_evaluate(e, key) + scope = e + break + end + end end + scope ||= @environments.last || @scopes.last + variable ||= lookup_and_evaluate(scope, key) variable = variable.to_liquid variable.context = self if variable.respond_to?(:context=) @@ -204,8 +207,7 @@ module Liquid (object.respond_to?(:fetch) and part.is_a?(Integer))) # if its a proc we will replace the entry with the proc - res = object[part] - res = object[part] = res.call(self) if res.is_a?(Proc) and object.respond_to?(:[]=) + res = lookup_and_evaluate(object, part) object = res.to_liquid # Some special cases. If the part wasn't in square brackets and @@ -229,10 +231,21 @@ 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] + def lookup_and_evaluate(obj, key) + if (value = obj[key]).is_a?(Proc) && obj.respond_to?(:[]=) + obj[key] = value.call(self) + else + value + end + end + + def squash_instance_assigns_with_environments + @scopes.last.each_key do |k| + @environments.each do |env| + if env.has_key?(k) + scopes.last[k] = lookup_and_evaluate(env, k) + break + end end end end diff --git a/lib/liquid/template.rb b/lib/liquid/template.rb index a9ab8821..edfb980e 100644 --- a/lib/liquid/template.rb +++ b/lib/liquid/template.rb @@ -93,12 +93,9 @@ module Liquid when Liquid::Context args.shift when Hash - environment = args.shift - environment.merge!(assigns) {|k,v1,v2| v1} - Context.new(environment, instance_assigns, registers, @rethrow_errors) + Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors) when nil - environment = assigns.dup - Context.new(environment, instance_assigns, registers, @rethrow_errors) + Context.new(assigns, instance_assigns, registers, @rethrow_errors) else raise ArgumentError, "Expect Hash or Liquid::Context as parameter" end diff --git a/test/context_test.rb b/test/context_test.rb index a57432b8..87b88406 100644 --- a/test/context_test.rb +++ b/test/context_test.rb @@ -67,8 +67,7 @@ class ContextTest < Test::Unit::TestCase include Liquid def setup - @template = Liquid::Template.new - @context = Liquid::Context.new(@template.assigns, @template.registers) + @context = Liquid::Context.new end def test_variables @@ -151,11 +150,11 @@ class ContextTest < Test::Unit::TestCase end end - context = Context.new(@template) + context = Context.new context.add_filters(filter) assert_equal 'hi? hi!', context.invoke(:hi, 'hi?') - context = Context.new(@template) + context = Context.new assert_raises(FilterNotFound) { context.invoke(:hi, 'hi?') } @@ -191,7 +190,7 @@ class ContextTest < Test::Unit::TestCase end end - context = Context.new(@template) + context = Context.new methods_before = context.strainer.methods.map { |method| method.to_s } context.add_filters(filter) methods_after = context.strainer.methods.map { |method| method.to_s } diff --git a/test/template_test.rb b/test/template_test.rb index 6f49a07f..94f8ca86 100644 --- a/test/template_test.rb +++ b/test/template_test.rb @@ -53,5 +53,23 @@ class TemplateTest < Test::Unit::TestCase t.assigns['foo'] = 'from persistent assigns' assert_equal 'from persistent assigns', t.parse("{{ foo }}").render end + + def test_lambda_is_called_once_from_persistent_assigns_over_multiple_parses_and_renders + t = Template.new + t.assigns['number'] = lambda { @global ||= 0; @global += 1 } + assert_equal '1', t.parse("{{number}}").render + assert_equal '1', t.parse("{{number}}").render + assert_equal '1', t.render + @global = nil + end + + def test_lambda_is_called_once_from_custom_assigns_over_multiple_parses_and_renders + t = Template.new + assigns = {'number' => lambda { @global ||= 0; @global += 1 }} + assert_equal '1', t.parse("{{number}}").render(assigns) + assert_equal '1', t.parse("{{number}}").render(assigns) + assert_equal '1', t.render(assigns) + @global = nil + end end \ No newline at end of file