Compare commits

..

3 Commits

Author SHA1 Message Date
ccocchi 4dae04896d First implementation of fragment cache 2012-05-28 10:56:53 +02:00
ccocchi 4cf10fb8f6 Merge branch 'master' into fragment-cache 2012-05-04 16:05:11 +02:00
ccocchi 79d6bb9c81 Basic fragment class and renderer code. Need to be tested 2012-04-16 00:38:02 +02:00
34 changed files with 271 additions and 1115 deletions

17
.gitignore vendored
View File

@ -1,10 +1,7 @@
## General
log
doc
rdoc
## Bundler
.bundle
pkg
Gemfile.lock
.bundle/
log/*.log
pkg/
test/dummy/db/*.sqlite3
test/dummy/log/*.log
test/dummy/tmp/
test/dummy/.sass-cache

View File

@ -1,5 +0,0 @@
language: ruby
rvm:
- 1.9.3
- jruby-19mode
- rbx-19mode

View File

@ -1,45 +0,0 @@
# CHANGELOG
## 0.3.1
* Add full template stack support to `glue` (fnordfish)
* Allow format to be a symbol (lloydmeta)
## 0.3.0
* Travis integration
* Add test for keywords used as variable names
* Add PList renderer
* Remove location header from post responses in responder
* Fix bug with incomplete template prefixing
## 0.2.2
* Add condition blocks
## 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
* Warning message printed on logger when JSON engine fail to load
## 0.1.1
* Add CHANGELOG
* 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

22
Gemfile
View File

@ -1,18 +1,18 @@
source "http://rubygems.org"
# 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
gem 'plist'
# jquery-rails is used by the dummy application
platforms :ruby do
gem 'oj'
gem 'libxml-ruby'
end
gem 'rspec-mocks'
platforms :jruby do
gem 'nokogiri'
end
# Declare any dependencies that are still in development here instead of in
# your gemspec. These might include edge Rails or gems from your path or
# Git. Remember to move these dependencies to your gemspec before releasing
# your gem to rubygems.org.
group :test do
gem 'rspec-mocks'
end
# To use debugger
# gem 'ruby-debug19', :require => 'ruby-debug'

67
Gemfile.lock Normal file
View File

@ -0,0 +1,67 @@
PATH
remote: .
specs:
rabl-rails (0.1.0)
activesupport (~> 3.2.1)
GEM
remote: http://rubygems.org/
specs:
actionpack (3.2.1)
activemodel (= 3.2.1)
activesupport (= 3.2.1)
builder (~> 3.0.0)
erubis (~> 2.7.0)
journey (~> 1.0.1)
rack (~> 1.4.0)
rack-cache (~> 1.1)
rack-test (~> 0.6.1)
sprockets (~> 2.1.2)
activemodel (3.2.1)
activesupport (= 3.2.1)
builder (~> 3.0.0)
activesupport (3.2.1)
i18n (~> 0.6)
multi_json (~> 1.0)
builder (3.0.0)
erubis (2.7.0)
hike (1.2.1)
i18n (0.6.0)
journey (1.0.3)
json (1.6.5)
multi_json (1.1.0)
rack (1.4.1)
rack-cache (1.1)
rack (>= 0.4)
rack-ssl (1.3.2)
rack
rack-test (0.6.1)
rack (>= 1.0)
railties (3.2.1)
actionpack (= 3.2.1)
activesupport (= 3.2.1)
rack-ssl (~> 1.3.2)
rake (>= 0.8.7)
rdoc (~> 3.4)
thor (~> 0.14.6)
rake (0.9.2.2)
rdoc (3.12)
json (~> 1.4)
rspec-mocks (2.8.0)
sprockets (2.1.2)
hike (~> 1.2)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
sqlite3 (1.3.5)
thor (0.14.6)
tilt (1.3.3)
PLATFORMS
ruby
DEPENDENCIES
actionpack (~> 3.2.1)
rabl-rails!
railties (~> 3.2.1)
rspec-mocks
sqlite3

View File

@ -1,4 +1,4 @@
Copyright 2012 Christopher Cocchi-Perrier
Copyright 2012 YOURNAME
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the

244
README.md
View File

@ -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 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.
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 only targets **Rails 3+ application** and is compatible with mri 1.9.3, jRuby and rubinius.
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
@ -17,24 +17,24 @@ gem install rabl-rails
or add directly to your `Gemfile`
```
gem 'rabl-rails'
gem 'rabl'
```
And that's it !
## Overview
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,
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
respond_to :html, :json, :xml
def index
@posts = Post.order('created_at DESC')
respond_with(@posts)
end
end
```
@ -64,229 +64,21 @@ That's a basic overview but there is a lot more to see such as partials, inherit
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 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) }
# 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.
## Configuration
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 = :oj
# config.xml_engine = 'LibXML'
# config.use_custom_responder = false
# config.default_responder_template = 'show'
end
```
## Usage
### Data declaration
To declare data to use in the template, you can use either `object` or `collection` with the symbol name or your data.
```ruby
# app/views/users/show.json.rabl
object :@user
# app/views/users/index.json.rabl
collection :@users
```
You can specify root label for the collection using hash or `:root` option
```ruby
collection :@posts, root: :articles
#is equivalent to
collection :@posts => :articles
# => { "articles" : [{...}, {...}] }
```
There are rares cases when the template doesn't map directly to any object. In these cases, you can set data to false or skip data declaration altogether.
```ruby
object false
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.
```ruby
attributes :id, :title, :to_s
```
You can aliases these attributes in your response
```ruby
attributes title: :foo, to_s: :bar
# => { "foo" : <title value>, "bar" : <to_s value> }
```
### Child nodes
You can include nested information from data associated with the parent model. You can also alias these associations.
For example if you have a `Post` model that belongs to a `User`
```ruby
object :@post
child(user: :author) do
attributes :name
end
# => { "post" : { "author" : { "name" : "John D." } } }
```
You can also use arbitrary data source with child nodes
```ruby
child(:@users) do
attributes :id, :name
end
```
### Custom nodes
You can create custom node in your response, based on the result of the given block
```ruby
object :@user
node(:full_name) { |u| u.first_name + " " + u.last_name }
# => { "user" : { "full_name" : "John Doe" } }
```
You can add the node only if a condition is true
```ruby
node(:email, if: -> { |u| u.valid_email? }) do |u|
u.email
end
```
Nodes are evaluated at the rendering time, so you can use any instance variables or view helpers inside them
```ruby
node(:url) { |post| post_url(post) }
```
Custom nodes are really usefull to create flexible representations of your resources.
### Extends & Partials
Often objects have a basic representation that is shared accross different views and enriched according to it. To avoid code redundancy you can extend your template from any other RABL template.
```ruby
# app/views/users/base.json.rabl
attributes :id, :name
# app/views/users/private.json.rabl
extends 'users/base'
attributes :super_secret_attribute
```
You can also extends template in child nodes using `partial` option (this is the same as using `extends` in the child block)
```ruby
collection @posts
attribute :title
child(:user, partial: 'users/base')
```
Partials can also be used inside custom nodes. When using partial this way, you MUST declare the object associated to the partial
```ruby
node(:location) do |user|
{ city: user.city, address: partial('users/address', object: m.address) }
end
```
### Nesting
Rabl allow you to define easily your templates, even with hierarchy of 2 or 3 levels. Let's suppose your have a `thread` model that has many `posts` and that each post has many `comments`. We can display a full thread in a few lines
```ruby
object :@thread
attribute :caption
child :posts do
attribute :title
child :comments do
extends 'comments/base'
end
end
```
### 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:
```ruby
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)
### 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.7.6 and rabl-rails 0.3.0
Overall, Rabl-rails is **20% faster and use 10% less memory**, even **twice faster** when using extends.
You can see full tests on test application repository.
## Authors and contributors
* [Christopher Cocchi-Perrier](http://github.com/ccocchi) - Creator of the project
Want to add another format to Rabl-rails ? Checkout [JSON renderer](http://github.com/ccocchi/rabl-rails/blob/master/lib/rabl-rails/renderers/json.rb) for reference
Want to make another change ? Just fork and contribute, any help is very much appreciated. If you found a bug, you can report it via the Github issues.
## Original idea
* [RABL](http://github.com/nesquena/rabl) Standart RABL gem. I used it a lot but I needed to improve my API response time, and
since most of the time was spent in view rendering, I decided to implement a faster rabl gem.
## Copyright
Copyright © 2011-2012 Christopher Cocchi-Perrier. See [MIT-LICENSE](http://github.com/ccocchi/rabl-rails/blob/master/MIT-LICENSE) for details.
## Usage

View File

@ -1,11 +1,12 @@
require 'rails/railtie'
require 'active_support'
require 'active_support/json'
require 'active_support/core_ext/class/attribute_accessors'
require 'rabl-rails/version'
require 'rabl-rails/template'
require 'rabl-rails/condition'
require 'rabl-rails/fragment'
require 'rabl-rails/compiler'
require 'rabl-rails/renderer'
@ -14,74 +15,19 @@ require 'rabl-rails/library'
require 'rabl-rails/handler'
require 'rabl-rails/railtie'
require 'multi_json'
module RablRails
extend Renderer
autoload :Responder, 'rabl-rails/responder'
extend self
mattr_accessor :cache_templates
@@cache_templates = true
mattr_accessor :include_json_root
@@include_json_root = true
mattr_accessor :use_custom_responder
@@use_custom_responder = false
mattr_accessor :responder_default_template
@@responder_default_template = 'show'
mattr_reader :plist_engine
@@plist_engine = nil
mattr_accessor :include_plist_root
@@include_plist_root = nil
def self.configure
def configure
yield self
ActionController::Base.responder = Responder if self.use_custom_responder
end
def self.json_engine=(name)
MultiJson.engine = name
rescue LoadError
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.plist_engine=(name)
raise "Your plist engine does not respond to #dump" unless name.respond_to?(:dump)
@@plist_engine = name
end
def self.cache_templates?
def cache_templates?
ActionController::Base.perform_caching && @@cache_templates
end
def self.load_default_engines!
self.json_engine = MultiJson.default_engine
self.plist_engine = Plist::Emit if defined?(Plist)
if defined?(LibXML)
self.xml_engine = 'LibXML'
elsif defined?(Nokogiri)
self.xml_engine = 'Nokogiri'
end
end
end

View File

@ -5,7 +5,8 @@ module RablRails
#
class Compiler
def initialize
@i = 0
@glue_count = 0
@cache_count = 0
end
#
@ -25,13 +26,20 @@ module RablRails
# object :@user, :root => :author
#
def object(data, options = {})
@template.data, @template.root_name = extract_data_and_name(data)
@template.root_name = options[:root] if options.has_key? :root
data, name = extract_data_and_name(data)
@template.data = data
@template.root_name = options[:root] || name
end
alias_method :collection, :object
def root(name)
@template.root_name = name
#
# Sets a collection to be used as data for the template
# Example:
# collection :@users
# collection :@users, :root => :morons
#
def collection(data, options = {})
object(data)
@template.root_name = options[:root] if options[:root]
end
#
@ -64,10 +72,10 @@ module RablRails
#
def child(name_or_data, options = {})
data, name = extract_data_and_name(name_or_data)
name = options[:root] if options.has_key? :root
name = options[:root] if options[:root]
if options[:partial]
template = Library.instance.compile_template_from_path(options[:partial])
@template[name] = template.merge(:_data => data)
template = Library.instance.get(options[:partial])
@template[name] = template.merge!(:_data => data)
elsif block_given?
@template[name] = sub_compile(data) { yield }
end
@ -80,8 +88,8 @@ module RablRails
#
def glue(data)
return unless block_given?
name = :"_glue#{@i}"
@i += 1
name = :"_glue#{@glue_count}"
@glue_count += 1
@template[name] = sub_compile(data) { yield }
end
@ -96,7 +104,7 @@ module RablRails
def node(name, options = {}, &block)
condition = options[:if]
if condition
if condition.present?
if condition.is_a?(Proc)
@template[name] = [condition, block]
else
@ -114,22 +122,19 @@ module RablRails
# extends 'users/base'
#
def extends(path)
t = Library.instance.compile_template_from_path(path)
t = Library.instance.get(path)
@template.merge!(t.source)
end
#
# Provide a conditionnal block
# cache key: ->(resource) { |resource| resource.cache_key }, expires_in: 1800
#
# condition(->(u) { u.is_a?(Admin) }) do
# attributes :secret
# end
#
def condition(proc)
def cache(options = {}, &block)
return unless block_given?
name = :"_if#{@i}"
@i += 1
@template[name] = Condition.new(proc, sub_compile(nil) { yield })
key = options.delete(:key)
source = sub_compile { yield }
@template[:"_cache#{@cache_count}"] = Fragment.new(key, source, options)
@cache_count += 1
end
protected
@ -143,16 +148,19 @@ module RablRails
def extract_data_and_name(name_or_data)
case name_or_data
when Symbol
str = name_or_data.to_s
str.start_with?('@') ? [name_or_data, str[1..-1]] : [name_or_data, name_or_data]
if name_or_data.to_s.start_with?('@')
[name_or_data, nil]
else
[name_or_data, name_or_data]
end
when Hash
name_or_data.first
else
name_or_data
end
end
def sub_compile(data)
def sub_compile(data = nil)
return {} unless block_given?
old_template, @template = @template, {}
yield
@ -161,4 +169,4 @@ module RablRails
@template = old_template
end
end
end
end

View File

@ -1,10 +0,0 @@
module RablRails
class Condition
attr_reader :proc, :source
def initialize(proc, source)
@proc = proc
@source = source
end
end
end

View File

@ -0,0 +1,42 @@
require 'active_support/cache'
module RablRails
class Fragment
attr_reader :compiled_source, :options
def initialize(key, source, options = {})
@key = key
@compiled_source = source
@options = options
end
def expand_cache_key(data)
if @key
@key.respond_to?(:call) ? @key.call(data) : @key
else
ActiveSupport::Cache.expand_cache_key(data, 'rabl')
end
end
end
end
#
# cache key: ->(resource) { |resource| resource.cache_key }, expires_in: 1800
#
# def cache(options = {}, &block)
# return unless block_given?
# key = options.delete(:key)
# source = sub_compiler { compile_block(&block) }
# @template[:"_cache#{cache_count}"] = Fragment.new(key, source, options)
# end
#
# when Fragment
# render_fragment(value)
#
# def render_fragment(data, fragment)
# Rails.cache.fetch(fragment.expand_cache_key(data), fragment.options) do
# render_resource(data, fragment.compiled_source)
# end
# end

View File

@ -7,7 +7,7 @@ module RablRails
def self.call(template)
%{
RablRails::Library.instance.
get_rendered_template(#{template.source.inspect}, self, local_assigns)
get_rendered_template(#{template.source.inspect}, self)
}
end
end

View File

@ -8,29 +8,30 @@ module RablRails
@cached_templates = {}
end
def get_rendered_template(source, context, locals = nil)
def get_rendered_template(source, context)
path = context.instance_variable_get(:@virtual_path)
@lookup_context = context.lookup_context
compiled_template = compile_template_from_source(source, path)
compiled_template = get_compiled_template(path, source)
format = context.params[:format] || 'json'
Renderers.const_get(format.to_s.upcase!).new(context, locals).render(compiled_template)
Renderers.const_get(format.upcase!).new(context).render(compiled_template)
end
def compile_template_from_source(source, path = nil)
def get_compiled_template(path, source)
if path && RablRails.cache_templates?
@cached_templates[path] ||= Compiler.new.compile_source(source)
@cached_templates[path].dup
else
Compiler.new.compile_source(source)
end
end
def compile_template_from_path(path)
def get(path)
template = @cached_templates[path]
return template if template
t = @lookup_context.find_template(path, [], false)
compile_template_from_source(t.source, path)
get_compiled_template(path, t.source)
end
end
end
end

View File

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

View File

@ -1,91 +1,2 @@
require 'rabl-rails/renderers/base'
require 'rabl-rails/renderers/json'
require 'rabl-rails/renderers/xml'
require 'rabl-rails/renderers/plist'
module RablRails
module Renderer
class TemplateNotFound < StandardError; end
mattr_reader :view_path
@@view_path = 'app/views'
class LookupContext
T = Struct.new(:source)
def initialize(view_path, format)
@view_path = view_path || RablRails::Renderer.view_path
@format = format
end
#
# Manually find given rabl template file with given format.
# View path can be set via options, otherwise default Rails
# path is used
#
def find_template(name, opt, partial = false)
path = File.join(@view_path, "#{name}.#{@format}.rabl")
File.exists?(path) ? T.new(File.read(path)) : nil
end
end
#
# Context class to emulate normal Rails view
# context
#
class Context
attr_reader :format
def initialize(path, options)
@virtual_path = path
@format = options.delete(:format) || 'json'
@_assigns = {}
@options = options
options[:locals].each { |k, v| @_assigns[k.to_s] = v } if options[:locals]
end
def assigns
@_assigns
end
def params
{ format: format }
end
def lookup_context
@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
# defined in the controller), you can passed them as locals
# options.
# For example, if you have this template:
# object :@user
# node(:read) { |u| u.has_read?(@post) }
#
# Your method call should look like this:
# RablRails.render(user, 'users/show', locals: { post: Post.new })
#
def render(object, template, options = {})
object = options[:locals].delete(:object) if !object && options[:locals]
c = Context.new(template, options)
t = c.lookup_context.find_template(template, [], false)
raise TemplateNotFound unless t
Library.instance.get_rendered_template(t.source, c, resource: object)
end
end
end
require 'rabl-rails/renderers/json'

View File

@ -3,12 +3,9 @@ module RablRails
class PartialError < StandardError; end
class Base
attr_accessor :_options
def initialize(context, locals = nil) # :nodoc:
def initialize(context) # :nodoc:
@_context = context
@_options = {}
@_resource = locals[:resource] if locals
setup_render_context
end
@ -19,17 +16,10 @@ module RablRails
# method defined by the renderer.
#
def render(template)
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
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)
_options[:root_name] = template.root_name
output_hash = { template.root_name => output_hash } if template.root_name
format_output(output_hash)
end
@ -48,7 +38,8 @@ module RablRails
# template source passed.
#
def render_resource(data, source)
source.inject({}) { |output, (key, value)|
source.inject({}) { |output, current|
key, value = current
out = case value
when Symbol
@ -61,24 +52,21 @@ module RablRails
when Hash
current_value = value.dup
data_symbol = current_value.delete(:_data)
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
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
output.merge!(render_resource(object, current_value))
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
when Condition
if instance_exec data, &(value.proc)
output.merge!(render_resource(data, value.source))
when Fragment
cache_output = Rails.cache.fetch(value.expand_cache_key(data), value.options) do
data.respond_to?(:each) ? render_collection(data, value.compiled_source) : render_resource(data, value.compiled_source)
end
output.merge!(cache_output)
next output
end
output[key] = out
@ -104,7 +92,7 @@ module RablRails
return [] if object.respond_to?(:empty?) && object.empty?
template = Library.instance.compile_template_from_path(template_path)
template = Library.instance.get(template_path)
object.respond_to?(:each) ? render_collection(object, template.source) : render_resource(object, template.source)
end
@ -124,9 +112,9 @@ module RablRails
#
def setup_render_context
@_context.instance_variable_get(:@_assigns).each_pair { |k, v|
instance_variable_set("@#{k}", v) unless k.start_with?('_')
instance_variable_set("@#{k}", v) unless k.start_with?('_') || k == @data
}
end
end
end
end
end

View File

@ -2,8 +2,7 @@ module RablRails
module Renderers
class JSON < Base
def format_output(hash)
hash = { _options[:root_name] => hash } if _options[:root_name] && RablRails.include_json_root
MultiJson.encode(hash)
ActiveSupport::JSON.encode(hash)
end
end
end

View File

@ -1,11 +0,0 @@
module RablRails
module Renderers
class PLIST < Base
def format_output(hash)
hash = { _options[:root_name] => hash } if _options[:root_name] && RablRails.include_plist_root
RablRails.plist_engine.dump(hash)
end
end
end
end

View File

@ -1,14 +0,0 @@
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

View File

@ -1,47 +0,0 @@
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[:prefixes] = controller._prefixes
options[:template] ||= template
controller.default_render options.merge(status: :created)
else
head :no_content
end
rescue ActionView::MissingTemplate => e
super(e)
end
end
end

View File

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

View File

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

View File

@ -1,22 +1,24 @@
$:.push File.expand_path("../lib", __FILE__)
# Maintain your gem's version:
require "rabl-rails/version"
# Describe your gem and declare its dependencies:
Gem::Specification.new do |s|
s.name = "rabl-rails"
s.version = RablRails::VERSION
s.platform = Gem::Platform::RUBY
s.authors = ["Christopher Cocchi-Perrier"]
s.email = ["cocchi.c@gmail.com"]
s.homepage = "https://github.com/ccocchi/rabl-rails"
s.summary = "Fast Rails 3+ templating system with JSON and XML support"
s.description = "Fast Rails 3+ templating system with JSON and XML support"
s.authors = ["TODO: Your name"]
s.email = ["TODO: Your email"]
s.homepage = "TODO"
s.summary = "TODO: Summary of RablRails."
s.description = "TODO: Description of RablRails."
s.files = `git ls-files`.split("\n")
s.test_files = `git ls-files -- test/*`.split("\n")
s.require_paths = ["lib"]
s.files = Dir["{app,config,db,lib}/**/*"] + ["MIT-LICENSE", "Rakefile", "README.md"]
s.test_files = Dir["test/**/*"]
s.add_dependency "activesupport", "~> 3.0"
s.add_dependency "railties", "~> 3.0"
s.add_dependency "activesupport", "~> 3.2.1"
s.add_development_dependency "actionpack", "~> 3.0"
s.add_development_dependency "sqlite3"
s.add_development_dependency "railties", "~> 3.2.1"
s.add_development_dependency "actionpack", "~> 3.2.1"
end

View File

@ -10,24 +10,24 @@ class CacheTemplatesTest < ActiveSupport::TestCase
test "cache templates if perform_caching is active and cache_templates is enabled" do
ActionController::Base.stub(:perform_caching).and_return(true)
@library.compile_template_from_source('', 'some/path')
t = @library.compile_template_from_source("attribute :id", 'some/path')
@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
ActionController::Base.stub(:perform_caching).and_return(true)
@library.compile_template_from_source('', 'some/path')
t = @library.compile_template_from_source("attribute :id", 'some/path')
@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
ActionController::Base.stub(:perform_caching).and_return(false)
@library.compile_template_from_source('', 'some/path')
t = @library.compile_template_from_source("attribute :id", 'some/path')
@library.get_compiled_template('some/path', "")
t = @library.get_compiled_template('some/path', "attribute :id")
assert_equal({ :id => :id }, t.source)
end

View File

@ -24,16 +24,6 @@ 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
@ -53,11 +43,6 @@ class CompilerTest < ActiveSupport::TestCase
assert_equal :users, t.root_name
end
test "root can be set to false via options" do
t = @compiler.compile_source(%( object :@user, root: false))
assert_equal false, t.root_name
end
# Compilation
test "simple attributes are compiled to hash" do
@ -109,11 +94,10 @@ class CompilerTest < ActiveSupport::TestCase
mock_template = RablRails::CompiledTemplate.new
mock_template.source = { :id => :id }
RablRails::Library.reset_instance
RablRails::Library.instance.stub(:compile_template_from_path).with('users/base').and_return(mock_template)
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)
assert_equal( {:id => :id}, mock_template.source)
end
test "glue is compiled as a child but with anonymous name" do
@ -133,22 +117,10 @@ class CompilerTest < ActiveSupport::TestCase
}, t.source)
end
test "glue accepts all dsl in its body" do
t = @compiler.compile_source(%{
glue :@user do node(:foo) { |u| u.name } end
})
assert_not_nil(t.source[:_glue0])
s = t.source[:_glue0]
assert_equal(:@user, s[:_data])
assert_instance_of(Proc, s[:foo])
end
test "extends use other template source as itself" do
template = mock('template', :source => { :id => :id })
RablRails::Library.reset_instance
RablRails::Library.instance.stub(:compile_template_from_path).with('users/base').and_return(template)
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
@ -166,12 +138,6 @@ class CompilerTest < ActiveSupport::TestCase
assert_equal 2, t.source[:foo].size
end
test "conditionnal block compile nicely" do
t = @compiler.compile_source(%{ condition(->(u) {}) do attributes :secret end })
assert_instance_of RablRails::Condition, t.source[:_if0]
assert_equal({ :secret => :secret }, t.source[:_if0].source)
end
test "compile with no object" do
t = @compiler.compile_source(%{
object false
@ -183,10 +149,14 @@ class CompilerTest < ActiveSupport::TestCase
assert_equal({ :user => { :_data => :@user, :id => :id } }, t.source)
assert_equal false, t.data
end
test "name extraction from argument" do
assert_equal [:@users, 'users'], @compiler.send(:extract_data_and_name, :@users)
assert_equal [:users, :users], @compiler.send(:extract_data_and_name, :users)
assert_equal [:@users, :authors], @compiler.send(:extract_data_and_name, :@users => :authors)
test "cache are compiled to fragment" do
t = @compiler.compile_source(%{
cache do
attributes :name
end
})
assert_instance_of RablRails::Fragment, t.source[:_cache0]
assert_equal({ :name => :name }, t.source[:_cache0].compiled_source)
end
end
end

View File

@ -19,10 +19,12 @@ 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
@context.virtual_path = 'users/show'
@context.stub(:instance_variable_get).with(:@user).and_return(@user)
@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
@ -38,7 +40,7 @@ class DeepNestingTest < ActiveSupport::TestCase
end
}
assert_equal(MultiJson.encode(:user => {
assert_equal(ActiveSupport::JSON.encode({
:id => 1,
:name => 'foobar',
:posts => [{

View File

@ -0,0 +1,4 @@
require 'test_helper'
class FragmentCacheTest < ActiveSupport::TestCase
end

View File

@ -1,47 +0,0 @@
require 'test_helper'
class KeywordTest < ActiveSupport::TestCase
class Collection
attr_accessor :id, :name
def initialize(id, name)
@id = id
@name = name
end
def cover(size)
"foo_#{size}"
end
end
setup do
RablRails::Library.reset_instance
@context = Context.new
@user = User.new(1, 'Marty')
@collections = [Collection.new(1, 'first'), Collection.new(2, 'last')]
@context.assigns['user'] = @user
@context.assigns['collections'] = @collections
@context.virtual_path = 'user/show'
@context.stub(lookup_context: nil)
end
test "collections model should render correctly" do
source = %{
object :@user
child(:@collections => :collections) do
attributes :id, :name
node(:cover_url) { |c|
c.cover(:medium)
}
end
}
assert_equal(MultiJson.encode(
user: { collections: [{
id: 1, name: 'first', cover_url: "foo_medium"
}, {
id: 2, name: 'last', cover_url: "foo_medium"
}] }
), RablRails::Library.instance.get_rendered_template(source, @context))
end
end

View File

@ -24,7 +24,7 @@ class NonRestfulResponseTest < ActiveSupport::TestCase
end
}
assert_equal(MultiJson.encode({
assert_equal(ActiveSupport::JSON.encode({
:post_count => 10,
:user => {
:id => 1,

View File

@ -1,63 +0,0 @@
require 'test_helper'
require 'pathname'
require 'tmpdir'
class RenderTest < ActiveSupport::TestCase
setup do
@user = User.new(1, 'Marty')
@tmp_path = Pathname.new(Dir.mktmpdir)
end
test "allow object to be passed as an option" do
File.open(@tmp_path + "nil.json.rabl", "w") do |f|
f.puts %q{
object :@user
attributes :name
}
end
assert_equal %q({"user":{"name":"Marty"}}), RablRails.render(nil, 'nil', locals: { object: @user }, view_path: @tmp_path)
end
test "load source from file" do
File.open(@tmp_path + "show.json.rabl", "w") do |f|
f.puts %q{
object :@user
attributes :id, :name
}
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 }
}
end
assert_equal %q({"username":"Marty"}), RablRails.render(nil, 'instance', view_path: @tmp_path, locals: { user: @user })
end
test "handle path for extends" do
File.open(@tmp_path + "extend.json.rabl", "w") do |f|
f.puts %q{
object :@user
extends 'base'
}
end
File.open(@tmp_path + "base.json.rabl", "w") do |f|
f.puts %q{
attribute :name, as: :extended_name
}
end
assert_equal %q({"user":{"extended_name":"Marty"}}), RablRails.render(@user, 'extend', view_path: @tmp_path)
end
end

View File

@ -4,9 +4,11 @@ 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
@context.stub(:instance_variable_get).with(:@data).and_return(@data)
@context.stub(:instance_variable_get).with(:@_assigns).and_return({})
@template = RablRails::CompiledTemplate.new
@template.data = :@data
@ -22,18 +24,11 @@ class TestJsonRenderer < ActiveSupport::TestCase
end
test "render collection with empty template" do
@context.assigns['data'] = [@data]
@context.stub(:instance_variable_get).with(:@data).and_return([@data])
@template.source = {}
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
@ -50,25 +45,14 @@ 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
end
test "render glued node" do
@template.source = { :_glue0 => { :_data => :@data, :foo => lambda { |u| u.name } } }
assert_equal(%q({"foo":"foobar"}), render_json_output)
end
test "render collection with attributes" do
@data = [User.new(1, 'foo', 'male'), User.new(2, 'bar', 'female')]
@context.assigns['data'] = @data
@context.stub(:instance_variable_get).with(:@data).and_return(@data)
@template.source = { :uid => :id, :name => :name, :gender => :sex }
assert_equal %q([{"uid":1,"name":"foo","gender":"male"},{"uid":2,"name":"bar","gender":"female"}]), render_json_output
end
@ -92,9 +76,8 @@ class TestJsonRenderer < ActiveSupport::TestCase
@template.source = { :name => [condition, proc] }
assert_equal %q({}), render_json_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 }
@ -104,13 +87,14 @@ class TestJsonRenderer < ActiveSupport::TestCase
test "partial should be evaluated at rendering time" do
# Set assigns
@context.assigns['user'] = @data
@context.stub(:instance_variable_get).with(:@_assigns).and_return({'user' => @data})
@data.stub(:respond_to?).with(:empty?).and_return(false)
# 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)
RablRails::Library.instance.should_receive(:get).with('users/base').and_return(t)
@template.data = false
@template.source = { :user => ->(s) { partial('users/base', :object => @user) } }
@ -131,30 +115,4 @@ class TestJsonRenderer < ActiveSupport::TestCase
assert_equal %q({"users":[]}), render_json_output
end
test "condition blocks are transparent if the condition passed" do
c = RablRails::Condition.new(->(u) { true }, { :name => :name })
@template.source = { :_if0 => c }
assert_equal %q({"name":"foobar"}), render_json_output
end
test "condition blocks are ignored if the condition is not met" do
c = RablRails::Condition.new(->(u) { false }, { :name => :name })
@template.source = { :_if0 => c }
assert_equal %q({}), render_json_output
end
test "render object with root node" do
RablRails.include_json_root = true
@template.root_name = :author
@template.source = { :id => :id, :name => :name }
assert_equal %q({"author":{"id":1,"name":"foobar"}}), render_json_output
end
test "render object with root options set to false" do
RablRails.include_json_root = false
@template.root_name = :author
@template.source = { :id => :id, :name => :name }
assert_equal %q({"id":1,"name":"foobar"}), render_json_output
end
end

View File

@ -1,135 +0,0 @@
require 'test_helper'
class TestPlistRenderer < 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_plist_output
output = RablRails::Renderers::PLIST.new(@context).render(@template).to_s.gsub!(INDENT_REGEXP, '')
output.sub!(HEADER_REGEXP, '').gsub!(%r(</?plist[^>]*>), '').sub!(%r(<dict/?>), '').sub(%r(</dict>), '')
end
test "plist engine should responsd to #dump" do
assert_raises(RuntimeError) { RablRails.plist_engine = Object.new }
end
test "render object wth empty template" do
@template.source = {}
assert_equal %q(), render_plist_output
end
test "render collection with empty template" do
@context.assigns['data'] = [@data]
@template.source = {}
assert_equal %q(<array></array>), render_plist_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(<key>id</key><integer>1</integer>), render_plist_output
end
test "render single object attributes" do
@template.source = { :id => :id, :name => :name }
assert_equal %q(<key>id</key><integer>1</integer><key>name</key><string>foobar</string>), render_plist_output
end
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(<key>address</key><dict><key>city</key><string>Paris</string></dict>), render_plist_output
end
test "render child with arbitrary data source" do
@template.source = { :author => { :_data => :@data, :name => :name } }
assert_equal %q(<key>author</key><dict><key>name</key><string>foobar</string></dict>), render_plist_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(<key>author</key><dict><key>name</key><string>foobar</string></dict>), render_plist_output
end
test "render node property" do
proc = lambda { |object| object.name }
@template.source = { :name => proc }
assert_equal %q(<key>name</key><string>foobar</string>), render_plist_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(<key>name</key><string>foobar</string>), render_plist_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_plist_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(<key>name</key><string>marty</string>), render_plist_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_plist_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 %q(<key>users</key><array/>), render_plist_output
end
test "condition blocks are transparent if the condition passed" do
c = RablRails::Condition.new(->(u) { true }, { :name => :name })
@template.source = { :_if0 => c }
assert_equal %q(<key>name</key><string>foobar</string>), render_plist_output
end
test "condition blocks are ignored if the condition is not met" do
c = RablRails::Condition.new(->(u) { false }, { :name => :name })
@template.source = { :_if0 => c }
assert_equal %q(), render_plist_output
end
test "render object with root node" do
RablRails.include_plist_root = true
@template.root_name = :author
@template.source = { :id => :id, :name => :name }
assert_equal %q(<key>author</key><dict><key>id</key><integer>1</integer><key>name</key><string>foobar</string></dict>), render_plist_output
end
test "render object with root options set to false" do
RablRails.include_plist_root = false
@template.root_name = :author
@template.source = { :id => :id, :name => :name }
assert_equal %q(<key>id</key><integer>1</integer><key>name</key><string>foobar</string>), render_plist_output
end
end

View File

@ -1,131 +0,0 @@
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(<user></user>), render_xml_output
end
test "render collection with empty template" do
@context.assigns['data'] = [@data]
@template.source = {}
@template.root_name = :users
assert_equal %q(<users type="array"><user></user></users>), 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(<user><id type="integer">1</id></user>), render_xml_output
end
test "render single object attributes" do
@template.source = { :name => :name }
assert_equal %q(<user><name>foobar</name></user>), 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(<post><author><name>foobar</name></author></post>), 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(<post><author><name>foobar</name></author></post>), render_xml_output
end
test "render glued attributes from single object" do
@template.source = { :_glue0 => { :_data => :@data, :name => :name } }
assert_equal %q(<user><name>foobar</name></user>), 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(<users type="array"><user><uid type="integer">1</uid><name>foo</name></user><user><uid type="integer">2</uid><name>bar</name></user></users>), render_xml_output
end
test "render node property" do
proc = lambda { |object| object.name }
@template.source = { :name => proc }
assert_equal %q(<user><name>foobar</name></user>), 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(<user><name>foobar</name></user>), 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(<user></user>), 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(<user><name>marty</name></user>), 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(<post><user><name>foobar</name></user></post>), 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(<list><users type="array"/></list>), render_xml_output
end
end

View File

@ -22,15 +22,6 @@ class <<Singleton
end
require 'rabl-rails'
require 'plist'
if RUBY_ENGINE == 'jruby'
require 'nokogiri'
else
require 'libxml'
end
RablRails.load_default_engines!
module ActiveSupport
class TestCase
@ -40,15 +31,10 @@ module ActiveSupport
end
class Context
attr_writer :virtual_path
attr_accessor :virtual_path
def initialize
@_assigns = {}
@virtual_path = nil
end
def assigns
@_assigns
end
def params
@ -56,11 +42,4 @@ class Context
end
end
class User
attr_accessor :id, :name, :sex
def initialize(id=nil, name=nil, sex=nil)
@id = id
@name = name
@sex = sex
end
end
User = Struct.new(:id, :name, :sex)