Compare commits
3 Commits
master
...
fragment-c
Author | SHA1 | Date |
---|---|---|
ccocchi | 4dae04896d | |
ccocchi | 4cf10fb8f6 | |
ccocchi | 79d6bb9c81 |
|
@ -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
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
language: ruby
|
||||
rvm:
|
||||
- 1.9.3
|
||||
- jruby-19mode
|
||||
- rbx-19mode
|
45
CHANGELOG.md
45
CHANGELOG.md
|
@ -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
22
Gemfile
|
@ -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'
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
228
README.md
228
README.md
|
@ -2,9 +2,9 @@
|
|||
|
||||
RABL (Ruby API Builder Language) is a ruby templating system for rendering resources in different format (JSON, XML, BSON, ...). You can find documentation [here](http://github.com/nesquena/rabl).
|
||||
|
||||
rabl-rails 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,14 +17,14 @@ 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
|
||||
|
@ -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
|
||||
# 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) }
|
||||
# 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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,8 +148,11 @@ 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
|
||||
|
@ -152,7 +160,7 @@ module RablRails
|
|||
end
|
||||
end
|
||||
|
||||
def sub_compile(data)
|
||||
def sub_compile(data = nil)
|
||||
return {} unless block_given?
|
||||
old_template, @template = @template, {}
|
||||
yield
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
module RablRails
|
||||
class Condition
|
||||
attr_reader :proc, :source
|
||||
|
||||
def initialize(proc, source)
|
||||
@proc = proc
|
||||
@source = source
|
||||
end
|
||||
end
|
||||
end
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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,7 +112,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?('_')
|
||||
instance_variable_set("@#{k}", v) unless k.start_with?('_') || k == @data
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -2,7 +2,7 @@ module RablRails
|
|||
class CompiledTemplate
|
||||
attr_accessor :source, :data, :root_name
|
||||
|
||||
delegate :[], :[]=, :merge!, :merge, :to => :source
|
||||
delegate :[], :[]=, :merge!, :to => :source
|
||||
|
||||
def initialize
|
||||
@source = {}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
module RablRails
|
||||
VERSION = '0.3.0'
|
||||
VERSION = '0.1.0'
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
@ -184,9 +150,13 @@ class CompilerTest < ActiveSupport::TestCase
|
|||
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
|
|
@ -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 => [{
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
require 'test_helper'
|
||||
|
||||
class FragmentCacheTest < ActiveSupport::TestCase
|
||||
end
|
|
@ -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
|
|
@ -24,7 +24,7 @@ class NonRestfulResponseTest < ActiveSupport::TestCase
|
|||
end
|
||||
}
|
||||
|
||||
assert_equal(MultiJson.encode({
|
||||
assert_equal(ActiveSupport::JSON.encode({
|
||||
:post_count => 10,
|
||||
:user => {
|
||||
:id => 1,
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
@ -94,7 +78,6 @@ 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 }
|
||||
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
Loading…
Reference in New Issue