diff --git a/CHANGELOG.md b/CHANGELOG.md index 53a0739..bb479a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ # CHANGELOG ## 0.2.0 (unrelased) - * Use MultiJson's preferred JSON engine as default + * Add XML renderer + * Use MultiJson's preferred JSON engine as default (shmeltex) * Default template to render with responder can be set per controller * Reponder works out of the box with devise * object or collection can be skipped if use with `respond_to` blocks diff --git a/Gemfile b/Gemfile index 46ed377..62a2914 100644 --- a/Gemfile +++ b/Gemfile @@ -3,6 +3,7 @@ source "http://rubygems.org" gemspec gem 'yajl-ruby' +gem 'libxml-ruby' group :test do gem 'rspec-mocks' diff --git a/README.md b/README.md index 1df7dda..417473c 100644 --- a/README.md +++ b/README.md @@ -71,9 +71,9 @@ The only places where you can actually used instance variables are into Proc (o ```ruby # We reference the @posts varibles that will be used at rendering time collection :@posts - + # Here you can use directly the instance variable because it -# will be evaluated when rendering the object +# will be evaluated when rendering the object node(:read) { |post| post.read_by?(@user) } ``` @@ -92,6 +92,7 @@ RablRails works out of the box, with default options and fastest engine availabl # config.cache_templates = true # config.include_json_root = true # config.json_engine = :yajl + # config.xml_engine = 'LibXML' end ``` @@ -139,7 +140,7 @@ You can aliases these attributes in your response ```ruby attributes title: :foo, to_s: :bar -# => { "foo" : , "bar" : <to_s value> } +# => { "foo" : <title value>, "bar" : <to_s value> } ``` ### Child nodes @@ -175,7 +176,7 @@ node(:full_name) { |u| u.first_name + " " + u.last_name } You can add the node only if a condition is true ```ruby -node(:email, if: -> { |u| u.valid_email? }) do |u| +node(:email, if: -> { |u| u.valid_email? }) do |u| u.email end ``` @@ -246,7 +247,7 @@ There are cases when you want to render object outside Rails view context. For i Rabl.render(object, template, :view_path => 'app/views', :format => :json) #=> "{...}" ``` -You can find more informations about how to use this method in the [wiki](http://github.com/ccocchi/rabl-rails/wiki/Render-object-directly) +You can find more informations about how to use this method in the [wiki](http://github.com/ccocchi/rabl-rails/wiki/Render-object-directly) ## Performance diff --git a/lib/rabl-rails.rb b/lib/rabl-rails.rb index d57e57d..2ce32c2 100644 --- a/lib/rabl-rails.rb +++ b/lib/rabl-rails.rb @@ -48,11 +48,22 @@ module RablRails MultiJson.engine end + def self.xml_engine=(name) + ActiveSupport::XmlMini.backend = name + rescue LoadError, NameError + Rails.logger.warn %Q(WARNING: rabl-rails could not load "#{name}" as XML engine, fallback to default) + end + + def self.xml_engine + ActiveSupport::XmlMini.backend + end + def self.cache_templates? ActionController::Base.perform_caching && @@cache_templates end def self.load_default_engines! self.json_engine = MultiJson.default_engine + self.xml_engine = 'LibXML' end end diff --git a/lib/rabl-rails/renderer.rb b/lib/rabl-rails/renderer.rb index b74d3d0..ad53d88 100644 --- a/lib/rabl-rails/renderer.rb +++ b/lib/rabl-rails/renderer.rb @@ -1,5 +1,6 @@ require 'rabl-rails/renderers/base' require 'rabl-rails/renderers/json' +require 'rabl-rails/renderers/xml' module RablRails module Renderer diff --git a/lib/rabl-rails/renderers/xml.rb b/lib/rabl-rails/renderers/xml.rb new file mode 100644 index 0000000..225a732 --- /dev/null +++ b/lib/rabl-rails/renderers/xml.rb @@ -0,0 +1,14 @@ +require 'active_support/core_ext/hash/conversions' + +module RablRails + module Renderers + class XML < Base + DEFAULT_OPTIONS = { dasherize: true, skip_types: false } + + def format_output(hash) + xml_options = { root: _options[:root_name] }.merge!(DEFAULT_OPTIONS) + hash.to_xml(xml_options) + end + end + end +end \ No newline at end of file diff --git a/test/renderers/xml_renderer_test.rb b/test/renderers/xml_renderer_test.rb new file mode 100644 index 0000000..e46a8e7 --- /dev/null +++ b/test/renderers/xml_renderer_test.rb @@ -0,0 +1,131 @@ +require 'test_helper' + +class TestXmlRenderer < ActiveSupport::TestCase + INDENT_REGEXP = /\n(\s)*/ + HEADER_REGEXP = /<[^>]+>/ + + setup do + @data = User.new(1, 'foobar', 'male') + + @context = Context.new + @context.assigns['data'] = @data + + @template = RablRails::CompiledTemplate.new + @template.data = :@data + @template.root_name = :user + end + + def render_xml_output + RablRails::Renderers::XML.new(@context).render(@template).to_s.gsub!(INDENT_REGEXP, '').sub!(HEADER_REGEXP, '') + end + + test "render object simple object" do + @template.source = {} + assert_equal %q(<user></user>), render_xml_output + end + + test "render collection with empty template" do + @context.assigns['data'] = [@data] + @template.source = {} + @template.root_name = :users + assert_equal %q(<users type="array"><user></user></users>), render_xml_output + end + + test "render object with local methods (used by decent_exposure)" do + @context.stub(:user).and_return(@data) + @template.source = { :id => :id } + assert_equal %q(<user><id type="integer">1</id></user>), render_xml_output + end + + test "render single object attributes" do + @template.source = { :name => :name } + assert_equal %q(<user><name>foobar</name></user>), render_xml_output + end + + test "render child with arbitrary data source" do + @template.source = { :author => { :_data => :@data, :name => :name } } + @template.root_name = :post + assert_equal %q(<post><author><name>foobar</name></author></post>), render_xml_output + end + + test "render child with local methods (used by decent_exposure)" do + @context.stub(:user).and_return(@data) + @template.source = { :author => { :_data => :user, :name => :name } } + @template.root_name = :post + assert_equal %q(<post><author><name>foobar</name></author></post>), render_xml_output + end + + test "render glued attributes from single object" do + @template.source = { :_glue0 => { :_data => :@data, :name => :name } } + assert_equal %q(<user><name>foobar</name></user>), render_xml_output + end + + test "render collection with attributes" do + @data = [User.new(1, 'foo', 'male'), User.new(2, 'bar', 'female')] + @context.assigns['data'] = @data + @template.root_name = :users + @template.source = { :uid => :id, :name => :name } + assert_equal %q(<users type="array"><user><uid type="integer">1</uid><name>foo</name></user><user><uid type="integer">2</uid><name>bar</name></user></users>), render_xml_output + end + + test "render node property" do + proc = lambda { |object| object.name } + @template.source = { :name => proc } + assert_equal %q(<user><name>foobar</name></user>), render_xml_output + end + + test "render node property with true condition" do + condition = lambda { |u| true } + proc = lambda { |object| object.name } + @template.source = { :name => [condition, proc] } + assert_equal %q(<user><name>foobar</name></user>), render_xml_output + end + + test "render node property with false condition" do + condition = lambda { |u| false } + proc = lambda { |object| object.name } + @template.source = { :name => [condition, proc] } + assert_equal %q(<user></user>), render_xml_output + end + + test "node with context method call" do + @context.stub(:respond_to?).with(:@data).and_return(false) + @context.stub(:respond_to?).with(:context_method).and_return(true) + @context.stub(:context_method).and_return('marty') + proc = lambda { |object| context_method } + @template.source = { :name => proc } + assert_equal %q(<user><name>marty</name></user>), render_xml_output + end + + test "partial should be evaluated at rendering time" do + # Set assigns + @context.assigns['user'] = @data + + # Stub Library#get + t = RablRails::CompiledTemplate.new + t.source = { :name => :name } + RablRails::Library.reset_instance + RablRails::Library.instance.should_receive(:compile_template_from_path).with('users/base').and_return(t) + + @template.data = false + @template.root_name = :post + @template.source = { :user => ->(s) { partial('users/base', :object => @user) } } + + assert_equal %q(<post><user><name>foobar</name></user></post>), render_xml_output + end + + test "partial with no values should raise an error" do + @template.data = false + @template.source = { :user => ->(s) { partial('users/base') } } + + assert_raises(RablRails::Renderers::PartialError) { render_xml_output } + end + + test "partial with empty values should not raise an error" do + @template.data = false + @template.root_name = :list + @template.source = { :users => ->(s) { partial('users/base', :object => []) } } + + assert_equal %q(<list><users type="array"/></list>), render_xml_output + end +end \ No newline at end of file