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" : }
+# => { "foo" : , "bar" : }
```
### 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(), render_xml_output
+ end
+
+ test "render collection with empty template" do
+ @context.assigns['data'] = [@data]
+ @template.source = {}
+ @template.root_name = :users
+ assert_equal %q(), 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(1), render_xml_output
+ end
+
+ test "render single object attributes" do
+ @template.source = { :name => :name }
+ assert_equal %q(foobar), 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(foobar), 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(foobar), render_xml_output
+ end
+
+ test "render glued attributes from single object" do
+ @template.source = { :_glue0 => { :_data => :@data, :name => :name } }
+ assert_equal %q(foobar), 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(1foo2bar), render_xml_output
+ end
+
+ test "render node property" do
+ proc = lambda { |object| object.name }
+ @template.source = { :name => proc }
+ assert_equal %q(foobar), 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(foobar), 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(), 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(marty), 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(foobar), 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(
), render_xml_output
+ end
+end
\ No newline at end of file