diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7e43728..8262c53 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,22 @@
# CHANGELOG
+## 0.2.1
+ * Avoid useless render on POST request with custom responder
+ * Custom responder now fallback to Rails default in case the template is not found
+
+## 0.2.0
+ * Add `root` in DSL to set root without changing the data source
+ * 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
+
+## 0.1.3
+ * Render correctly when variables are not passed via the assigns ivar but as helper methods
+ (decent_exposure, focused_controller)
+ * Add custom Responder
+
## 0.1.2
* Add RablRails#render method (see README or source code)
* Fix fail when JSON engine is not found. Now fallback to MultiJson.default_adapter
@@ -8,7 +25,7 @@
## 0.1.1
* Add CHANGELOG
- * Remove unnused test in loop
+ * Remove unused test in loop
* Speed up rendering by not double copying variable from context
* Rename private variable to avoid name conflict
- * Remove sqlite3 development dependency
\ No newline at end of file
+ * Remove sqlite3 development dependency
diff --git a/Gemfile b/Gemfile
index 46ed377..c9d2029 100644
--- a/Gemfile
+++ b/Gemfile
@@ -2,7 +2,8 @@ source "http://rubygems.org"
gemspec
-gem 'yajl-ruby'
+gem 'oj'
+gem 'libxml-ruby'
group :test do
gem 'rspec-mocks'
diff --git a/README.md b/README.md
index 1df7dda..842e5b7 100644
--- a/README.md
+++ b/README.md
@@ -2,9 +2,9 @@
RABL (Ruby API Builder Language) is a ruby templating system for rendering resources in different format (JSON, XML, BSON, ...). You can find documentation [here](http://github.com/nesquena/rabl).
-RABL-rails only target Rails 3+ application because Rails 2 applications are becoming less and less present and will be obsolete with Rails 4. So let's look to the future !
+rabl-rails is **faster** and uses **less memory** than the standard rabl gem while letting you access the same features. There are some slight changes to do on your templates to get this gem to work but it should't take you more than 5 minutes.
-So now you ask why used `rabl-rails` if `rabl` already exists and supports Rails. Rabl-rails is **faster** and uses **less memory** than standard rabl gem while letting you access same features. Of course, there are some slight changes to do on your templates to get this gem to work but it should't take you more than 5 minutes.
+rabl-rails only target **Rails 3+ application**.
## Installation
@@ -24,7 +24,7 @@ And that's it !
## Overview
-Once you have installed RABL, you can directly used RABL templates to render your resources without changing anything to you controller. As example,
+Once you have installed rabl-rails, you can directly used RABL-rails templates to render your resources without changing anything to you controller. As example,
assuming you have a `Post` model filled with blog posts, and a `PostController` that look like this :
```ruby
@@ -32,7 +32,7 @@ class PostController < ApplicationController
respond_to :html, :json, :xml
def index
- @posts = Post.order('created_at DESC')
+ @posts = Post.order('created_at DESC')
respond_with(@posts)
end
end
@@ -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) }
```
@@ -83,16 +83,19 @@ After the template is compiled into a hash, Rabl-rails will use a renderer to do
## Configuration
-RablRails works out of the box, with default options and fastest engine available (yajl, libxml). But depending on your needs, you might want to change that or how your output looks like. You can set global configuration in your application:
+RablRails works out of the box, with default options and fastest engine available (oj, libxml). But depending on your needs, you might want to change that or how your output looks like. You can set global configuration in your application:
```ruby
- # config/initializers/rabl_rails.rb
- RablRails.configure do |config|
- # These are the default
- # config.cache_templates = true
- # config.include_json_root = true
- # config.json_engine = :yajl
- end
+# config/initializers/rabl_rails.rb
+RablRails.configure do |config|
+ # These are the default
+ # config.cache_templates = true
+ # config.include_json_root = true
+ # config.json_engine = :oj
+ # config.xml_engine = 'LibXML'
+ # config.use_custom_responder = false
+ # config.default_responder_template = 'show'
+end
```
## Usage
@@ -127,6 +130,24 @@ node(:some_count) { |_| @user.posts.count }
child(:@user) { attribute :name }
```
+If you use gem like *decent_exposure* or *focused_controller*, you can use your variable directly without the leading `@`
+
+```ruby
+object :object_exposed
+```
+
+You can even skip data declaration at all. If you used `respond_with`, rabl-rails will render the data you passed to it.
+As there is no name, you can set a root via the `root` macro. This allow you to use your template without caring about variables passed to it.
+
+```ruby
+# in controller
+respond_with(@post)
+
+# in rabl-rails template
+root :article
+attribute :title
+```
+
### Attributes / Methods
Basic usage is to declared attributes to include in the response. These can be database attributes or any instance method.
@@ -139,7 +160,7 @@ You can aliases these attributes in your response
```ruby
attributes title: :foo, to_s: :bar
-# => { "foo" :
, "bar" : }
+# => { "foo" : , "bar" : }
```
### Child nodes
@@ -175,7 +196,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
```
@@ -232,12 +253,6 @@ child :posts do
end
```
-### Caching
-
-Caching is not a part of Rabl-rails. It is already in Rails itself, because caching all view output is the same as action caching (Rails caching is even better because it will also not run your queries).
-
-Moreover caching each object in a collection can be really not effective with big collections or simple objects. This is also a nightmare with cache expiration.
-
### Render object directly
There are cases when you want to render object outside Rails view context. For instance to render objects in the console or to create message queue payloads. For these situations, you can use `RablRails.render` as show below:
@@ -246,13 +261,17 @@ 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)
+
+### Other features
+
+You can find more informations about other features (caching, custom_responder, ...) in the [WIKI](https://github.com/ccocchi/rabl-rails/wiki)
## Performance
Benchmarks have been made using this [application](http://github.com/ccocchi/rabl-benchmark), with rabl 0.6.14 and rabl-rails 0.1.0
-Overall, Rabl-rails is **20% faster and use 10% less memory**.
+Overall, Rabl-rails is **20% faster and use 10% less memory**, even **twice faster** when rendering collections with extends.
You can see full tests on test application repository.
diff --git a/lib/rabl-rails.rb b/lib/rabl-rails.rb
index 18f6b04..db3c321 100644
--- a/lib/rabl-rails.rb
+++ b/lib/rabl-rails.rb
@@ -19,24 +19,44 @@ require 'multi_json'
module RablRails
extend Renderer
+ autoload :Responder, 'rabl-rails/responder'
+
mattr_accessor :cache_templates
@@cache_templates = true
mattr_accessor :include_json_root
@@include_json_root = true
- mattr_reader :json_engine
- @@json_engine = :yajl
+ mattr_accessor :use_custom_responder
+ @@use_custom_responder = false
+
+ mattr_accessor :responder_default_template
+ @@responder_default_template = 'show'
def self.configure
yield self
+
+ ActionController::Base.responder = Responder if self.use_custom_responder
end
def self.json_engine=(name)
MultiJson.engine = name
- @@json_engine = name
rescue LoadError
- Rails.logger.warn %Q(WARNING: rabl-rails could not load "#{self.json_engine}" as JSON engine, fallback to default)
+ Rails.logger.warn %Q(WARNING: rabl-rails could not load "#{name}" as JSON engine, fallback to default)
+ end
+
+ def self.json_engine
+ 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?
@@ -44,6 +64,7 @@ module RablRails
end
def self.load_default_engines!
- self.json_engine = :yajl
+ self.json_engine = MultiJson.default_engine
+ self.xml_engine = 'LibXML' if defined?(LibXML)
end
end
diff --git a/lib/rabl-rails/compiler.rb b/lib/rabl-rails/compiler.rb
index 8ac8042..707a279 100644
--- a/lib/rabl-rails/compiler.rb
+++ b/lib/rabl-rails/compiler.rb
@@ -30,6 +30,10 @@ module RablRails
end
alias_method :collection, :object
+ def root(name)
+ @template.root_name = name
+ end
+
#
# Includes the attribute or method in the output
# Example:
diff --git a/lib/rabl-rails/handler.rb b/lib/rabl-rails/handler.rb
index 8cc028e..6b1d9ab 100644
--- a/lib/rabl-rails/handler.rb
+++ b/lib/rabl-rails/handler.rb
@@ -7,7 +7,7 @@ module RablRails
def self.call(template)
%{
RablRails::Library.instance.
- get_rendered_template(#{template.source.inspect}, self)
+ get_rendered_template(#{template.source.inspect}, self, local_assigns)
}
end
end
diff --git a/lib/rabl-rails/library.rb b/lib/rabl-rails/library.rb
index 0324369..e53bc4a 100644
--- a/lib/rabl-rails/library.rb
+++ b/lib/rabl-rails/library.rb
@@ -8,14 +8,14 @@ module RablRails
@cached_templates = {}
end
- def get_rendered_template(source, context)
+ def get_rendered_template(source, context, locals = nil)
path = context.instance_variable_get(:@virtual_path)
@lookup_context = context.lookup_context
compiled_template = compile_template_from_source(source, path)
format = context.params[:format] || 'json'
- Renderers.const_get(format.upcase!).new(context).render(compiled_template)
+ Renderers.const_get(format.upcase!).new(context, locals).render(compiled_template)
end
def compile_template_from_source(source, path = nil)
diff --git a/lib/rabl-rails/renderer.rb b/lib/rabl-rails/renderer.rb
index 168b409..ad53d88 100644
--- a/lib/rabl-rails/renderer.rb
+++ b/lib/rabl-rails/renderer.rb
@@ -1,10 +1,11 @@
require 'rabl-rails/renderers/base'
require 'rabl-rails/renderers/json'
+require 'rabl-rails/renderers/xml'
module RablRails
module Renderer
class TemplateNotFound < StandardError; end
-
+
mattr_reader :view_path
@@view_path = 'app/views'
@@ -33,7 +34,6 @@ module RablRails
#
class Context
attr_reader :format
- attr_accessor :target_object
def initialize(path, options)
@virtual_path = path
@@ -56,17 +56,17 @@ module RablRails
@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
+ # 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:
@@ -80,12 +80,11 @@ module RablRails
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)
+ Library.instance.get_rendered_template(t.source, c, resource: object)
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 07c0796..ef664d7 100644
--- a/lib/rabl-rails/renderers/base.rb
+++ b/lib/rabl-rails/renderers/base.rb
@@ -5,9 +5,10 @@ module RablRails
class Base
attr_accessor :_options
- def initialize(context) # :nodoc:
+ def initialize(context, locals = nil) # :nodoc:
@_context = context
@_options = {}
+ @_resource = locals[:resource] if locals
setup_render_context
end
@@ -18,8 +19,14 @@ module RablRails
# method defined by the renderer.
#
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)
+ collection_or_resource = if template.data
+ if @_context.respond_to?(template.data)
+ @_context.send(template.data)
+ else
+ instance_variable_get(template.data)
+ end
+ end
+ collection_or_resource ||= @_resource
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
@@ -55,7 +62,13 @@ module RablRails
when Hash
current_value = value.dup
data_symbol = current_value.delete(:_data)
- object = data_symbol.nil? ? data : data_symbol.to_s.start_with?('@') ? instance_variable_get(data_symbol) : data.send(data_symbol)
+ object = if data_symbol == nil
+ data
+ else
+ data_symbol.to_s.start_with?('@') ? instance_variable_get(data_symbol)
+ : data.respond_to?(data_symbol) ? data.send(data_symbol)
+ : send(data_symbol)
+ end
if key.to_s.start_with?('_') # glue
current_value.each_pair { |k, v|
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/lib/rabl-rails/responder.rb b/lib/rabl-rails/responder.rb
new file mode 100644
index 0000000..47220c4
--- /dev/null
+++ b/lib/rabl-rails/responder.rb
@@ -0,0 +1,46 @@
+module RablRails
+ #
+ # Override default responder's api behavior to not
+ # user to_format methods on a resource as a default
+ # representation but instead use a rabl template
+ #
+ class Responder < ActionController::Responder
+ def initialize(controller, resources, options = {})
+ super
+ if options[:locals]
+ options[:locals][:resource] = resource
+ else
+ options[:locals] = { resource: resource }
+ end
+ end
+
+ def to_format
+ if get? || response_overridden?
+ default_render
+ elsif has_errors?
+ display_errors
+ else
+ api_behavior(nil)
+ end
+ end
+
+ protected
+
+ def api_behavior(error)
+ if post?
+ template = if @controller.respond_to?(:responder_default_template, true)
+ controller.send(:responder_default_template)
+ else
+ RablRails.responder_default_template
+ end
+ options[:template] ||= "#{@controller.controller_name}/#{template}"
+
+ controller.default_render options.merge(status: :created, location: api_location)
+ else
+ head :no_content
+ end
+ rescue ActionView::MissingTemplate => e
+ super(e)
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/rabl-rails/version.rb b/lib/rabl-rails/version.rb
index 50534f0..c4daa75 100644
--- a/lib/rabl-rails/version.rb
+++ b/lib/rabl-rails/version.rb
@@ -1,3 +1,3 @@
module RablRails
- VERSION = '0.1.2'
+ VERSION = '0.2.1'
end
diff --git a/test/compiler_test.rb b/test/compiler_test.rb
index 399e048..7011939 100644
--- a/test/compiler_test.rb
+++ b/test/compiler_test.rb
@@ -24,6 +24,16 @@ class CompilerTest < ActiveSupport::TestCase
assert_equal({}, t.source)
end
+ test "root can be defined via keyword" do
+ t = @compiler.compile_source(%{ root :author })
+ assert_equal :author, t.root_name
+ end
+
+ test "root keyword override object root" do
+ t = @compiler.compile_source(%{ object :@user ; root :author })
+ assert_equal :author, t.root_name
+ end
+
test "collection set the data for the template" do
t = @compiler.compile_source(%{ collection :@user })
assert_equal :@user, t.data
diff --git a/test/deep_nesting_test.rb b/test/deep_nesting_test.rb
index e689233..4292877 100644
--- a/test/deep_nesting_test.rb
+++ b/test/deep_nesting_test.rb
@@ -19,7 +19,6 @@ class DeepNestingTest < ActiveSupport::TestCase
@post = Post.new(42, 'I rock !')
@user = User.new(1, 'foobar', 'male')
@user.stub(:posts).and_return([@post])
- @user.stub(:respond_to?).with(:each).and_return(false)
@context = Context.new
@context.assigns['user'] = @user
diff --git a/test/render_test.rb b/test/render_test.rb
index 8108f7c..9b4b26d 100644
--- a/test/render_test.rb
+++ b/test/render_test.rb
@@ -6,7 +6,6 @@ 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
@@ -29,16 +28,16 @@ class RenderTest < ActiveSupport::TestCase
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 }
+ node(:username) { |_| @user.name }
}
end
assert_equal %q({"username":"Marty"}), RablRails.render(nil, 'instance', view_path: @tmp_path, locals: { user: @user })
@@ -51,7 +50,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
diff --git a/test/renderers/json_renderer_test.rb b/test/renderers/json_renderer_test.rb
index 8e430c8..ec0cd80 100644
--- a/test/renderers/json_renderer_test.rb
+++ b/test/renderers/json_renderer_test.rb
@@ -4,7 +4,6 @@ class TestJsonRenderer < ActiveSupport::TestCase
setup do
@data = User.new(1, 'foobar', 'male')
- @data.stub(:respond_to?).with(:each).and_return(false)
@context = Context.new
@context.assigns['data'] = @data
@@ -28,6 +27,13 @@ class TestJsonRenderer < ActiveSupport::TestCase
assert_equal %q([{}]), render_json_output
end
+ test "render object with local methods (used by decent_exposure)" do
+ @context.stub(:user).and_return(@data)
+ @template.data = :user
+ @template.source = { :id => :id }
+ assert_equal %q({"id":1}), render_json_output
+ end
+
test "render single object attributes" do
@template.source = { :id => :id, :name => :name }
assert_equal %q({"id":1,"name":"foobar"}), render_json_output
@@ -44,6 +50,12 @@ class TestJsonRenderer < ActiveSupport::TestCase
assert_equal %q({"author":{"name":"foobar"}}), render_json_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 } }
+ assert_equal %q({"author":{"name":"foobar"}}), render_json_output
+ end
+
test "render glued attributes from single object" do
@template.source = { :_glue0 => { :_data => :@data, :name => :name } }
assert_equal %q({"name":"foobar"}), render_json_output
@@ -77,6 +89,7 @@ class TestJsonRenderer < ActiveSupport::TestCase
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 }
@@ -86,7 +99,6 @@ class TestJsonRenderer < ActiveSupport::TestCase
test "partial should be evaluated at rendering time" do
# Set assigns
- @data.stub(:respond_to?).with(:empty?).and_return(false)
@context.assigns['user'] = @data
# Stub Library#get
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
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 26b144e..7468f73 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -32,12 +32,12 @@ end
class Context
attr_writer :virtual_path
-
+
def initialize
@_assigns = {}
@virtual_path = nil
end
-
+
def assigns
@_assigns
end
@@ -47,4 +47,11 @@ class Context
end
end
-User = Struct.new(:id, :name, :sex)
\ No newline at end of file
+class User
+ attr_accessor :id, :name, :sex
+ def initialize(id=nil, name=nil, sex=nil)
+ @id = id
+ @name = name
+ @sex = sex
+ end
+end
\ No newline at end of file