From 52941e623217311fa2a683a70669acb3ed0433ee Mon Sep 17 00:00:00 2001 From: ccocchi Date: Tue, 10 Apr 2012 01:03:07 +0200 Subject: [PATCH] Extract rendering from template into renderers --- lib/rabl-fast-json.rb | 9 ++- lib/rabl-fast-json/handler.rb | 2 +- lib/rabl-fast-json/library.rb | 10 +-- lib/rabl-fast-json/renderer.rb | 2 + lib/rabl-fast-json/renderers/base.rb | 108 +++++++++++++++++++++++++++ lib/rabl-fast-json/renderers/json.rb | 9 +++ lib/rabl-fast-json/template.rb | 77 +------------------ test/cache_templates_test.rb | 26 +++---- test/compiled_template_test.rb | 4 + 9 files changed, 150 insertions(+), 97 deletions(-) create mode 100644 lib/rabl-fast-json/renderer.rb create mode 100644 lib/rabl-fast-json/renderers/base.rb create mode 100644 lib/rabl-fast-json/renderers/json.rb diff --git a/lib/rabl-fast-json.rb b/lib/rabl-fast-json.rb index f3c43fd..0f29c9c 100644 --- a/lib/rabl-fast-json.rb +++ b/lib/rabl-fast-json.rb @@ -8,20 +8,25 @@ require 'rabl-fast-json/version' require 'rabl-fast-json/helpers' require 'rabl-fast-json/template' require 'rabl-fast-json/compiler' + +require 'rabl-fast-json/renderer' + require 'rabl-fast-json/library' require 'rabl-fast-json/handler' require 'rabl-fast-json/railtie' + + module RablFastJson extend self - + mattr_accessor :cache_templates @@cache_templates = true def configure yield self end - + def cache_templates? ActionController::Base.perform_caching && @@cache_templates end diff --git a/lib/rabl-fast-json/handler.rb b/lib/rabl-fast-json/handler.rb index b95f5e2..c86635c 100644 --- a/lib/rabl-fast-json/handler.rb +++ b/lib/rabl-fast-json/handler.rb @@ -4,7 +4,7 @@ module RablFastJson cattr_accessor :default_format self.default_format = 'application/json' - def self.call(template) + def self.call(template) %{ RablFastJson::Library.instance. get_rendered_template(#{template.source.inspect}, self) diff --git a/lib/rabl-fast-json/library.rb b/lib/rabl-fast-json/library.rb index 32ccaad..10565e0 100644 --- a/lib/rabl-fast-json/library.rb +++ b/lib/rabl-fast-json/library.rb @@ -12,12 +12,12 @@ module RablFastJson def get_rendered_template(source, context) path = context.instance_variable_get(:@virtual_path) - @view_renderer = context.instance_variable_get(:@view_renderer) + @lookup_context = context.lookup_context compiled_template = get_compiled_template(path, source) - compiled_template.context = context - body = compiled_template.render - ActiveSupport::JSON.encode(compiled_template.root_name ? { compiled_template.root_name => body } : body) + + format = context.params[:format] || 'json' + Renderers.const_get(format.upcase!).new(context).render(compiled_template) end def get_compiled_template(path, source) @@ -32,7 +32,7 @@ module RablFastJson def get(path) template = @cached_templates[path] return template unless template.nil? - t = @view_renderer.lookup_context.find_template(path, [], false) + t = @lookup_context.find_template(path, [], false) get_compiled_template(path, t.source) end end diff --git a/lib/rabl-fast-json/renderer.rb b/lib/rabl-fast-json/renderer.rb new file mode 100644 index 0000000..3065875 --- /dev/null +++ b/lib/rabl-fast-json/renderer.rb @@ -0,0 +1,2 @@ +require 'rabl-fast-json/renderers/base' +require 'rabl-fast-json/renderers/json' \ No newline at end of file diff --git a/lib/rabl-fast-json/renderers/base.rb b/lib/rabl-fast-json/renderers/base.rb new file mode 100644 index 0000000..5709580 --- /dev/null +++ b/lib/rabl-fast-json/renderers/base.rb @@ -0,0 +1,108 @@ +module RablFastJson + module Renderers + class Base + + def initialize(context) # :nodoc: + @_context = context + setup_render_context + end + + # + # Render a template. + # Uses the compiled template source to get a hash with the actual + # data and then format the result according to the `format_result` + # method defined by the renderer. + # + def render(template) + collection_or_resource = @_context.instance_variable_get(template.data) if template.data + output_hash = collection_or_resource.respond_to?(:each) ? render_collection(collection_or_resource, template.source) : + render_resource(collection_or_resource, template.source) + output_hash = { template.root_name => output_hash } if template.root_name + format_output(output_hash) + end + + # + # Format a hash into the desired output. + # Renderer subclasses must implement this method + # + def format_output(hash) + raise "Muse be implemented by renderer" + end + + protected + + # + # Render a single resource as a hash, according to the compiled + # template source passed. + # + def render_resource(data, source) + source.inject({}) { |output, current| + key, value = current + + out = case value + when Symbol + data.send(value) # attributes + when Proc + instance_exec data, &value # node + when Array # node with condition + next output if !instance_exec data, &(value.first) + instance_exec data, &(value.last) + when Hash + current_value = value.dup + data_symbol = current_value.delete(:_data) + object = data_symbol.nil? ? data : data_symbol.to_s.start_with?('@') ? @_context.instance_variable_get(data_symbol) : data.send(data_symbol) + + if key.to_s.start_with?('_') # glue + current_value.each_pair { |k, v| + output[k] = object.send(v) + } + next output + else # child + object.respond_to?(:each) ? render_collection(object, current_value) : render_resource(object, current_value) + end + end + output[key] = out + output + } + end + + # + # Call the render_resource mtehod on each object of the collection + # and return an array of the returned values. + # + def render_collection(collection, source) + collection.map { |o| render_resource(o, source) } + end + + def partial(template_path, options = {}) + raise "No object was given to partial" unless options[:object] + object = options[:object] + + return [] if object.respond_to?(:empty?) && object.empty? + + template = Library.instance.get(template_path) + object.respond_to?(:each) ? render_collection(object, template.source) : render_resource(object, template.source) + end + + # + # If a method is called inside a 'node' property or a 'if' lambda + # it will be passed to context if it exists or treated as a standard + # missing method. + # + def method_missing(name, *args, &block) + @context.respond_to?(name) ? @context.send(name, *args, &block) : super + end + + # + # Copy assigns from controller's context into this + # renderer context to include instances variables when + # evaluating 'node' properties. + # + def setup_render_context + @_context.instance_variable_get(:@_assigns).each_pair { |k, v| + instance_variable_set("@#{k}", v) unless k.start_with?('_') || k == @data + } + end + end + end +end \ No newline at end of file diff --git a/lib/rabl-fast-json/renderers/json.rb b/lib/rabl-fast-json/renderers/json.rb new file mode 100644 index 0000000..ed36cef --- /dev/null +++ b/lib/rabl-fast-json/renderers/json.rb @@ -0,0 +1,9 @@ +module RablFastJson + module Renderers + class JSON < Base + def format_output(hash) + ActiveSupport::JSON.encode(hash) + end + end + end +end \ No newline at end of file diff --git a/lib/rabl-fast-json/template.rb b/lib/rabl-fast-json/template.rb index 0a755ca..8445de1 100644 --- a/lib/rabl-fast-json/template.rb +++ b/lib/rabl-fast-json/template.rb @@ -1,86 +1,11 @@ module RablFastJson class CompiledTemplate - attr_accessor :source, :data, :root_name, :context + attr_accessor :source, :data, :root_name delegate :[], :[]=, :merge!, :to => :source def initialize @source = {} end - - def has_root_name? - !@root_name.nil? - end - - def render - get_object_from_context - get_assigns_from_context - @object.respond_to?(:each) ? render_collection : render_resource - end - - def render_resource(data = nil, source = nil) - data ||= @object - source ||= @source - - source.inject({}) { |output, current| - key, value = current - - out = case value - when Symbol - data.send(value) # attributes - when Proc - instance_exec data, &value # node - when Array # node with condition - next output if !instance_exec data, &(value.first) #value.first.call(data) - instance_exec data, &(value.last) - when Hash - current_value = value.dup - data_symbol = current_value.delete(:_data) - object = data_symbol.nil? ? data : data_symbol.to_s.start_with?('@') ? @context.instance_variable_get(data_symbol) : data.send(data_symbol) - - if key.to_s.start_with?('_') # glue - current_value.each_pair { |k, v| - output[k] = object.send(v) - } - next output - else # child - object.respond_to?(:each) ? render_collection(object, current_value) : render_resource(object, current_value) - end - end - output[key] = out - output - } - end - - def render_collection(collection = nil, source = nil) - collection ||= @object - collection.inject([]) { |output, o| output << render_resource(o, source) } - end - - def method_missing(name, *args, &block) - @context.respond_to?(name) ? @context.send(name, *args, &block) : super - end - - def partial(template_path, options = {}) - raise "No object was given to partial" if options[:object].nil? - object = options[:object] - - return [] if object.respond_to?(:empty?) && object.empty? - - template = Library.instance.get(template_path) - object.respond_to?(:each) ? template.render_collection(object) : template.render_resource(object) - end - - protected - - def get_object_from_context - @object = @context.instance_variable_get(@data) if @data - end - - def get_assigns_from_context - @context.instance_variable_get(:@_assigns).each_pair { |k, v| - instance_variable_set("@#{k}", v) unless k.start_with?('_') || k == @data - } - end end end \ No newline at end of file diff --git a/test/cache_templates_test.rb b/test/cache_templates_test.rb index 5de4e56..288c668 100644 --- a/test/cache_templates_test.rb +++ b/test/cache_templates_test.rb @@ -4,31 +4,31 @@ class CacheTemplatesTest < ActiveSupport::TestCase setup do RablFastJson::Library.reset_instance - @library = RablFastJson::Library.instance - end - - test "cache templates if perform_caching is active and cache_templates is enabled" do + @library = RablFastJson::Library.instance RablFastJson.cache_templates = true - ActionController::Base.stub(:perform_caching).and_return(true) + end + + test "cache templates if perform_caching is active and cache_templates is enabled" do + ActionController::Base.stub(:perform_caching).and_return(true) @library.get_compiled_template('some/path', "") t = @library.get_compiled_template('some/path', "attribute :id") - + assert_equal({}, t.source) end - + test "cached templates should not be modifiable in place" do - RablFastJson.cache_templates = true ActionController::Base.stub(:perform_caching).and_return(true) t = @library.get_compiled_template('some/path', "") assert_nil t.context t.context = "foobar" assert_nil @library.get_compiled_template('some/path', "").context end - + test "don't cache templates cache_templates is enabled but perform_caching is not active" do - RablFastJson.cache_templates = true - ActionController::Base.stub(:perform_caching).and_return(false) - - refute_equal @library.get_compiled_template('some/path', ""), @library.get_compiled_template('some/path', "") + ActionController::Base.stub(:perform_caching).and_return(false) + @library.get_compiled_template('some/path', "") + t = @library.get_compiled_template('some/path', "attribute :id") + + assert_equal({ :id => :id }, t.source) end end \ No newline at end of file diff --git a/test/compiled_template_test.rb b/test/compiled_template_test.rb index 7971bbe..3ad7ec1 100644 --- a/test/compiled_template_test.rb +++ b/test/compiled_template_test.rb @@ -13,6 +13,10 @@ class TestCompiledTemplate < ActiveSupport::TestCase @template.data = :@data end + def render_json_output + RablFastJson::Renderers::JSON.new(@context).render(@template) + end + test "render object wth empty template" do @template.source = {} assert_equal({}, @template.render)