diff --git a/.gitignore b/.gitignore
index 1dfe31e..e55a421 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..7668f0e
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,24 @@
+# CHANGELOG
+
+## 0.2.0 (unrelased)
+ * 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 correcly 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 unnused test in loop
+ * Speed up rendering by not double copying variable from context
+ * Rename private variable to avoid name conflict
+ * Remove sqlite3 development dependency
\ No newline at end of file
diff --git a/Gemfile b/Gemfile
index 577d5f7..46ed377 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,18 +1,9 @@
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 'yajl-ruby'
-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'
+group :test do
+ gem 'rspec-mocks'
+end
diff --git a/Gemfile.lock b/Gemfile.lock
deleted file mode 100644
index a07074c..0000000
--- a/Gemfile.lock
+++ /dev/null
@@ -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
diff --git a/MIT-LICENSE b/MIT-LICENSE
index 406f17b..98f382c 100644
--- a/MIT-LICENSE
+++ b/MIT-LICENSE
@@ -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
diff --git a/README.md b/README.md
index 594c214..1df7dda 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@ RABL (Ruby API Builder Language) is a ruby templating system for rendering resou
RABL-rails only target Rails 3+ application because Rails 2 applications are becoming less and less present and will be obsolete with Rails 4. So let's look to the future !
-So now you ask why used `rabl-rails` if `rabl` already exists and supports Rails. Rabl-rails is *faster* and uses * less memory* than standard rabl gem while letting you access same features. Of course, there are some slight changes to do on your templates to get this gem to work but it should't take you more than 5 minutes.
+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,7 +17,7 @@ gem install rabl-rails
or add directly to your `Gemfile`
```
-gem 'rabl'
+gem 'rabl-rails'
```
And that's it !
@@ -29,12 +29,12 @@ assuming you have a `Post` model filled with blog posts, and a `PostController`
```ruby
class PostController < ApplicationController
- respond_to :html, :json, :xml
-
- def index
- @posts = Post.order('created_at DESC')
- respond_with(@posts)
- end
+ respond_to :html, :json, :xml
+
+ def index
+ @posts = Post.order('created_at DESC')
+ respond_with(@posts)
+ end
end
```
@@ -64,21 +64,209 @@ 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.
-## Usage
\ No newline at end of file
+## Configuration
+
+RablRails works out of the box, with default options and fastest engine available (yajl, 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 = :yajl
+ 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 }
+```
+
+### 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" :
, "bar" : }
+```
+
+### 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
+```
+
+### Caching
+
+Caching is not a part of Rabl-rails. It is already in Rails itself, because caching all view output is the same as action caching (Rails caching is even better because it will also not run your queries).
+
+Moreover caching each object in a collection can be really not effective with big collections or simple objects. This is also a nightmare with cache expiration.
+
+### 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)
+
+## Performance
+
+Benchmarks have been made using this [application](http://github.com/ccocchi/rabl-benchmark), with rabl 0.6.14 and rabl-rails 0.1.0
+
+Overall, Rabl-rails is **20% faster and use 10% less memory**.
+
+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
+
+## Original idea
+
+* [RABL](http://github.com/nesquena/rabl) Standart RABL gem. I used it a lot before deciding I wanted faster views
+
+## Copyright
+
+Copyright © 2011-2012 Christopher Cocchi-Perrier. See [MIT-LICENSE](http://github.com/ccocchi/rabl-rails/blob/master/MIT-LICENSE) for details.
diff --git a/lib/rabl-rails.rb b/lib/rabl-rails.rb
index 289746b..d6b645b 100644
--- a/lib/rabl-rails.rb
+++ b/lib/rabl-rails.rb
@@ -1,7 +1,6 @@
require 'rails/railtie'
require 'active_support'
-require 'active_support/json'
require 'active_support/core_ext/class/attribute_accessors'
require 'rabl-rails/version'
@@ -14,22 +13,46 @@ 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
-
+
mattr_accessor :include_json_root
@@include_json_root = true
- def configure
+ mattr_reader :json_engine
+ @@json_engine = :yajl
+
+ mattr_accessor :use_custom_responder
+ @@use_custom_responder = false
+
+ mattr_accessor :responder_default_template
+ @@responder_default_template = 'show'
+
+ 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
+ @@json_engine = name
+ rescue LoadError
+ Rails.logger.warn %Q(WARNING: rabl-rails could not load "#{self.json_engine}" as JSON engine, fallback to default)
+ end
+
+ def self.cache_templates?
ActionController::Base.perform_caching && @@cache_templates
end
+
+ def self.load_default_engines!
+ self.json_engine = :yajl
+ end
end
diff --git a/lib/rabl-rails/compiler.rb b/lib/rabl-rails/compiler.rb
index ea291bb..eb2f9a5 100644
--- a/lib/rabl-rails/compiler.rb
+++ b/lib/rabl-rails/compiler.rb
@@ -26,7 +26,7 @@ module RablRails
#
def object(data, options = {})
@template.data, @template.root_name = extract_data_and_name(data)
- @template.root_name = options[:root] if options[:root]
+ @template.root_name = options[:root] if options.has_key? :root
end
alias_method :collection, :object
@@ -60,7 +60,7 @@ 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.compile_template_from_path(options[:partial])
@template[name] = template.merge!(:_data => data)
@@ -133,7 +133,7 @@ module RablRails
name_or_data
end
end
-
+
def sub_compile(data)
return {} unless block_given?
old_template, @template = @template, {}
diff --git a/lib/rabl-rails/handler.rb b/lib/rabl-rails/handler.rb
index 8cc028e..6b1d9ab 100644
--- a/lib/rabl-rails/handler.rb
+++ b/lib/rabl-rails/handler.rb
@@ -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
diff --git a/lib/rabl-rails/library.rb b/lib/rabl-rails/library.rb
index 0324369..e53bc4a 100644
--- a/lib/rabl-rails/library.rb
+++ b/lib/rabl-rails/library.rb
@@ -8,14 +8,14 @@ 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 = 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.upcase!).new(context, locals).render(compiled_template)
end
def compile_template_from_source(source, path = nil)
diff --git a/lib/rabl-rails/railtie.rb b/lib/rabl-rails/railtie.rb
index 207bd61..eee7e22 100644
--- a/lib/rabl-rails/railtie.rb
+++ b/lib/rabl-rails/railtie.rb
@@ -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
diff --git a/lib/rabl-rails/renderer.rb b/lib/rabl-rails/renderer.rb
index 0ee0acf..b74d3d0 100644
--- a/lib/rabl-rails/renderer.rb
+++ b/lib/rabl-rails/renderer.rb
@@ -1,2 +1,89 @@
require 'rabl-rails/renderers/base'
-require 'rabl-rails/renderers/json'
\ No newline at end of file
+require 'rabl-rails/renderers/json'
+
+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
\ No newline at end of file
diff --git a/lib/rabl-rails/renderers/base.rb b/lib/rabl-rails/renderers/base.rb
index 2d04abd..c336de3 100644
--- a/lib/rabl-rails/renderers/base.rb
+++ b/lib/rabl-rails/renderers/base.rb
@@ -3,11 +3,12 @@ module RablRails
class PartialError < StandardError; end
class Base
- attr_accessor :options
+ attr_accessor :_options
- def initialize(context) # :nodoc:
+ def initialize(context, locals = nil) # :nodoc:
@_context = context
- @options = {}
+ @_options = {}
+ @_resource = locals[:resource] if locals
setup_render_context
end
@@ -18,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)
- options[:root_name] = template.root_name
+ _options[:root_name] = template.root_name
format_output(output_hash)
end
@@ -54,7 +62,13 @@ 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|
@@ -108,7 +122,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
diff --git a/lib/rabl-rails/renderers/json.rb b/lib/rabl-rails/renderers/json.rb
index 29f528c..632aaec 100644
--- a/lib/rabl-rails/renderers/json.rb
+++ b/lib/rabl-rails/renderers/json.rb
@@ -2,8 +2,8 @@ module RablRails
module Renderers
class JSON < Base
def format_output(hash)
- hash = { options[:root_name] => hash } if options[:root_name] && RablRails.include_json_root
- ActiveSupport::JSON.encode(hash)
+ hash = { _options[:root_name] => hash } if _options[:root_name] && RablRails.include_json_root
+ MultiJson.encode(hash)
end
end
end
diff --git a/lib/rabl-rails/responder.rb b/lib/rabl-rails/responder.rb
new file mode 100644
index 0000000..997bf80
--- /dev/null
+++ b/lib/rabl-rails/responder.rb
@@ -0,0 +1,27 @@
+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
+ @api_template = @controller.respond_to?(:responder_default_template, true) ? controller.send(:responder_default_template) : nil
+ end
+
+ protected
+
+ def api_behavior(error)
+ rabl_options = options.merge(template: @api_template || RablRails.responder_default_template)
+
+ if get?
+ controller.default_render rabl_options
+ elsif post?
+ controller.default_render rabl_options.merge!(status: :created, location: api_location, locals: { resource: resource })
+ else
+ head :no_content
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/rabl-rails/version.rb b/lib/rabl-rails/version.rb
index f73f1e5..e19060c 100644
--- a/lib/rabl-rails/version.rb
+++ b/lib/rabl-rails/version.rb
@@ -1,3 +1,3 @@
module RablRails
- VERSION = '0.1.0'
+ VERSION = '0.1.3'
end
diff --git a/rabl-rails.gemspec b/rabl-rails.gemspec
index 0748115..bf7c0bd 100644
--- a/rabl-rails.gemspec
+++ b/rabl-rails.gemspec
@@ -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
diff --git a/test/compiler_test.rb b/test/compiler_test.rb
index 493e4e6..b9f8fcc 100644
--- a/test/compiler_test.rb
+++ b/test/compiler_test.rb
@@ -42,6 +42,11 @@ class CompilerTest < ActiveSupport::TestCase
assert_equal :@user, t.data
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
diff --git a/test/deep_nesting_test.rb b/test/deep_nesting_test.rb
index 7aac9e8..4292877 100644
--- a/test/deep_nesting_test.rb
+++ b/test/deep_nesting_test.rb
@@ -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(:user => {
+ assert_equal(MultiJson.encode(:user => {
:id => 1,
:name => 'foobar',
:posts => [{
diff --git a/test/non_restful_response_test.rb b/test/non_restful_response_test.rb
index 44c3109..5228f15 100644
--- a/test/non_restful_response_test.rb
+++ b/test/non_restful_response_test.rb
@@ -24,7 +24,7 @@ class NonRestfulResponseTest < ActiveSupport::TestCase
end
}
- assert_equal(ActiveSupport::JSON.encode({
+ assert_equal(MultiJson.encode({
:post_count => 10,
:user => {
:id => 1,
diff --git a/test/render_test.rb b/test/render_test.rb
new file mode 100644
index 0000000..9b4b26d
--- /dev/null
+++ b/test/render_test.rb
@@ -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
\ No newline at end of file
diff --git a/test/renderers/json_renderer_test.rb b/test/renderers/json_renderer_test.rb
index 8b376d5..93d3ab0 100644
--- a/test/renderers/json_renderer_test.rb
+++ b/test/renderers/json_renderer_test.rb
@@ -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,6 +50,12 @@ 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
@@ -52,7 +63,7 @@ class TestJsonRenderer < ActiveSupport::TestCase
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
@@ -76,8 +87,9 @@ class TestJsonRenderer < ActiveSupport::TestCase
@template.source = { :name => [condition, proc] }
assert_equal %q({}), render_json_output
end
-
+
test "node with context method call" do
+ @context.stub(:respond_to?).with(:@data).and_return(false)
@context.stub(:respond_to?).with(:context_method).and_return(true)
@context.stub(:context_method).and_return('marty')
proc = lambda { |object| context_method }
@@ -87,8 +99,7 @@ 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
@@ -115,17 +126,17 @@ class TestJsonRenderer < ActiveSupport::TestCase
assert_equal %q({"users":[]}), render_json_output
end
-
+
test "render object with root node" do
@template.root_name = :author
@template.source = { :id => :id, :name => :name }
- assert_equal %q({"author":{"id":1,"name":"foobar"}}), render_json_output
+ 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
+ assert_equal %q({"id":1,"name":"foobar"}), render_json_output
end
end
\ No newline at end of file
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 1a054dc..7468f73 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -31,10 +31,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 +47,11 @@ class Context
end
end
-User = Struct.new(:id, :name, :sex)
\ No newline at end of file
+class User
+ attr_accessor :id, :name, :sex
+ def initialize(id=nil, name=nil, sex=nil)
+ @id = id
+ @name = name
+ @sex = sex
+ end
+end
\ No newline at end of file