From 9364803048fd38c4cf865fc633c80994c7b47660 Mon Sep 17 00:00:00 2001 From: ccocchi Date: Sun, 15 Jul 2012 18:23:21 +0200 Subject: [PATCH 1/6] Add XML renderer --- lib/rabl-rails/renderers/xml.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 lib/rabl-rails/renderers/xml.rb diff --git a/lib/rabl-rails/renderers/xml.rb b/lib/rabl-rails/renderers/xml.rb new file mode 100644 index 0000000..289dae5 --- /dev/null +++ b/lib/rabl-rails/renderers/xml.rb @@ -0,0 +1,13 @@ +module RablRails + module Renderers + class XML < Base + DEFAULT_OPTIONS = { dasherize: true, skip_types: false } + + def format_output(hash) + # hash = hash[options[:root_name]] if options[:root_name] + xml_options = { root: options[:root_name] }.merge!(DEFAULT_OPTIONS) + hash.to_xml(xml_options) + end + end + end +end \ No newline at end of file From 283b957666a066ee616eb751f8253174596ea3d0 Mon Sep 17 00:00:00 2001 From: ccocchi Date: Mon, 17 Sep 2012 15:08:28 +0200 Subject: [PATCH 2/6] Add tests for XML renderers --- lib/rabl-rails/renderer.rb | 1 + lib/rabl-rails/renderers/xml.rb | 7 +- test/renderers/xml_renderer_test.rb | 131 ++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 test/renderers/xml_renderer_test.rb 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 index 289dae5..225a732 100644 --- a/lib/rabl-rails/renderers/xml.rb +++ b/lib/rabl-rails/renderers/xml.rb @@ -1,11 +1,12 @@ +require 'active_support/core_ext/hash/conversions' + module RablRails module Renderers class XML < Base - DEFAULT_OPTIONS = { dasherize: true, skip_types: false } + DEFAULT_OPTIONS = { dasherize: true, skip_types: false } def format_output(hash) - # hash = hash[options[:root_name]] if options[:root_name] - xml_options = { root: options[:root_name] }.merge!(DEFAULT_OPTIONS) + xml_options = { root: _options[:root_name] }.merge!(DEFAULT_OPTIONS) hash.to_xml(xml_options) end end 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 From c566c0d78854a1e3aedb85058afa07d6ab00f38e Mon Sep 17 00:00:00 2001 From: ccocchi Date: Mon, 17 Sep 2012 15:08:54 +0200 Subject: [PATCH 3/6] Add libxml to faster XML output generation --- Gemfile | 1 + test/test_helper.rb | 2 ++ 2 files changed, 3 insertions(+) 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/test/test_helper.rb b/test/test_helper.rb index 7468f73..9106808 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -23,6 +23,8 @@ end require 'rabl-rails' +ActiveSupport::XmlMini.backend = 'LibXML' + module ActiveSupport class TestCase RSpec::Mocks::setup(self) From 84cd69bdfd83f0413fc34b92f5ee9cae914cee71 Mon Sep 17 00:00:00 2001 From: ccocchi Date: Mon, 17 Sep 2012 15:24:39 +0200 Subject: [PATCH 4/6] Add XML engine configuration --- lib/rabl-rails.rb | 11 +++++++++++ test/test_helper.rb | 2 -- 2 files changed, 11 insertions(+), 2 deletions(-) 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/test/test_helper.rb b/test/test_helper.rb index 9106808..7468f73 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -23,8 +23,6 @@ end require 'rabl-rails' -ActiveSupport::XmlMini.backend = 'LibXML' - module ActiveSupport class TestCase RSpec::Mocks::setup(self) From d81c3bebfe5a3ba825c217757435ad10de3e7e8f Mon Sep 17 00:00:00 2001 From: ccocchi Date: Mon, 17 Sep 2012 15:26:36 +0200 Subject: [PATCH 5/6] Update README --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) 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 From 62cabb5f37eb38d9c8a10a03152fe4b8507d2f82 Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Mon, 17 Sep 2012 15:28:14 +0200 Subject: [PATCH 6/6] Add entry to CHANGELOG --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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