Merge branch 'master' into fragment-cache

This commit is contained in:
ccocchi 2012-05-04 16:05:11 +02:00
commit 4cf10fb8f6
24 changed files with 390 additions and 268 deletions

View File

@ -1,6 +1,6 @@
source "http://rubygems.org"
# Declare your gem's dependencies in rabl-fast-json.gemspec.
# Declare your gem's dependencies in rabl-rails.gemspec.
# Bundler will treat runtime dependencies like base dependencies, and
# development dependencies will be added by default to the :development group.
gemspec

View File

@ -1,7 +1,7 @@
PATH
remote: .
specs:
rabl-fast-json (0.1.0)
rabl-rails (0.1.0)
activesupport (~> 3.2.1)
GEM
@ -61,7 +61,7 @@ PLATFORMS
DEPENDENCIES
actionpack (~> 3.2.1)
rabl-fast-json!
rabl-rails!
railties (~> 3.2.1)
rspec-mocks
sqlite3

View File

@ -1,3 +1,84 @@
= RablFastJson
# RABL for Rails #
This project rocks and uses MIT-LICENSE.
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 !
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.
## Installation
Install as a gem :
```
gem install rabl-rails
```
or add directly to your `Gemfile`
```
gem 'rabl'
```
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,
assuming you have a `Post` model filled with blog posts, and a `PostController` that look like this :
```ruby
class PostController < ApplicationController
respond_to :html, :json, :xml
def index
@posts = Post.order('created_at DESC')
respond_with(@posts)
end
end
```
You can create the following RABL-rails template to express the API output of `@posts`
```ruby
# app/views/post/index.rabl
collection :@posts
attributes :id, :title, :subject
child(:user) { attributes :full_name }
node(:read) { |post| post.read_by?(@user) }
```
This would output the following JSON when visiting `http://localhost:3000/posts.json`
```js
[{
"id" : 5, title: "...", subject: "...",
"user" : { full_name : "..." },
"read" : true
}]
```
That's a basic overview but there is a lot more to see such as partials, inheritance or fragment caching.
## How it works
As opposed to standard RABL gem, this gem separate compiling (a.k.a transforming a RABL-rails template into a Ruby hash) and the actual rendering of the object or collection. This allow to only compile the template once and only Ruby hashes.
The fact of compiling the template outside of any rendering context prevent us to use any instances variables (with the exception of node) in the template because they are rendering objects. So instead, you'll have to use symbols of these variables. For example, to render the collection `@posts` inside your `PostController`, you need to use `:@posts` inside of the template.
The only places where you can actually used instance variables are into Proc (or lambda) or into custom node (because they are treated as Proc).
```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
node(:read) { |post| post.read_by?(@user) }
```
The same rule applies for view helpers such as `current_user`
After the template is compiled into a hash, Rabl-rails will use a renderer to do the actual output. Actually, only JSON and XML formats are supported.
## Usage

2
Rakefile Normal file → Executable file
View File

@ -14,7 +14,7 @@
#
# RDoc::Task.new(:rdoc) do |rdoc|
# rdoc.rdoc_dir = 'rdoc'
# rdoc.title = 'RablFastJson'
# rdoc.title = 'RablRails'
# rdoc.options << '--line-numbers'
# rdoc.rdoc_files.include('README.rdoc')
# rdoc.rdoc_files.include('lib/**/*.rb')

View File

@ -1,11 +0,0 @@
module RablFastJson
module Helpers
def root_given?(options) #:nodoc:
options[:root].present?
end
def partial_given?(options) #:nodoc:
options[:partial].present?
end
end
end

View File

@ -1,86 +0,0 @@
module RablFastJson
class CompiledTemplate
attr_accessor :source, :data, :root_name, :context
delegate :[], :[]=, :merge!, :to => :source
def initialize
@source = {}
end
def has_root_name?
!@root_name.nil?
end
def render
get_object_from_context
get_assigns_from_context
@object.respond_to?(:each) ? render_collection : render_resource
end
def render_resource(data = nil, source = nil)
data ||= @object
source ||= @source
source.inject({}) { |output, current|
key, value = current
out = case value
when Symbol
data.send(value) # attributes
when Proc
instance_exec data, &value # node
when Array # node with condition
next output if !instance_exec data, &(value.first) #value.first.call(data)
instance_exec data, &(value.last)
when Hash
current_value = value.dup
data_symbol = current_value.delete(:_data)
object = data_symbol.nil? ? data : data_symbol.to_s.start_with?('@') ? @context.instance_variable_get(data_symbol) : data.send(data_symbol)
if key.to_s.start_with?('_') # glue
current_value.each_pair { |k, v|
output[k] = object.send(v)
}
next output
else # child
object.respond_to?(:each) ? render_collection(object, current_value) : render_resource(object, current_value)
end
end
output[key] = out
output
}
end
def render_collection(collection = nil, source = nil)
collection ||= @object
collection.inject([]) { |output, o| output << render_resource(o, source) }
end
def method_missing(name, *args, &block)
@context.respond_to?(name) ? @context.send(name, *args, &block) : super
end
def partial(template_path, options = {})
raise "No object was given to partial" if options[:object].nil?
object = options[:object]
return [] if object.respond_to?(:empty?) && object.empty?
template = Library.instance.get(template_path)
object.respond_to?(:each) ? template.render_collection(object) : template.render_resource(object)
end
protected
def get_object_from_context
@object = @context.instance_variable_get(@data) if @data
end
def get_assigns_from_context
@context.instance_variable_get(:@_assigns).each_pair { |k, v|
instance_variable_set("@#{k}", v) unless k.start_with?('_') || k == @data
}
end
end
end

View File

@ -4,24 +4,28 @@ require 'active_support'
require 'active_support/json'
require 'active_support/core_ext/class/attribute_accessors'
require 'rabl-fast-json/version'
require 'rabl-fast-json/helpers'
require 'rabl-fast-json/template'
require 'rabl-fast-json/compiler'
require 'rabl-fast-json/library'
require 'rabl-fast-json/handler'
require 'rabl-fast-json/railtie'
require 'rabl-rails/version'
require 'rabl-rails/template'
require 'rabl-rails/compiler'
module RablFastJson
require 'rabl-rails/renderer'
require 'rabl-rails/library'
require 'rabl-rails/handler'
require 'rabl-rails/railtie'
module RablRails
extend self
mattr_accessor :cache_templates
@@cache_templates = true
def configure
yield self
end
def cache_templates?
ActionController::Base.perform_caching && @@cache_templates
end

View File

@ -1,11 +1,9 @@
module RablFastJson
module RablRails
#
# Class that will compile RABL source code into a hash
# representing data structure
#
class Compiler
include Helpers
def initialize
@glue_count = 0
end
@ -20,15 +18,6 @@ module RablFastJson
@template
end
#
# Same as compile_source but from a block
#
def compile_block(&block)
@template = {}
instance_eval(&block)
@template
end
#
# Sets the object to be used as the data for the template
# Example:
@ -49,7 +38,7 @@ module RablFastJson
#
def collection(data, options = {})
object(data)
@template.root_name = options[:root] if root_given?(options)
@template.root_name = options[:root] if options[:root]
end
#
@ -80,14 +69,14 @@ module RablFastJson
# child(:@posts, :root => :posts) { attribute :id }
# child(:posts, :partial => 'posts/base')
#
def child(name_or_data, options = {}, &block)
def child(name_or_data, options = {})
data, name = extract_data_and_name(name_or_data)
name = options[:root] if root_given?(options)
if partial_given?(options)
name = options[:root] if options[:root]
if options[:partial]
template = Library.instance.get(options[:partial])
@template[name] = template.merge!(:_data => data)
else
_compile_sub_template(name, data, &block)
elsif block_given?
@template[name] = sub_compile(data) { yield }
end
end
@ -96,11 +85,11 @@ module RablFastJson
# Example:
# glue(:@user) { attribute :name }
#
def glue(data, &block)
def glue(data)
return unless block_given?
name = :"_glue#{@glue_count}"
@glue_count += 1
_compile_sub_template(name, data, &block)
@template[name] = sub_compile(data) { yield }
end
#
@ -158,11 +147,14 @@ module RablFastJson
name_or_data
end
end
def _compile_sub_template(name, data, &block) #:nodoc:
compiler = Compiler.new
template = compiler.compile_block(&block)
@template[name] = template.merge!(:_data => data)
def sub_compile(data)
return {} unless block_given?
old_template, @template = @template, {}
yield
@template.merge!(:_data => data)
ensure
@template = old_template
end
end
end

View File

@ -1,12 +1,12 @@
module RablFastJson
module RablRails
module Handlers
class Rabl
cattr_accessor :default_format
self.default_format = 'application/json'
def self.call(template)
def self.call(template)
%{
RablFastJson::Library.instance.
RablRails::Library.instance.
get_rendered_template(#{template.source.inspect}, self)
}
end

View File

@ -1,27 +1,25 @@
require 'singleton'
module RablFastJson
module RablRails
class Library
include Singleton
attr_accessor :view_renderer
def initialize
@cached_templates = {}
end
def get_rendered_template(source, context)
path = context.instance_variable_get(:@virtual_path)
@view_renderer = context.instance_variable_get(:@view_renderer)
@lookup_context = context.lookup_context
compiled_template = get_compiled_template(path, source)
compiled_template.context = context
body = compiled_template.render
ActiveSupport::JSON.encode(compiled_template.root_name ? { compiled_template.root_name => body } : body)
format = context.params[:format] || 'json'
Renderers.const_get(format.upcase!).new(context).render(compiled_template)
end
def get_compiled_template(path, source)
if path && RablFastJson.cache_templates?
if path && RablRails.cache_templates?
@cached_templates[path] ||= Compiler.new.compile_source(source)
@cached_templates[path].dup
else
@ -31,8 +29,8 @@ module RablFastJson
def get(path)
template = @cached_templates[path]
return template unless template.nil?
t = @view_renderer.lookup_context.find_template(path, [], false)
return template if template
t = @lookup_context.find_template(path, [], false)
get_compiled_template(path, t.source)
end
end

View File

@ -1,8 +1,8 @@
module RablFastJson
module RablRails
class Railtie < Rails::Railtie
initializer "rabl.initialize" do |app|
ActiveSupport.on_load(:action_view) do
ActionView::Template.register_template_handler :rabl, RablFastJson::Handlers::Rabl
ActionView::Template.register_template_handler :rabl, RablRails::Handlers::Rabl
end
end
end

View File

@ -0,0 +1,2 @@
require 'rabl-rails/renderers/base'
require 'rabl-rails/renderers/json'

View File

@ -0,0 +1,114 @@
module RablRails
module Renderers
class PartialError < StandardError; end
class Base
def initialize(context) # :nodoc:
@_context = context
setup_render_context
end
#
# Render a template.
# Uses the compiled template source to get a hash with the actual
# data and then format the result according to the `format_result`
# method defined by the renderer.
#
def render(template)
collection_or_resource = @_context.instance_variable_get(template.data) if template.data
output_hash = collection_or_resource.respond_to?(:each) ? render_collection(collection_or_resource, template.source) :
render_resource(collection_or_resource, template.source)
output_hash = { template.root_name => output_hash } if template.root_name
format_output(output_hash)
end
#
# Format a hash into the desired output.
# Renderer subclasses must implement this method
#
def format_output(hash)
raise "Muse be implemented by renderer"
end
protected
#
# Render a single resource as a hash, according to the compiled
# template source passed.
#
def render_resource(data, source)
source.inject({}) { |output, current|
key, value = current
out = case value
when Symbol
data.send(value) # attributes
when Proc
instance_exec data, &value # node
when Array # node with condition
next output if !instance_exec data, &(value.first)
instance_exec data, &(value.last)
when Hash
current_value = value.dup
data_symbol = current_value.delete(:_data)
object = data_symbol.nil? ? data : data_symbol.to_s.start_with?('@') ? @_context.instance_variable_get(data_symbol) : data.send(data_symbol)
if key.to_s.start_with?('_') # glue
current_value.each_pair { |k, v|
output[k] = object.send(v)
}
next output
else # child
object.respond_to?(:each) ? render_collection(object, current_value) : render_resource(object, current_value)
end
end
output[key] = out
output
}
end
#
# Call the render_resource mtehod on each object of the collection
# and return an array of the returned values.
#
def render_collection(collection, source)
collection.map { |o| render_resource(o, source) }
end
#
# Allow to use partial inside of node blocks (they are evaluated at)
# rendering time.
#
def partial(template_path, options = {})
raise PartialError.new("No object was given to partial #{template_path}") unless options[:object]
object = options[:object]
return [] if object.respond_to?(:empty?) && object.empty?
template = Library.instance.get(template_path)
object.respond_to?(:each) ? render_collection(object, template.source) : render_resource(object, template.source)
end
#
# If a method is called inside a 'node' property or a 'if' lambda
# it will be passed to context if it exists or treated as a standard
# missing method.
#
def method_missing(name, *args, &block)
@_context.respond_to?(name) ? @_context.send(name, *args, &block) : super
end
#
# Copy assigns from controller's context into this
# renderer context to include instances variables when
# evaluating 'node' properties.
#
def setup_render_context
@_context.instance_variable_get(:@_assigns).each_pair { |k, v|
instance_variable_set("@#{k}", v) unless k.start_with?('_') || k == @data
}
end
end
end
end

View File

@ -0,0 +1,9 @@
module RablRails
module Renderers
class JSON < Base
def format_output(hash)
ActiveSupport::JSON.encode(hash)
end
end
end
end

View File

@ -0,0 +1,11 @@
module RablRails
class CompiledTemplate
attr_accessor :source, :data, :root_name
delegate :[], :[]=, :merge!, :to => :source
def initialize
@source = {}
end
end
end

View File

@ -1,3 +1,3 @@
module RablFastJson
module RablRails
VERSION = '0.1.0'
end

View File

@ -1,4 +1,4 @@
# desc "Explaining what the task does"
# task :rabl-fast-json do
# task :rabl-rails do
# # Task goes here
# end

View File

@ -1,17 +1,17 @@
$:.push File.expand_path("../lib", __FILE__)
# Maintain your gem's version:
require "rabl-fast-json/version"
require "rabl-rails/version"
# Describe your gem and declare its dependencies:
Gem::Specification.new do |s|
s.name = "rabl-fast-json"
s.version = RablFastJson::VERSION
s.name = "rabl-rails"
s.version = RablRails::VERSION
s.authors = ["TODO: Your name"]
s.email = ["TODO: Your email"]
s.homepage = "TODO"
s.summary = "TODO: Summary of RablFastJson."
s.description = "TODO: Description of RablFastJson."
s.summary = "TODO: Summary of RablRails."
s.description = "TODO: Description of RablRails."
s.files = Dir["{app,config,db,lib}/**/*"] + ["MIT-LICENSE", "Rakefile", "README.md"]
s.test_files = Dir["test/**/*"]

View File

@ -3,32 +3,32 @@ require 'test_helper'
class CacheTemplatesTest < ActiveSupport::TestCase
setup do
RablFastJson::Library.reset_instance
@library = RablFastJson::Library.instance
RablRails::Library.reset_instance
@library = RablRails::Library.instance
RablRails.cache_templates = true
end
test "cache templates if perform_caching is active and cache_templates is enabled" do
RablFastJson.cache_templates = true
ActionController::Base.stub(:perform_caching).and_return(true)
ActionController::Base.stub(:perform_caching).and_return(true)
@library.get_compiled_template('some/path', "")
t = @library.get_compiled_template('some/path', "attribute :id")
assert_equal({}, t.source)
end
test "cached templates should not be modifiable in place" do
RablFastJson.cache_templates = true
ActionController::Base.stub(:perform_caching).and_return(true)
t = @library.get_compiled_template('some/path', "")
assert_nil t.context
t.context = "foobar"
assert_nil @library.get_compiled_template('some/path', "").context
@library.get_compiled_template('some/path', "")
t = @library.get_compiled_template('some/path', "attribute :id")
assert_equal({}, t.source)
end
test "don't cache templates cache_templates is enabled but perform_caching is not active" do
RablFastJson.cache_templates = true
ActionController::Base.stub(:perform_caching).and_return(false)
refute_equal @library.get_compiled_template('some/path', ""), @library.get_compiled_template('some/path', "")
ActionController::Base.stub(:perform_caching).and_return(false)
@library.get_compiled_template('some/path', "")
t = @library.get_compiled_template('some/path', "attribute :id")
assert_equal({ :id => :id }, t.source)
end
end

View File

@ -4,13 +4,47 @@ class CompilerTest < ActiveSupport::TestCase
setup do
@user = User.new
@compiler = RablFastJson::Compiler.new
@compiler = RablRails::Compiler.new
end
test "compiler return a compiled template" do
assert_instance_of RablFastJson::CompiledTemplate, @compiler.compile_source("")
assert_instance_of RablRails::CompiledTemplate, @compiler.compile_source("")
end
test "object set data for the template" do
t = @compiler.compile_source(%{ object :@user })
assert_equal :@user, t.data
assert_equal({}, t.source)
end
test "object property can define root name" do
t = @compiler.compile_source(%{ object :@user => :author })
assert_equal :@user, t.data
assert_equal :author, t.root_name
assert_equal({}, t.source)
end
test "collection set the data for the template" do
t = @compiler.compile_source(%{ collection :@user })
assert_equal :@user, t.data
assert_equal({}, t.source)
end
test "collection property can define root name" do
t = @compiler.compile_source(%{ collection :@user => :users })
assert_equal :@user, t.data
assert_equal :users, t.root_name
assert_equal({}, t.source)
end
test "collection property can define root name via options" do
t = @compiler.compile_source(%{ collection :@user, :root => :users })
assert_equal :@user, t.data
assert_equal :users, t.root_name
end
# Compilation
test "simple attributes are compiled to hash" do
t = @compiler.compile_source(%{ attributes :id, :name })
assert_equal({ :id => :id, :name => :name}, t.source)
@ -57,11 +91,10 @@ class CompilerTest < ActiveSupport::TestCase
end
test "child with succint partial notation" do
@view_renderer = mock()
@view_renderer.stub_chain(:lookup_context, :find_template).with('users/base', [], false).and_return(
mock(:source => %{ attribute :id }))
RablFastJson::Library.reset_instance
RablFastJson::Library.instance.view_renderer = @view_renderer
mock_template = RablRails::CompiledTemplate.new
mock_template.source = { :id => :id }
RablRails::Library.reset_instance
RablRails::Library.instance.stub(:get).with('users/base').and_return(mock_template)
t = @compiler.compile_source(%{child(:user, :partial => 'users/base') })
assert_equal( {:user => { :_data => :user, :id => :id } }, t.source)
@ -84,42 +117,10 @@ class CompilerTest < ActiveSupport::TestCase
}, t.source)
end
test "object set data for the template" do
t = @compiler.compile_source(%{ object :@user })
assert_equal :@user, t.data
assert_equal({}, t.source)
end
test "object property can define root name" do
t = @compiler.compile_source(%{ object :@user => :author })
assert_equal :@user, t.data
assert_equal :author, t.root_name
assert_equal({}, t.source)
end
test "collection set the data for the template" do
t = @compiler.compile_source(%{ collection :@user })
assert_equal :@user, t.data
assert_equal({}, t.source)
end
test "collection property can define root name" do
t = @compiler.compile_source(%{ collection :@user => :users })
assert_equal :@user, t.data
assert_equal :users, t.root_name
assert_equal({}, t.source)
end
test "collection property can define root name via options" do
t = @compiler.compile_source(%{ collection :@user, :root => :users })
assert_equal :@user, t.data
assert_equal :users, t.root_name
end
test "extends use other template source as itself" do
template = mock('template', :source => { :id => :id })
RablFastJson::Library.reset_instance
RablFastJson::Library.instance.stub(:get).with('users/base').and_return(template)
RablRails::Library.reset_instance
RablRails::Library.instance.stub(:get).with('users/base').and_return(template)
t = @compiler.compile_source(%{ extends 'users/base' })
assert_equal({ :id => :id }, t.source)
end

View File

@ -15,21 +15,17 @@ class DeepNestingTest < ActiveSupport::TestCase
end
setup do
RablFastJson::Library.reset_instance
RablRails::Library.reset_instance
@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)
@view_renderer = mock()
@view_renderer.stub_chain(:lookup_context, :find_template).with('comments/show', [], false).and_return(
mock(:source => %{ object :@comment\n attribute :content }))
@context = Context.new
@context.stub(:instance_variable_get).with(:@user).and_return(@user)
@context.stub(:instance_variable_get).with(:@view_renderer).and_return(@view_renderer)
@context.stub(:instance_variable_get).with(:@virtual_path).and_return('users/show')
@context.stub(:instance_variable_get).with(:@_assigns).and_return({})
@context.stub(:lookup_context).and_return(mock(:find_template => mock(:source => %{ object :@comment\n attribute :content })))
end
test "compile and render deep nesting template" do
@ -54,7 +50,7 @@ class DeepNestingTest < ActiveSupport::TestCase
{ :content => 'second' }
]
}]
}), RablFastJson::Library.instance.get_rendered_template(source, @context))
}), RablRails::Library.instance.get_rendered_template(source, @context))
end
end

View File

@ -2,19 +2,19 @@ require 'test_helper'
class NonRestfulResponseTest < ActiveSupport::TestCase
setup do
RablFastJson::Library.reset_instance
RablRails::Library.reset_instance
@user = User.new(1, 'foo', 'male')
@user.stub_chain(:posts, :count).and_return(10)
@user.stub(:respond_to?).with(:each).and_return(false)
@context = Context.new
@context.stub(:instance_variable_get).with(:@user).and_return(@user)
@context.stub(:instance_variable_get).with(:@view_renderer).and_return(mock())
@context.stub(:instance_variable_get).with(:@virtual_path).and_return('user/show')
@context.stub(:instance_variable_get).with(:@_assigns).and_return({'user' => @user})
@context.stub(:lookup_context)
end
test "compile and render non restful resource" do
source = %{
object false
@ -23,13 +23,13 @@ class NonRestfulResponseTest < ActiveSupport::TestCase
attributes :id, :name
end
}
assert_equal(ActiveSupport::JSON.encode({
:post_count => 10,
:user => {
:id => 1,
:name => 'foo'
}
}), RablFastJson::Library.instance.get_rendered_template(source, @context))
}), RablRails::Library.instance.get_rendered_template(source, @context))
end
end

View File

@ -1,72 +1,88 @@
require 'test_helper'
class TestCompiledTemplate < ActiveSupport::TestCase
class TestJsonRenderer < ActiveSupport::TestCase
setup do
@context = Context.new
@data = User.new(1, 'foobar', 'male')
@data.stub(:respond_to?).with(:each).and_return(false)
@context = Context.new
@context.stub(:instance_variable_get).with(:@data).and_return(@data)
@context.stub(:instance_variable_get).with(:@_assigns).and_return({})
@template = RablFastJson::CompiledTemplate.new
@template.context = @context
@template = RablRails::CompiledTemplate.new
@template.data = :@data
end
def render_json_output
RablRails::Renderers::JSON.new(@context).render(@template).to_s
end
test "render object wth empty template" do
@template.source = {}
assert_equal({}, @template.render)
assert_equal %q({}), render_json_output
end
test "render collection with empty template" do
@context.stub(:instance_variable_get).with(:@data).and_return([@data])
@template.source = {}
assert_equal([{}], @template.render)
assert_equal %q([{}]), render_json_output
end
test "render single object attributes" do
@template.source = { :id => :id, :name => :name }
assert_equal({ :id => 1, :name => 'foobar'}, @template.render)
assert_equal %q({"id":1,"name":"foobar"}), render_json_output
end
test "render object as a child" do
test "render child with object association" do
@data.stub(:address).and_return(mock(:city => 'Paris'))
@template.source = { :address => { :_data => :address, :city => :city } }
assert_equal %q({"address":{"city":"Paris"}}), render_json_output
end
test "render child with arbitrary data source" do
@template.source = { :author => { :_data => :@data, :name => :name } }
assert_equal({ :author => { :name => 'foobar' } }, @template.render)
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({ :name => 'foobar' }, @template.render)
assert_equal %q({"name":"foobar"}), render_json_output
end
test "render collection with attributes" do
@data = [User.new(1, 'foo', 'male'), User.new(2, 'bar', 'female')]
@context.stub(:instance_variable_get).with(:@data).and_return(@data)
@template.source = { :uid => :id, :name => :name, :gender => :sex }
assert_equal([
{ :uid => 1, :name => 'foo', :gender => 'male'},
{ :uid => 2, :name => 'bar', :gender => 'female'}
], @template.render)
assert_equal %q([{"uid":1,"name":"foo","gender":"male"},{"uid":2,"name":"bar","gender":"female"}]), render_json_output
end
test "render node property" do
proc = lambda { |object| object.sex }
@template.source = { :sex => proc }
assert_equal({ :sex => 'male' }, @template.render)
proc = lambda { |object| object.name }
@template.source = { :name => proc }
assert_equal %q({"name":"foobar"}), render_json_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({ :name => 'foobar' }, @template.render)
assert_equal %q({"name":"foobar"}), render_json_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({}, @template.render)
assert_equal %q({}), render_json_output
end
test "node with context method call" do
@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({"name":"marty"}), render_json_output
end
test "partial should be evaluated at rendering time" do
@ -75,28 +91,28 @@ class TestCompiledTemplate < ActiveSupport::TestCase
@data.stub(:respond_to?).with(:empty?).and_return(false)
# Stub Library#get
t = RablFastJson::CompiledTemplate.new
t.source, t.context = { :name => :name }, @context
RablFastJson::Library.reset_instance
RablFastJson::Library.instance.should_receive(:get).with('users/base').and_return(t)
t = RablRails::CompiledTemplate.new
t.source = { :name => :name }
RablRails::Library.reset_instance
RablRails::Library.instance.should_receive(:get).with('users/base').and_return(t)
@template.data = false
@template.source = { :user => ->(s) { partial('users/base', :object => @user) } }
assert_equal({ :user => { :name => 'foobar' } }, @template.render)
assert_equal %q({"user":{"name":"foobar"}}), render_json_output
end
test "partial with nil values should raise an error" do
test "partial with no values should raise an error" do
@template.data = false
@template.source = { :user => ->(s) { partial('users/base') } }
assert_raises(RuntimeError) { @template.render }
assert_raises(RablRails::Renderers::PartialError) { render_json_output }
end
test "partial with empty values should not raise an error" do
@template.data = false
@template.source = { :users => ->(s) { partial('users/base', :object => []) } }
assert_equal({ :users => [] }, @template.render)
assert_equal %q({"users":[]}), render_json_output
end
end

View File

@ -21,7 +21,7 @@ class <<Singleton
alias_method_chain :included, :reset
end
require 'rabl-fast-json'
require 'rabl-rails'
module ActiveSupport
class TestCase
@ -35,16 +35,11 @@ class Context
def initialize
@_assigns = {}
@virtual_path = '/users'
end
def set_assign(key, value)
@_assigns[key] = value
end
def get_assign(key)
@_assigns[key]
def params
{}
end
end
User = Struct.new(:id, :name, :sex)