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" 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 # Bundler will treat runtime dependencies like base dependencies, and
# development dependencies will be added by default to the :development group. # development dependencies will be added by default to the :development group.
gemspec gemspec

View File

@ -1,7 +1,7 @@
PATH PATH
remote: . remote: .
specs: specs:
rabl-fast-json (0.1.0) rabl-rails (0.1.0)
activesupport (~> 3.2.1) activesupport (~> 3.2.1)
GEM GEM
@ -61,7 +61,7 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
actionpack (~> 3.2.1) actionpack (~> 3.2.1)
rabl-fast-json! rabl-rails!
railties (~> 3.2.1) railties (~> 3.2.1)
rspec-mocks rspec-mocks
sqlite3 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::Task.new(:rdoc) do |rdoc|
# rdoc.rdoc_dir = 'rdoc' # rdoc.rdoc_dir = 'rdoc'
# rdoc.title = 'RablFastJson' # rdoc.title = 'RablRails'
# rdoc.options << '--line-numbers' # rdoc.options << '--line-numbers'
# rdoc.rdoc_files.include('README.rdoc') # rdoc.rdoc_files.include('README.rdoc')
# rdoc.rdoc_files.include('lib/**/*.rb') # 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,15 +4,19 @@ require 'active_support'
require 'active_support/json' require 'active_support/json'
require 'active_support/core_ext/class/attribute_accessors' require 'active_support/core_ext/class/attribute_accessors'
require 'rabl-fast-json/version' require 'rabl-rails/version'
require 'rabl-fast-json/helpers' require 'rabl-rails/template'
require 'rabl-fast-json/template' require 'rabl-rails/compiler'
require 'rabl-fast-json/compiler'
require 'rabl-fast-json/library'
require 'rabl-fast-json/handler'
require 'rabl-fast-json/railtie'
module RablFastJson require 'rabl-rails/renderer'
require 'rabl-rails/library'
require 'rabl-rails/handler'
require 'rabl-rails/railtie'
module RablRails
extend self extend self
mattr_accessor :cache_templates mattr_accessor :cache_templates

View File

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

View File

@ -1,4 +1,4 @@
module RablFastJson module RablRails
module Handlers module Handlers
class Rabl class Rabl
cattr_accessor :default_format cattr_accessor :default_format
@ -6,7 +6,7 @@ module RablFastJson
def self.call(template) def self.call(template)
%{ %{
RablFastJson::Library.instance. RablRails::Library.instance.
get_rendered_template(#{template.source.inspect}, self) get_rendered_template(#{template.source.inspect}, self)
} }
end end

View File

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

View File

@ -1,8 +1,8 @@
module RablFastJson module RablRails
class Railtie < Rails::Railtie class Railtie < Rails::Railtie
initializer "rabl.initialize" do |app| initializer "rabl.initialize" do |app|
ActiveSupport.on_load(:action_view) do 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 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' VERSION = '0.1.0'
end end

View File

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

View File

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

View File

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

View File

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

View File

@ -15,21 +15,17 @@ class DeepNestingTest < ActiveSupport::TestCase
end end
setup do setup do
RablFastJson::Library.reset_instance RablRails::Library.reset_instance
@post = Post.new(42, 'I rock !') @post = Post.new(42, 'I rock !')
@user = User.new(1, 'foobar', 'male') @user = User.new(1, 'foobar', 'male')
@user.stub(:posts).and_return([@post]) @user.stub(:posts).and_return([@post])
@user.stub(:respond_to?).with(:each).and_return(false) @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 = Context.new
@context.stub(:instance_variable_get).with(:@user).and_return(@user) @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(:@virtual_path).and_return('users/show')
@context.stub(:instance_variable_get).with(:@_assigns).and_return({}) @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 end
test "compile and render deep nesting template" do test "compile and render deep nesting template" do
@ -54,7 +50,7 @@ class DeepNestingTest < ActiveSupport::TestCase
{ :content => 'second' } { :content => 'second' }
] ]
}] }]
}), RablFastJson::Library.instance.get_rendered_template(source, @context)) }), RablRails::Library.instance.get_rendered_template(source, @context))
end end
end end

View File

@ -2,7 +2,7 @@ require 'test_helper'
class NonRestfulResponseTest < ActiveSupport::TestCase class NonRestfulResponseTest < ActiveSupport::TestCase
setup do setup do
RablFastJson::Library.reset_instance RablRails::Library.reset_instance
@user = User.new(1, 'foo', 'male') @user = User.new(1, 'foo', 'male')
@user.stub_chain(:posts, :count).and_return(10) @user.stub_chain(:posts, :count).and_return(10)
@ -10,9 +10,9 @@ class NonRestfulResponseTest < ActiveSupport::TestCase
@context = Context.new @context = Context.new
@context.stub(:instance_variable_get).with(:@user).and_return(@user) @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(:@virtual_path).and_return('user/show')
@context.stub(:instance_variable_get).with(:@_assigns).and_return({'user' => @user}) @context.stub(:instance_variable_get).with(:@_assigns).and_return({'user' => @user})
@context.stub(:lookup_context)
end end
test "compile and render non restful resource" do test "compile and render non restful resource" do
@ -30,6 +30,6 @@ class NonRestfulResponseTest < ActiveSupport::TestCase
:id => 1, :id => 1,
:name => 'foo' :name => 'foo'
} }
}), RablFastJson::Library.instance.get_rendered_template(source, @context)) }), RablRails::Library.instance.get_rendered_template(source, @context))
end end
end end

View File

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

View File

@ -21,7 +21,7 @@ class <<Singleton
alias_method_chain :included, :reset alias_method_chain :included, :reset
end end
require 'rabl-fast-json' require 'rabl-rails'
module ActiveSupport module ActiveSupport
class TestCase class TestCase
@ -35,15 +35,10 @@ class Context
def initialize def initialize
@_assigns = {} @_assigns = {}
@virtual_path = '/users'
end end
def set_assign(key, value) def params
@_assigns[key] = value {}
end
def get_assign(key)
@_assigns[key]
end end
end end