From 042609b5d4247ec8c0b10f3a23a8b7e4ca57d4f5 Mon Sep 17 00:00:00 2001 From: ccocchi Date: Tue, 24 Jul 2012 16:32:32 +0200 Subject: [PATCH 1/6] Add RablRails#render --- lib/rabl-rails/renderer.rb | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/lib/rabl-rails/renderer.rb b/lib/rabl-rails/renderer.rb index 0ee0acf..f149b4f 100644 --- a/lib/rabl-rails/renderer.rb +++ b/lib/rabl-rails/renderer.rb @@ -1,2 +1,24 @@ require 'rabl-rails/renderers/base' -require 'rabl-rails/renderers/json' \ No newline at end of file +require 'rabl-rails/renderers/json' + +module Renderer + mattr_reader :view_path + @@view_path = 'app/views' + + def render(object, template, options = {}) + format = options.delete(:format) || 'json' + source = find_template(template, format, options.delete(:view_path)) + compiled_template = Compiler.new.compile_source(source) + + # TODO: context needs to be set from options + Renderers.const_get(format.upcase!).new(context).render(compiled_template) + end + + private + + def find_template(name, format, view_path = nil) + view_path ||= self.view_path + path = File.join(view_path, "#{name}.#{format}.rabl") + File.exists?(path) : File.read(path) : nil + end +end \ No newline at end of file From 9e6b4db8ebd1654229560c38bff58c4473ea112d Mon Sep 17 00:00:00 2001 From: ccocchi Date: Wed, 25 Jul 2012 19:22:10 +0200 Subject: [PATCH 2/6] Make render work with same signature as standard RABL gem --- lib/rabl-rails.rb | 2 ++ lib/rabl-rails/renderer.rb | 44 +++++++++++++++++++++++++------------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/lib/rabl-rails.rb b/lib/rabl-rails.rb index 471fa00..5fbf960 100644 --- a/lib/rabl-rails.rb +++ b/lib/rabl-rails.rb @@ -16,6 +16,8 @@ require 'rabl-rails/railtie' require 'multi_json' module RablRails + extend Renderer + mattr_accessor :cache_templates @@cache_templates = true diff --git a/lib/rabl-rails/renderer.rb b/lib/rabl-rails/renderer.rb index f149b4f..7590150 100644 --- a/lib/rabl-rails/renderer.rb +++ b/lib/rabl-rails/renderer.rb @@ -1,24 +1,38 @@ require 'rabl-rails/renderers/base' require 'rabl-rails/renderers/json' -module Renderer - mattr_reader :view_path - @@view_path = 'app/views' +module RablRails + module Renderer + mattr_reader :view_path + @@view_path = 'app/views' - def render(object, template, options = {}) - format = options.delete(:format) || 'json' - source = find_template(template, format, options.delete(:view_path)) - compiled_template = Compiler.new.compile_source(source) + class Context + def initialize + @_assigns = {} + end + + def assigns + @_assigns + end + end + + def render(object, template, options = {}) + format = options.delete(:format) || 'json' + source = find_template(template, format, options.delete(:view_path)) + compiled_template = Compiler.new.compile_source(source) - # TODO: context needs to be set from options - Renderers.const_get(format.upcase!).new(context).render(compiled_template) - end + c = Context.new + c.assigns[compiled_template.data.to_s[1..-1]] = object if compiled_template.data + + Renderers.const_get(format.upcase!).new(c).render(compiled_template) + end - private + private - def find_template(name, format, view_path = nil) - view_path ||= self.view_path - path = File.join(view_path, "#{name}.#{format}.rabl") - File.exists?(path) : File.read(path) : nil + def find_template(name, format, view_path = nil) + view_path ||= self.view_path + path = File.join(view_path, "#{name}.#{format}.rabl") + File.exists?(path) ? File.read(path) : nil + end end end \ No newline at end of file From c60306eee54b07985d010cb0ef28b4e0d1eb7680 Mon Sep 17 00:00:00 2001 From: ccocchi Date: Wed, 25 Jul 2012 22:46:20 +0200 Subject: [PATCH 3/6] Add documentation for RablRails#render --- lib/rabl-rails/renderer.rb | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/rabl-rails/renderer.rb b/lib/rabl-rails/renderer.rb index 7590150..5f2c081 100644 --- a/lib/rabl-rails/renderer.rb +++ b/lib/rabl-rails/renderer.rb @@ -6,6 +6,10 @@ module RablRails mattr_reader :view_path @@view_path = 'app/views' + # + # Context class to emulate normal rendering view + # context + # class Context def initialize @_assigns = {} @@ -16,11 +20,22 @@ module RablRails end end + # + # Renders object with the given rabl template. + # + # Object can also be passed as an option : + # { locals: { object: obj_to_render } } + # + # Default render format is JSON, but can be changed via + # an option: { format: 'xml' } + # def render(object, template, options = {}) format = options.delete(:format) || 'json' + object = options[:locals].delete(:object) if !object && options[:locals] + source = find_template(template, format, options.delete(:view_path)) compiled_template = Compiler.new.compile_source(source) - + c = Context.new c.assigns[compiled_template.data.to_s[1..-1]] = object if compiled_template.data @@ -29,6 +44,11 @@ module RablRails private + # + # Manually find given rabl template file with given format. + # View path can be set via options, otherwise default Rails + # path is used + # def find_template(name, format, view_path = nil) view_path ||= self.view_path path = File.join(view_path, "#{name}.#{format}.rabl") From d4c434e6b0c0345046d7b00a0d4a5f17892d0757 Mon Sep 17 00:00:00 2001 From: ccocchi Date: Thu, 26 Jul 2012 02:21:33 +0200 Subject: [PATCH 4/6] Refactor RablRails#render to use standard library methods. Emulate a render and lookup context like Rails. --- lib/rabl-rails/renderer.rb | 62 +++++++++++++++++++++----------- lib/rabl-rails/renderers/base.rb | 1 + test/render_test.rb | 52 +++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 21 deletions(-) create mode 100644 test/render_test.rb diff --git a/lib/rabl-rails/renderer.rb b/lib/rabl-rails/renderer.rb index 5f2c081..6fb6efe 100644 --- a/lib/rabl-rails/renderer.rb +++ b/lib/rabl-rails/renderer.rb @@ -5,19 +5,54 @@ module RablRails module Renderer mattr_reader :view_path @@view_path = 'app/views' - + + class LookupContext + T = Struct.new(:source) + + def initialize(view_path, format) + @view_path = view_path || RablRails::Renderer.view_path + @format = format + end + + # + # Manually find given rabl template file with given format. + # View path can be set via options, otherwise default Rails + # path is used + # + def find_template(name, opt, partial = false) + path = File.join(@view_path, "#{name}.#{@format}.rabl") + File.exists?(path) ? T.new(File.read(path)) : nil + end + end + # # Context class to emulate normal rendering view # context # class Context - def initialize + attr_reader :format + attr_accessor :target_object + + def initialize(path, options) + @virtual_path = path + @format = options.delete(:format) || 'json' @_assigns = {} + @options = options + + options[:locals].each { |k, v| @_assigns[k.to_s] = v } if options[:locals] end def assigns @_assigns end + + def params + { format: format } + end + + def lookup_context + @lookup_context ||= LookupContext.new(@options[:view_path], format) + end end # @@ -30,29 +65,14 @@ module RablRails # an option: { format: 'xml' } # def render(object, template, options = {}) - format = options.delete(:format) || 'json' object = options[:locals].delete(:object) if !object && options[:locals] - source = find_template(template, format, options.delete(:view_path)) - compiled_template = Compiler.new.compile_source(source) + c = Context.new(template, options) + c.target_object = object - c = Context.new - c.assigns[compiled_template.data.to_s[1..-1]] = object if compiled_template.data + t = c.lookup_context.find_template(template, [], false) - Renderers.const_get(format.upcase!).new(c).render(compiled_template) - end - - private - - # - # Manually find given rabl template file with given format. - # View path can be set via options, otherwise default Rails - # path is used - # - def find_template(name, format, view_path = nil) - view_path ||= self.view_path - path = File.join(view_path, "#{name}.#{format}.rabl") - File.exists?(path) ? File.read(path) : nil + Library.instance.get_rendered_template(t.source, c) end end end \ No newline at end of file diff --git a/lib/rabl-rails/renderers/base.rb b/lib/rabl-rails/renderers/base.rb index e053a19..b8b3316 100644 --- a/lib/rabl-rails/renderers/base.rb +++ b/lib/rabl-rails/renderers/base.rb @@ -19,6 +19,7 @@ module RablRails # def render(template) collection_or_resource = instance_variable_get(template.data) if template.data + collection_or_resource = @_context.target_object unless collection_or_resource || template.data == false || !@_context.respond_to?(:target_object) output_hash = collection_or_resource.respond_to?(:each) ? render_collection(collection_or_resource, template.source) : render_resource(collection_or_resource, template.source) options[:root_name] = template.root_name diff --git a/test/render_test.rb b/test/render_test.rb new file mode 100644 index 0000000..f0b543e --- /dev/null +++ b/test/render_test.rb @@ -0,0 +1,52 @@ +require 'test_helper' +require 'pathname' +require 'tmpdir' + +class RenderTest < ActiveSupport::TestCase + + setup do + @user = User.new(1, 'Marty') + @user.stub(:respond_to?).with(:each).and_return(false) + @tmp_path = Pathname.new(Dir.mktmpdir) + end + + test "allow object to be passed as an option" do + File.open(@tmp_path + "nil.json.rabl", "w") do |f| + f.puts %q{ + object :@user + attributes :name + } + end + + assert_equal %q({"user":{"name":"Marty"}}), RablRails.render(nil, 'nil', locals: { object: @user }, view_path: @tmp_path) + end + + test "load source from file" do + File.open(@tmp_path + "show.json.rabl", "w") do |f| + f.puts %q{ + object :@user + attributes :id, :name + } + end + + assert_equal %q({"user":{"id":1,"name":"Marty"}}), RablRails.render(@user, 'show', view_path: @tmp_path) + end + + test "handle path for extends" do + File.open(@tmp_path + "extend.json.rabl", "w") do |f| + f.puts %q{ + object :@user + extends 'base' + } + end + + File.open(@tmp_path + "base.json.rabl", "w") do |f| + f.puts %q{ + attribute :name, as: :extended_name + } + end + + assert_equal %q({"user":{"extended_name":"Marty"}}), RablRails.render(@user, 'extend', view_path: @tmp_path) + end + +end \ No newline at end of file From b42f63788f3c1d7a730f46c10ce9de575985de4a Mon Sep 17 00:00:00 2001 From: ccocchi Date: Thu, 26 Jul 2012 14:59:28 +0200 Subject: [PATCH 5/6] Raise error when template is not found --- lib/rabl-rails/renderer.rb | 3 +++ test/render_test.rb | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/lib/rabl-rails/renderer.rb b/lib/rabl-rails/renderer.rb index 6fb6efe..2a04493 100644 --- a/lib/rabl-rails/renderer.rb +++ b/lib/rabl-rails/renderer.rb @@ -3,6 +3,8 @@ require 'rabl-rails/renderers/json' module RablRails module Renderer + class TemplateNotFound < StandardError; end + mattr_reader :view_path @@view_path = 'app/views' @@ -71,6 +73,7 @@ module RablRails c.target_object = object t = c.lookup_context.find_template(template, [], false) + raise TemplateNotFound unless t Library.instance.get_rendered_template(t.source, c) end diff --git a/test/render_test.rb b/test/render_test.rb index f0b543e..c9dd417 100644 --- a/test/render_test.rb +++ b/test/render_test.rb @@ -31,6 +31,10 @@ class RenderTest < ActiveSupport::TestCase assert_equal %q({"user":{"id":1,"name":"Marty"}}), RablRails.render(@user, 'show', view_path: @tmp_path) end + + test "raise error if template is not found" do + assert_raises(RablRails::Renderer::TemplateNotFound) { RablRails.render(@user, 'show') } + end test "handle path for extends" do File.open(@tmp_path + "extend.json.rabl", "w") do |f| From 8f5ebfac03ad32d7f4053f7284165c7a033c3664 Mon Sep 17 00:00:00 2001 From: ccocchi Date: Thu, 26 Jul 2012 15:24:20 +0200 Subject: [PATCH 6/6] Add test for RablRails#render with instance variables --- lib/rabl-rails/renderer.rb | 12 +++++++++++- test/render_test.rb | 16 ++++++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/lib/rabl-rails/renderer.rb b/lib/rabl-rails/renderer.rb index 2a04493..168b409 100644 --- a/lib/rabl-rails/renderer.rb +++ b/lib/rabl-rails/renderer.rb @@ -28,7 +28,7 @@ module RablRails end # - # Context class to emulate normal rendering view + # Context class to emulate normal Rails view # context # class Context @@ -66,6 +66,16 @@ module RablRails # Default render format is JSON, but can be changed via # an option: { format: 'xml' } # + # If template includes uses of instance variables (usually + # defined in the controller), you can passed them as locals + # options. + # For example, if you have this template: + # object :@user + # node(:read) { |u| u.has_read?(@post) } + # + # Your method call should look like this: + # RablRails.render(user, 'users/show', locals: { post: Post.new }) + # def render(object, template, options = {}) object = options[:locals].delete(:object) if !object && options[:locals] diff --git a/test/render_test.rb b/test/render_test.rb index c9dd417..8108f7c 100644 --- a/test/render_test.rb +++ b/test/render_test.rb @@ -17,7 +17,6 @@ class RenderTest < ActiveSupport::TestCase attributes :name } end - assert_equal %q({"user":{"name":"Marty"}}), RablRails.render(nil, 'nil', locals: { object: @user }, view_path: @tmp_path) end @@ -28,12 +27,21 @@ class RenderTest < ActiveSupport::TestCase attributes :id, :name } end - assert_equal %q({"user":{"id":1,"name":"Marty"}}), RablRails.render(@user, 'show', view_path: @tmp_path) end test "raise error if template is not found" do - assert_raises(RablRails::Renderer::TemplateNotFound) { RablRails.render(@user, 'show') } + assert_raises(RablRails::Renderer::TemplateNotFound) { RablRails.render(@user, 'not_found') } + end + + test "instance variables can be passed via options[:locals]" do + File.open(@tmp_path + "instance.json.rabl", "w") do |f| + f.puts %q{ + object false + node(:username) { |_| @user.name } + } + end + assert_equal %q({"username":"Marty"}), RablRails.render(nil, 'instance', view_path: @tmp_path, locals: { user: @user }) end test "handle path for extends" do @@ -43,7 +51,7 @@ class RenderTest < ActiveSupport::TestCase extends 'base' } end - + File.open(@tmp_path + "base.json.rabl", "w") do |f| f.puts %q{ attribute :name, as: :extended_name