Compare commits

..

92 Commits

Author SHA1 Message Date
John Bintz cb53352607 ensure that child usage does not manipulate cached template 2013-01-22 14:16:44 -05:00
ccocchi 297f8a9145 Update CHANGELOG 2013-01-19 16:49:44 +01:00
ccocchi 8c35a24408 Add full template stack support. Closes #20 2013-01-19 16:48:56 +01:00
Christopher Cocchi-Perrier 0761830988 Merge pull request #19 from lloydmeta/feature/support_symbol_format
Add support for format as symbol
2013-01-17 07:00:43 -08:00
Lloyd 2d05060311 Add support for format as symbol (e.g. when specified in routes.rb or inside the controller 2013-01-16 12:21:00 +09:00
ccocchi b1ae9a4c58 Bump to 0.3.0 2012-11-14 15:52:17 +01:00
ccocchi 84fa2dd282 Update Changelog 2012-11-14 15:51:08 +01:00
ccocchi fde46000a4 Update README [ci skip] 2012-11-14 15:50:06 +01:00
ccocchi 3ca937246b Use libxml for rbx 2012-11-14 15:19:13 +01:00
ccocchi ff22261ac0 oj is cruby specific 2012-11-14 15:16:22 +01:00
ccocchi 0e18a4141e Use nokogiri on JRuby 2012-11-14 15:13:59 +01:00
ccocchi 8b1e88d4ff Add .travis.yml 2012-11-14 12:45:14 +01:00
ccocchi 2c3f835895 Update CHANGELOG 2012-11-14 12:41:06 +01:00
ccocchi ded9e66973 Add test for keywords used as variable names 2012-11-14 12:40:11 +01:00
Christopher Cocchi-Perrier 0039cc7369 Merge pull request #11 from ccocchi/plist-renderer
Add PList renderer
2012-11-14 03:22:41 -08:00
ccocchi aa659d0b8b Assert that plist engine respond to #dump 2012-11-14 12:10:54 +01:00
ccocchi 2939638c0d Complete Plist tests 2012-11-14 12:02:35 +01:00
ccocchi fa6239a564 Remove location from response by default 2012-11-12 17:16:51 +01:00
ccocchi 543543b5cf Add Plist renderer and some tests 2012-10-28 21:57:19 +01:00
ccocchi 286f08eb83 Remove unused dup. Let inject split hash for us 2012-10-22 18:05:22 +01:00
ccocchi 24ce3a1d7c Fix bug with missing prefixes when using custom responder 2012-10-09 14:24:27 +02:00
ccocchi b5a396a594 Bump to 0.2.2 2012-10-05 17:17:22 +02:00
Christopher Cocchi-Perrier 140a80ecf9 Merge pull request #8 from ccocchi/condition-blocks
Allow conditionnal blocks within template
2012-10-05 05:09:42 -07:00
ccocchi 842f63b080 Merge branch 'master' into condition-blocks
Conflicts:
	lib/rabl-rails.rb
	test/renderers/json_renderer_test.rb
2012-10-05 14:08:45 +02:00
ccocchi 3ce9be556f Bump to 0.2.1 2012-09-27 18:23:05 +02:00
ccocchi 35d21b38e6 Override Responder#to_format to avoid first missing render on POST
request.
Fallback to default Rails responder behavior if template is not found
2012-09-27 18:19:35 +02:00
ccocchi 0cf1efea14 Bump to 0.2.0 2012-09-24 16:02:18 +02:00
Christopher Cocchi-Perrier de8a0da9be Update README.md 2012-09-24 16:58:51 +03:00
ccocchi 9891d09322 Replace yajl by oj 2012-09-20 18:45:46 +02:00
ccocchi a347ebcf5b Only use LibXML if present to avoid generating warning 2012-09-20 18:33:35 +02:00
ccocchi 137234e705 Correctly pass resource for simple render. Only fetch template name
when needed
2012-09-20 13:07:18 +02:00
ccocchi 85eb4ebbf7 Pass resource to all requests (not only POST) to use it instead of ivar in RABL views with respond_to 2012-09-19 18:03:40 +02:00
ccocchi a97a8cd24f Add root in DSL 2012-09-19 17:36:57 +02:00
Christopher Cocchi-Perrier 9f0a92ecd7 Merge pull request #7 from ccocchi/xml-renderer
Add XML renderer
2012-09-19 08:26:22 -07:00
ccocchi 62cabb5f37 Add entry to CHANGELOG 2012-09-17 15:28:14 +02:00
ccocchi d81c3bebfe Update README 2012-09-17 15:26:36 +02:00
ccocchi 84cd69bdfd Add XML engine configuration 2012-09-17 15:24:39 +02:00
ccocchi c566c0d788 Add libxml to faster XML output generation 2012-09-17 15:08:54 +02:00
ccocchi 283b957666 Add tests for XML renderers 2012-09-17 15:08:28 +02:00
ccocchi 9b1abaa691 Merge branch 'master' into xml-renderer 2012-09-17 13:49:19 +02:00
ccocchi 9c9a953e22 Merge branch 'master' into xml-renderer 2012-09-17 13:48:36 +02:00
Christopher Cocchi-Perrier a769f8a3b1 Merge pull request #6 from shmeltex/defaultJSONEngine-change
Let MultiJson select default JSON adapter
2012-09-14 14:21:28 -07:00
Alex Smelik 7a44da28d2 Update to 0.2.0 2012-09-14 12:52:26 -07:00
Alex Smelik 360d70b838 Revert to old MultiJson syntax. 2012-09-13 08:05:52 -07:00
ccocchi 04defc9a19 Add CHANGELOG entries 2012-09-13 16:08:42 +02:00
ccocchi e9f0c69f15 Default template to render can be defined per controller 2012-09-13 16:05:29 +02:00
ccocchi 395a3d7439 Use locals in responder. Responder now works out of the box with
Devise
2012-09-13 16:04:43 +02:00
ccocchi 4b61edad64 Locals are now passed to the renderer object.
Allow to skip object or collection definition when using `respond_to` block
2012-09-13 15:57:05 +02:00
Alex Smelik 28f3dbedd0 Let MultiJson select default JSON adapter (engine), support for latest MultiJson syntax 2012-09-12 10:45:45 -07:00
ccocchi 7e6da1a619 Bump to 0.1.3 2012-09-12 10:19:29 +02:00
Christopher Cocchi-Perrier ed3592566d Update CHANGELOG.md 2012-09-12 02:47:20 +03:00
ccocchi 8376c0d974 RablRails now accepts local methods for child nodes and
root objects. Local methods are used by decent_exposure or
focused_controller
2012-09-12 01:34:54 +02:00
ccocchi dc66b43638 Replace Struct by Class to avoid stubbing respond_to?
Add some tests with local methods
2012-09-12 01:33:04 +02:00
Christopher Cocchi-Perrier 5ca0a44241 Merge pull request #4 from ccocchi/responder
Add custom responder
2012-09-11 02:53:15 -07:00
ccocchi 5c1490c246 Update CHANGELOG 2012-09-11 11:49:07 +02:00
ccocchi 1a98d69146 Merge branch 'master' into responder
Conflicts:
	lib/rabl-rails.rb
2012-09-11 11:42:19 +02:00
ccocchi 3999737daf Allow conditionnal blocks within template 2012-09-05 18:11:23 +02:00
ccocchi cf39a3f748 Bump 0.1.2 2012-08-03 15:39:08 +02:00
ccocchi 62dfd3b7d5 Fix engines loading.
Load default engines once and setter also set up engine
2012-08-03 15:38:43 +02:00
ccocchi 9ce0e2b350 Update README (configuration and render) 2012-07-26 17:36:36 +02:00
ccocchi d6f71ad6cf Add .gitignore and remove Gemfile.lock from git 2012-07-26 16:54:53 +02:00
ccocchi 8a49001b0f Do not fail when JSON engine is not found
and fallback to default
2012-07-26 16:44:51 +02:00
ccocchi 6e5fc9c833 Update CHANGELOG 2012-07-26 16:37:09 +02:00
Christopher Cocchi-Perrier d0b023936b Merge pull request #2 from ccocchi/render-method
Add RablRails#render method
2012-07-26 07:21:36 -07:00
ccocchi 8f5ebfac03 Add test for RablRails#render with instance
variables
2012-07-26 15:24:20 +02:00
ccocchi b42f63788f Raise error when template is not found 2012-07-26 14:59:28 +02:00
ccocchi d4c434e6b0 Refactor RablRails#render to use standard library
methods.

Emulate a render and lookup context like Rails.
2012-07-26 02:21:33 +02:00
ccocchi 69a650c673 Bump to 0.1.1 2012-07-26 00:00:13 +02:00
ccocchi f9709dd034 Remove sqlite3 from developent dependencies 2012-07-25 23:52:42 +02:00
ccocchi 0dc4d16b7e Add CHANGELOG 2012-07-25 23:48:21 +02:00
ccocchi 1c466a3625 Underscore private instance variable @options
to avoid conflicts with view variables
2012-07-25 22:53:09 +02:00
ccocchi c60306eee5 Add documentation for RablRails#render 2012-07-25 22:46:20 +02:00
ccocchi 9e6b4db8eb Make render work with same signature as
standard RABL gem
2012-07-25 19:22:10 +02:00
ccocchi 63b50b2a31 Merge branch 'master' into render-method 2012-07-25 18:37:40 +02:00
ccocchi 0034e05486 Don't need to retrieve data variable from context
since it's has already been copied from assigns.

Modify test Context class to reflect this change
2012-07-25 18:23:41 +02:00
ccocchi 36fe3ec56e Remove test against undefined variable 2012-07-24 18:29:03 +02:00
ccocchi 042609b5d4 Add RablRails#render 2012-07-24 16:32:32 +02:00
ccocchi 8ef2ab6577 Use MultiJson in test also 2012-07-24 12:13:01 +02:00
ccocchi 959192e275 Update LICENSE 2012-07-24 12:07:26 +02:00
ccocchi c3758c0cc7 Add LICENSE and update README 2012-07-23 18:29:36 +02:00
Christopher Cocchi-Perrier 8f387a74c9 Update `performance` block in README 2012-07-23 19:22:27 +03:00
ccocchi b61fdec326 Allow to set root_name to false in template 2012-07-20 15:14:46 +02:00
ccocchi cfd9e3e65d Remove useless dependency 2012-07-18 23:43:35 +02:00
ccocchi 1db4cae807 Allow to specify json engine used.
Default to MultiJson (with Yajl engine) instead of ActiveSupport::JSON
2012-07-18 23:42:09 +02:00
ccocchi a9143693d4 Update gemspec to be valid 2012-07-15 23:31:51 +02:00
ccocchi 3224a33489 Correct indent in README 2012-07-15 23:22:51 +02:00
ccocchi 07a18c9f1d Update README.md 2012-07-15 23:18:17 +02:00
ccocchi 9364803048 Add XML renderer 2012-07-15 18:23:21 +02:00
ccocchi 213043589b Better methods naming 2012-07-15 17:56:11 +02:00
ccocchi b0d83f444b Add include_json_root to configuration. 2012-07-13 19:15:04 +02:00
ccocchi 49f7fe24ae Add custom Rabl reponder 2012-07-03 17:45:23 +02:00
ccocchi 57366b7fd6 Remove useless method call 2012-07-03 15:31:17 +02:00
34 changed files with 1115 additions and 271 deletions

17
.gitignore vendored
View File

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

5
.travis.yml Normal file
View File

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

45
CHANGELOG.md Normal file
View File

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

24
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
# jquery-rails is used by the dummy application
gem 'plist'
platforms :ruby do
gem 'oj'
gem 'libxml-ruby'
end
platforms :jruby do
gem 'nokogiri'
end
group :test do
gem 'rspec-mocks'
# 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.
# To use debugger
# gem 'ruby-debug19', :require => 'ruby-debug'
end

View File

@ -1,67 +0,0 @@
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 YOURNAME
Copyright 2012 Christopher Cocchi-Perrier
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the

216
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 only target Rails 3+ application because Rails 2 applications are becoming less and less present and will be obsolete with Rails 4. So let's look to the future !
rabl-rails is **faster** and uses **less memory** than the standard rabl gem while letting you access the same features. There are some slight changes to do on your templates to get this gem to work but it should't take you more than 5 minutes.
So now you ask why used `rabl-rails` if `rabl` already exists and supports Rails. Rabl-rails is *faster* and uses * less memory* than standard rabl gem while letting you access same features. Of course, there are some slight changes to do on your templates to get this gem to work but it should't take you more than 5 minutes.
rabl-rails only targets **Rails 3+ application** and is compatible with mri 1.9.3, jRuby and rubinius.
## Installation
@ -17,14 +17,14 @@ gem install rabl-rails
or add directly to your `Gemfile`
```
gem 'rabl'
gem 'rabl-rails'
```
And that's it !
## Overview
Once you have installed RABL, you can directly used RABL templates to render your resources without changing anything to you controller. As example,
Once you have installed rabl-rails, you can directly used RABL-rails templates to render your resources without changing anything to you controller. As example,
assuming you have a `Post` model filled with blog posts, and a `PostController` that look like this :
```ruby
@ -81,4 +81,212 @@ 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.

View File

@ -1,12 +1,11 @@
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/fragment'
require 'rabl-rails/condition'
require 'rabl-rails/compiler'
require 'rabl-rails/renderer'
@ -15,19 +14,74 @@ require 'rabl-rails/library'
require 'rabl-rails/handler'
require 'rabl-rails/railtie'
require 'multi_json'
module RablRails
extend self
extend Renderer
autoload :Responder, 'rabl-rails/responder'
mattr_accessor :cache_templates
@@cache_templates = true
def configure
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
yield self
ActionController::Base.responder = Responder if self.use_custom_responder
end
def cache_templates?
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?
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,8 +5,7 @@ module RablRails
#
class Compiler
def initialize
@glue_count = 0
@cache_count = 0
@i = 0
end
#
@ -26,20 +25,13 @@ module RablRails
# object :@user, :root => :author
#
def object(data, options = {})
data, name = extract_data_and_name(data)
@template.data = data
@template.root_name = options[:root] || name
@template.data, @template.root_name = extract_data_and_name(data)
@template.root_name = options[:root] if options.has_key? :root
end
alias_method :collection, :object
#
# 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]
def root(name)
@template.root_name = name
end
#
@ -72,10 +64,10 @@ module RablRails
#
def child(name_or_data, options = {})
data, name = extract_data_and_name(name_or_data)
name = options[:root] if options[:root]
name = options[:root] if options.has_key? :root
if options[:partial]
template = Library.instance.get(options[:partial])
@template[name] = template.merge!(:_data => data)
template = Library.instance.compile_template_from_path(options[:partial])
@template[name] = template.merge(:_data => data)
elsif block_given?
@template[name] = sub_compile(data) { yield }
end
@ -88,8 +80,8 @@ module RablRails
#
def glue(data)
return unless block_given?
name = :"_glue#{@glue_count}"
@glue_count += 1
name = :"_glue#{@i}"
@i += 1
@template[name] = sub_compile(data) { yield }
end
@ -104,7 +96,7 @@ module RablRails
def node(name, options = {}, &block)
condition = options[:if]
if condition.present?
if condition
if condition.is_a?(Proc)
@template[name] = [condition, block]
else
@ -122,19 +114,22 @@ module RablRails
# extends 'users/base'
#
def extends(path)
t = Library.instance.get(path)
t = Library.instance.compile_template_from_path(path)
@template.merge!(t.source)
end
#
# cache key: ->(resource) { |resource| resource.cache_key }, expires_in: 1800
# Provide a conditionnal block
#
def cache(options = {}, &block)
# condition(->(u) { u.is_a?(Admin) }) do
# attributes :secret
# end
#
def condition(proc)
return unless block_given?
key = options.delete(:key)
source = sub_compile { yield }
@template[:"_cache#{@cache_count}"] = Fragment.new(key, source, options)
@cache_count += 1
name = :"_if#{@i}"
@i += 1
@template[name] = Condition.new(proc, sub_compile(nil) { yield })
end
protected
@ -148,11 +143,8 @@ module RablRails
def extract_data_and_name(name_or_data)
case name_or_data
when Symbol
if name_or_data.to_s.start_with?('@')
[name_or_data, nil]
else
[name_or_data, name_or_data]
end
str = name_or_data.to_s
str.start_with?('@') ? [name_or_data, str[1..-1]] : [name_or_data, name_or_data]
when Hash
name_or_data.first
else
@ -160,7 +152,7 @@ module RablRails
end
end
def sub_compile(data = nil)
def sub_compile(data)
return {} unless block_given?
old_template, @template = @template, {}
yield

View File

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

View File

@ -1,42 +0,0 @@
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)
get_rendered_template(#{template.source.inspect}, self, local_assigns)
}
end
end

View File

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

View File

@ -1,6 +1,8 @@
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,2 +1,91 @@
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

View File

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

View File

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

View File

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

@ -0,0 +1,14 @@
require 'active_support/core_ext/hash/conversions'
module RablRails
module Renderers
class XML < Base
DEFAULT_OPTIONS = { dasherize: true, skip_types: false }
def format_output(hash)
xml_options = { root: _options[:root_name] }.merge!(DEFAULT_OPTIONS)
hash.to_xml(xml_options)
end
end
end
end

View File

@ -0,0 +1,47 @@
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,7 +2,7 @@ module RablRails
class CompiledTemplate
attr_accessor :source, :data, :root_name
delegate :[], :[]=, :merge!, :to => :source
delegate :[], :[]=, :merge!, :merge, :to => :source
def initialize
@source = {}

View File

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

View File

@ -1,24 +1,22 @@
$:.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.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.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.files = Dir["{app,config,db,lib}/**/*"] + ["MIT-LICENSE", "Rakefile", "README.md"]
s.test_files = Dir["test/**/*"]
s.files = `git ls-files`.split("\n")
s.test_files = `git ls-files -- test/*`.split("\n")
s.require_paths = ["lib"]
s.add_dependency "activesupport", "~> 3.2.1"
s.add_dependency "activesupport", "~> 3.0"
s.add_dependency "railties", "~> 3.0"
s.add_development_dependency "sqlite3"
s.add_development_dependency "railties", "~> 3.2.1"
s.add_development_dependency "actionpack", "~> 3.2.1"
s.add_development_dependency "actionpack", "~> 3.0"
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.get_compiled_template('some/path', "")
t = @library.get_compiled_template('some/path', "attribute :id")
@library.compile_template_from_source('', 'some/path')
t = @library.compile_template_from_source("attribute :id", 'some/path')
assert_equal({}, t.source)
end
test "cached templates should not be modifiable in place" do
ActionController::Base.stub(:perform_caching).and_return(true)
@library.get_compiled_template('some/path', "")
t = @library.get_compiled_template('some/path', "attribute :id")
@library.compile_template_from_source('', 'some/path')
t = @library.compile_template_from_source("attribute :id", 'some/path')
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.get_compiled_template('some/path', "")
t = @library.get_compiled_template('some/path', "attribute :id")
@library.compile_template_from_source('', 'some/path')
t = @library.compile_template_from_source("attribute :id", 'some/path')
assert_equal({ :id => :id }, t.source)
end

View File

@ -24,6 +24,16 @@ class CompilerTest < ActiveSupport::TestCase
assert_equal({}, t.source)
end
test "root can be defined via keyword" do
t = @compiler.compile_source(%{ root :author })
assert_equal :author, t.root_name
end
test "root keyword override object root" do
t = @compiler.compile_source(%{ object :@user ; root :author })
assert_equal :author, t.root_name
end
test "collection set the data for the template" do
t = @compiler.compile_source(%{ collection :@user })
assert_equal :@user, t.data
@ -43,6 +53,11 @@ 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
@ -94,10 +109,11 @@ class CompilerTest < ActiveSupport::TestCase
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)
RablRails::Library.instance.stub(:compile_template_from_path).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
@ -117,10 +133,22 @@ 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(:get).with('users/base').and_return(template)
RablRails::Library.instance.stub(:compile_template_from_path).with('users/base').and_return(template)
t = @compiler.compile_source(%{ extends 'users/base' })
assert_equal({ :id => :id }, t.source)
end
@ -138,6 +166,12 @@ 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
@ -150,13 +184,9 @@ class CompilerTest < ActiveSupport::TestCase
assert_equal false, t.data
end
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)
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)
end
end

View File

@ -19,12 +19,10 @@ 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.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.assigns['user'] = @user
@context.virtual_path = 'users/show'
@context.stub(:lookup_context).and_return(mock(:find_template => mock(:source => %{ object :@comment\n attribute :content })))
end
@ -40,7 +38,7 @@ class DeepNestingTest < ActiveSupport::TestCase
end
}
assert_equal(ActiveSupport::JSON.encode({
assert_equal(MultiJson.encode(:user => {
:id => 1,
:name => 'foobar',
:posts => [{

View File

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

47
test/keyword_test.rb Normal file
View File

@ -0,0 +1,47 @@
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(ActiveSupport::JSON.encode({
assert_equal(MultiJson.encode({
:post_count => 10,
:user => {
:id => 1,

63
test/render_test.rb Normal file
View File

@ -0,0 +1,63 @@
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,11 +4,9 @@ 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.stub(:instance_variable_get).with(:@data).and_return(@data)
@context.stub(:instance_variable_get).with(:@_assigns).and_return({})
@context.assigns['data'] = @data
@template = RablRails::CompiledTemplate.new
@template.data = :@data
@ -24,11 +22,18 @@ class TestJsonRenderer < ActiveSupport::TestCase
end
test "render collection with empty template" do
@context.stub(:instance_variable_get).with(:@data).and_return([@data])
@context.assigns['data'] = [@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
@ -45,14 +50,25 @@ 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.stub(:instance_variable_get).with(:@data).and_return(@data)
@context.assigns['data'] = @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
@ -78,6 +94,7 @@ class TestJsonRenderer < ActiveSupport::TestCase
end
test "node with context method call" do
@context.stub(:respond_to?).with(:@data).and_return(false)
@context.stub(:respond_to?).with(:context_method).and_return(true)
@context.stub(:context_method).and_return('marty')
proc = lambda { |object| context_method }
@ -87,14 +104,13 @@ class TestJsonRenderer < ActiveSupport::TestCase
test "partial should be evaluated at rendering time" do
# Set assigns
@context.stub(:instance_variable_get).with(:@_assigns).and_return({'user' => @data})
@data.stub(:respond_to?).with(:empty?).and_return(false)
@context.assigns['user'] = @data
# Stub Library#get
t = RablRails::CompiledTemplate.new
t.source = { :name => :name }
RablRails::Library.reset_instance
RablRails::Library.instance.should_receive(:get).with('users/base').and_return(t)
RablRails::Library.instance.should_receive(:compile_template_from_path).with('users/base').and_return(t)
@template.data = false
@template.source = { :user => ->(s) { partial('users/base', :object => @user) } }
@ -115,4 +131,30 @@ 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

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

@ -0,0 +1,131 @@
require 'test_helper'
class TestXmlRenderer < ActiveSupport::TestCase
INDENT_REGEXP = /\n(\s)*/
HEADER_REGEXP = /<[^>]+>/
setup do
@data = User.new(1, 'foobar', 'male')
@context = Context.new
@context.assigns['data'] = @data
@template = RablRails::CompiledTemplate.new
@template.data = :@data
@template.root_name = :user
end
def render_xml_output
RablRails::Renderers::XML.new(@context).render(@template).to_s.gsub!(INDENT_REGEXP, '').sub!(HEADER_REGEXP, '')
end
test "render object simple object" do
@template.source = {}
assert_equal %q(<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,6 +22,15 @@ 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
@ -31,10 +40,15 @@ module ActiveSupport
end
class Context
attr_accessor :virtual_path
attr_writer :virtual_path
def initialize
@_assigns = {}
@virtual_path = nil
end
def assigns
@_assigns
end
def params
@ -42,4 +56,11 @@ class Context
end
end
User = Struct.new(:id, :name, :sex)
class User
attr_accessor :id, :name, :sex
def initialize(id=nil, name=nil, sex=nil)
@id = id
@name = name
@sex = sex
end
end