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 0ee0acf..168b409 100644 --- a/lib/rabl-rails/renderer.rb +++ b/lib/rabl-rails/renderer.rb @@ -1,2 +1,91 @@ require 'rabl-rails/renderers/base' -require 'rabl-rails/renderers/json' \ No newline at end of file +require 'rabl-rails/renderers/json' + +module RablRails + module Renderer + class TemplateNotFound < StandardError; end + + 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 Rails view + # context + # + class Context + 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 + + # + # 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' } + # + # 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] + + c = Context.new(template, options) + 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 + 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 00c85d7..cb9bcac 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..8108f7c --- /dev/null +++ b/test/render_test.rb @@ -0,0 +1,64 @@ +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 "raise error if template is not found" do + 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 + 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