diff --git a/Gemfile b/Gemfile index 5e53c34..03b33ca 100644 --- a/Gemfile +++ b/Gemfile @@ -7,6 +7,7 @@ gemspec # jquery-rails is used by the dummy application gem "jquery-rails" +gem 'rspec-mocks' # Declare any dependencies that are still in development here instead of in # your gemspec. These might include edge Rails or gems from your path or diff --git a/Gemfile.lock b/Gemfile.lock index 5c957db..824fc09 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -39,7 +39,7 @@ GEM erubis (2.7.0) hike (1.2.1) i18n (0.6.0) - journey (1.0.2) + journey (1.0.3) jquery-rails (2.0.0) railties (>= 3.2.0.beta, < 5.0) thor (~> 0.14) @@ -76,6 +76,7 @@ GEM rake (0.9.2.2) rdoc (3.12) json (~> 1.4) + rspec-mocks (2.8.0) sprockets (2.1.2) hike (~> 1.2) rack (~> 1.0) @@ -94,4 +95,5 @@ PLATFORMS DEPENDENCIES jquery-rails rabl-fast-json! + rspec-mocks sqlite3 diff --git a/lib/rabl-fast-json.rb b/lib/rabl-fast-json.rb index 08b598b..5214e6e 100644 --- a/lib/rabl-fast-json.rb +++ b/lib/rabl-fast-json.rb @@ -7,6 +7,7 @@ require 'active_support/core_ext/class/attribute_accessors' require 'rabl-fast-json/version' require 'rabl-fast-json/template' require 'rabl-fast-json/compiler' +require 'rabl-fast-json/library' require 'rabl-fast-json/handler' require 'rabl-fast-json/railtie' diff --git a/lib/rabl-fast-json/compiler.rb b/lib/rabl-fast-json/compiler.rb index aa50983..9d3c17c 100644 --- a/lib/rabl-fast-json/compiler.rb +++ b/lib/rabl-fast-json/compiler.rb @@ -1,13 +1,9 @@ -require 'singleton' - module RablFastJson class Compiler - def initialize(context, assigns = nil) + def initialize(context = nil, assigns = nil) @context = context @glue_count = 0 - _get_assigns_from_context(assigns) - _get_virtual_path_from_context end def compile_source(source) @@ -41,6 +37,7 @@ module RablFastJson return unless block_given? name = :"_glue#{@glue_count}" @glue_count += 1 + Rails.logger.warn "[RABL] glue called with data = #{data.inspect}" _compile_sub_template(name, data, &block) end @@ -50,7 +47,12 @@ module RablFastJson alias_method :code, :node def collection(data, options = {}) - @data = data.to_a if data + object(data) + end + + def extends(path) + t = Library.instance.get(path) + @template.merge!(t.source) end def object(data) @@ -58,6 +60,10 @@ module RablFastJson @template.data, @template.root_name = data, name end + def method_missing(name, *args, &block) + @context.respond_to?(name) ? @context.send(name, *args, &block) : super + end + protected def extract_data_and_name(name_or_data) @@ -76,16 +82,5 @@ module RablFastJson template = compiler.compile_block(&block) @template[name] = template.merge!(:_data => data) end - - def _get_assigns_from_context(assigns) - source = assigns || @context.instance_variable_get(:@_assigns) - source.each_pair { |k, v| - instance_variable_set("@#{k}", v) unless k.start_with?('_') - } - end - - def _get_virtual_path_from_context - @virtual_path = @context.instance_variable_get(:@virtual_path) - end end end \ No newline at end of file diff --git a/lib/rabl-fast-json/handler.rb b/lib/rabl-fast-json/handler.rb index 1318498..c86635c 100644 --- a/lib/rabl-fast-json/handler.rb +++ b/lib/rabl-fast-json/handler.rb @@ -6,7 +6,8 @@ module RablFastJson def self.call(template) %{ - RablFastJson::Compiler.instance.compile_source(#{template.source.inspect}, self) + RablFastJson::Library.instance. + get_rendered_template(#{template.source.inspect}, self) } end end diff --git a/lib/rabl-fast-json/library.rb b/lib/rabl-fast-json/library.rb new file mode 100644 index 0000000..ba7e012 --- /dev/null +++ b/lib/rabl-fast-json/library.rb @@ -0,0 +1,43 @@ +require 'singleton' + +module RablFastJson + class Library + include Singleton + + def initialize + @cached_templates = {} + end + + def get_rendered_template(source, context) + path = context.instance_variable_get(:@virtual_path) + @view_renderer = context.instance_variable_get(:@view_renderer) + start = Time.now + compiled_template = get_compiled_template(path, source, context) + compiled_time = Time.now + compiled_template.context = context + r = compiled_template.render + render_time = Time.now + res = ActiveSupport::JSON.encode(r) + final_time = Time.now + + Rails.logger.warn "[BENCHMARK] Compilation:\t#{(compiled_time - start) * 1000.0}ms" + Rails.logger.warn "[BENCHMARK] Rendering:\t\t#{(render_time - compiled_time) * 1000.0}ms" + Rails.logger.warn "[BENCHMARK] JSON encoding:\t#{(final_time - render_time) * 1000.0}ms" + Rails.logger.warn "[BENCHMARK] Total:\t\t#{(final_time - start) * 1000.0}ms" + + res + end + + def get_compiled_template(path, source, context) + #@cached_templates[path] ||= + Compiler.new(context).compile_source(source) + end + + def get(path) + template = @cached_templates[path] + return template if !template.nil? + t = @view_renderer.lookup_context.find_template(path, [], false) + get_compiled_template(path, t.source, nil) + 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 a97a572..16d3dba 100644 --- a/lib/rabl-fast-json/template.rb +++ b/lib/rabl-fast-json/template.rb @@ -1,45 +1,58 @@ module RablFastJson class CompiledTemplate - attr_accessor :source, :data, :root_name + attr_accessor :source, :data, :root_name, :context - delegate :[], :[]=, :to => :source + delegate :[], :[]=, :merge!, :to => :source def initialize @source = {} end - # - # def render(data = nil) - # output = {} - # @data = data if data - # - # return render_collection if @data.respond_to?(:each) - # - # @template.each_pair { |key, value| - # out = case value - # when Symbol - # @data.send(value) - # end - # output[key] = out - # } - # output - # end - # - # def render_collection - # output = [] - # @data.each { |o| - # object = {} - # @template.each_pair { |key, value| - # out = case value - # when Symbol - # o.send(value) - # end - # object[key] = out - # } - # output << object - # } - # output - # end + def get_object_from_assigns + @object = @context.instance_variable_get(@data) + end + + def render + get_object_from_assigns + @object.respond_to?(:each) ? render_collection : render_resource + end + + def render_resource(data = nil, source = nil) + output = {} + + data ||= @object + source ||= @source + + source.each_pair { |key, value| + out = case value + when Symbol + data.send(value) # attributes + when Proc + value.call(data) # node + when Hash + data_symbol = value.delete(:_data) + object = data_symbol.to_s.start_with?('@') ? @context.instance_variable_get(data_symbol) : @object.send(data_symbol) + if key.to_s.start_with?('_') # glue + value.each_pair { |k, v| + output[k] = object.send(v) + } + + next + else # child + object.respond_to?(:each) ? render_collection(object, value) : render_resource(object, value) + end + end + output[key] = out + } + output + end + + def render_collection(collection = nil, source = nil) + output = [] + collection ||= @object + collection.each { |o| output << render_resource(o, source) } + output + end end end \ No newline at end of file diff --git a/test/compiled_template_test.rb b/test/compiled_template_test.rb index 8759458..ff4be17 100644 --- a/test/compiled_template_test.rb +++ b/test/compiled_template_test.rb @@ -1,5 +1,12 @@ require 'test_helper' -class TestCompiledTemplate +class TestCompiledTemplate < ActiveSupport::TestCase + setup do + @context = Context.new + @user = User.new + @context.set_assign('user', @user) + @template = RablFastJson::CompiledTemplate.new + @template.context = @context + end end \ No newline at end of file diff --git a/test/compiler_test.rb b/test/compiler_test.rb index 7b60a45..dc8c985 100644 --- a/test/compiler_test.rb +++ b/test/compiler_test.rb @@ -1,26 +1,6 @@ require 'test_helper' class CompilerTest < ActiveSupport::TestCase - class Context - attr_accessor :virtual_path - - def initialize - @_assigns = {} - @virtual_path = '/users' - end - - def set_assign(key, value) - @_assigns[key] = value - end - - def get_assign(key) - @_assigns[key] - end - end - - class User - attr_accessor :name - end setup do @context = Context.new @@ -29,20 +9,6 @@ class CompilerTest < ActiveSupport::TestCase @compiler = RablFastJson::Compiler.new(@context) end - test "assigns are correctly imported from context" do - assert_equal @user, @compiler.instance_variable_get(:@user) - end - - test "virtual path is correctly imported from context" do - assert_equal '/users', @compiler.instance_variable_get(:@virtual_path) - end - - test "assigns are not imported if already passed to the compiler" do - compiler = RablFastJson::Compiler.new(@context, { 'other' => 'foo' }) - assert_nil compiler.instance_variable_get(:@user) - assert_equal 'foo', compiler.instance_variable_get(:@other) - end - test "compiler return a compiled template" do assert_instance_of RablFastJson::CompiledTemplate, @compiler.compile_source("") end @@ -78,38 +44,56 @@ class CompilerTest < ActiveSupport::TestCase end test "child with arbitrary source store the data with the template" do - t = @compiler.compile_source(%{ child @user => :author do attribute :name end }) - assert_equal({ :author => { :_data => @user, :name => :name } }, t.source) + t = @compiler.compile_source(%{ child :@user => :author do attribute :name end }) + assert_equal({ :author => { :_data => :@user, :name => :name } }, t.source) end test "glue is compiled as a child but with anonymous name" do - t = @compiler.compile_source(%{ glue @user do attribute :name end }) - assert_equal({ :_glue0 => { :_data => @user, :name => :name } }, t.source) + t = @compiler.compile_source(%{ glue :@user do attribute :name end }) + assert_equal({ :_glue0 => { :_data => :@user, :name => :name } }, t.source) end test "multiple glue don't come with name collisions" do t = @compiler.compile_source(%{ - glue @user do attribute :name end - glue @user do attribute :foo end + glue :@user do attribute :name end + glue :@user do attribute :foo end }) assert_equal({ - :_glue0 => { :_data => @user, :name => :name}, - :_glue1 => { :_data => @user, :foo => :foo} + :_glue0 => { :_data => :@user, :name => :name}, + :_glue1 => { :_data => :@user, :foo => :foo} }, t.source) end test "object set data for the template" do - t = @compiler.compile_source(%{ object @user }) - assert_equal @user, t.data + t = @compiler.compile_source(%{ object :@user }) + assert_equal :@user, t.data end test "object property can define root name" do - t = @compiler.compile_source(%{ object @user => :author }) - assert_equal @user, t.data + t = @compiler.compile_source(%{ object :@user => :author }) + assert_equal :@user, t.data assert_equal :author, t.root_name end + test "collection set the data for the template" do + t = @compiler.compile_source(%{ collection :@user }) + assert_equal :@user, t.data + end + + test "collection property can define root name" do + t = @compiler.compile_source(%{ collection :@user => :users }) + assert_equal :@user, t.data + assert_equal :users, t.root_name + end + + test "extends use other template one's active" do + template = mock('template', :source => { :id => :id }) + RablFastJson::Library.stub_chain(:instance, :get).with('users/base').and_return(template) + t = @compiler.compile_source(%{ extends 'users/base' }) + assert_equal({ :id => :id }, t.source) + end + test "node are compiled without evaluating the block" do t = @compiler.compile_source(%{ node(:foo) { bar } }) assert_not_nil t.source[:foo] diff --git a/test/test_helper.rb b/test/test_helper.rb index 7264099..d545410 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -2,6 +2,35 @@ ENV["RAILS_ENV"] = "test" $:.unshift File.expand_path('../../lib', __FILE__) +require 'rspec/mocks' require 'minitest/autorun' require 'active_support/test_case' require 'rabl-fast-json' + +module ActiveSupport + class TestCase + RSpec::Mocks::setup(self) + include RSpec::Mocks::ExampleMethods + end +end + +class Context + attr_accessor :virtual_path + + def initialize + @_assigns = {} + @virtual_path = '/users' + end + + def set_assign(key, value) + @_assigns[key] = value + end + + def get_assign(key) + @_assigns[key] + end +end + +class User + attr_accessor :name +end \ No newline at end of file