From 49f7fe24ae0f28e034c64841fe75d9c389df24d6 Mon Sep 17 00:00:00 2001 From: ccocchi Date: Tue, 3 Jul 2012 17:45:23 +0200 Subject: [PATCH 01/38] Add custom Rabl reponder --- lib/rabl-rails.rb | 16 +++++++++++----- lib/rabl-rails/responder.rb | 22 ++++++++++++++++++++++ 2 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 lib/rabl-rails/responder.rb diff --git a/lib/rabl-rails.rb b/lib/rabl-rails.rb index 77c6c4c..89b56aa 100644 --- a/lib/rabl-rails.rb +++ b/lib/rabl-rails.rb @@ -14,19 +14,25 @@ require 'rabl-rails/library' require 'rabl-rails/handler' require 'rabl-rails/railtie' - - module RablRails - extend self + autoload :Responder, 'rabl-rails/responder' mattr_accessor :cache_templates @@cache_templates = true - def configure + 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.cache_templates? ActionController::Base.perform_caching && @@cache_templates end end diff --git a/lib/rabl-rails/responder.rb b/lib/rabl-rails/responder.rb new file mode 100644 index 0000000..fb094bc --- /dev/null +++ b/lib/rabl-rails/responder.rb @@ -0,0 +1,22 @@ +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 + protected + + def api_behavior(error) + rabl_options = options.merge(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) + else + head :no_content + end + end + end +end \ No newline at end of file From 07a18c9f1daf134bd11ee39ddc0f0ec442449c52 Mon Sep 17 00:00:00 2001 From: ccocchi Date: Sun, 15 Jul 2012 23:18:17 +0200 Subject: [PATCH 02/38] Update README.md --- README.md | 177 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 171 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 594c214..67527f9 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 ! @@ -30,9 +30,9 @@ 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') + @posts = Post.order('created_at DESC') respond_with(@posts) end end @@ -64,7 +64,7 @@ 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). @@ -81,4 +81,169 @@ 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 +## 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" : <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 +``` + +## Performance + +Performance tests have been made using this [application](http://github.com/ccocchi/rabl-benchmark), with Rabl 0.9 and Rabl-rails 0.2.0 + +Overall, Rabl-rails is **20% faster and use 15% less memory**. + +You can see full tests and graphic on the benchmark application repository. + +## 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). + +And 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. + +## Resources + +## 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 is used a lot before deciding I wanted faster views + +## Copyright From 3224a3348954515cd103a102b890c67fb10ab49d Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Sun, 15 Jul 2012 23:22:51 +0200 Subject: [PATCH 03/38] Correct indent in README --- README.md | 76 +++++++++++++++++++++++++++---------------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 67527f9..6843d66 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ class PostController < ApplicationController def index @posts = Post.order('created_at DESC') - respond_with(@posts) + respond_with(@posts) end end ``` @@ -69,12 +69,12 @@ The fact of compiling the template outside of any rendering context prevent us t 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` @@ -98,19 +98,19 @@ 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 +collection :@posts, root: :articles +#is equivalent to +collection :@posts => :articles - # => { "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 } +object false +node(:some_count) { |_| @user.posts.count } +child(:@user) { attribute :name } ``` ### Attributes / Methods @@ -118,14 +118,14 @@ There are rares cases when the template doesn't map directly to any object. In t 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 +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> } +attributes title: :foo, to_s: :bar +# => { "foo" : <title value>, "bar" : <to_s value> } ``` ### Child nodes @@ -134,18 +134,18 @@ You can include nested information from data associated with the parent model. Y 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." } } } +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 +child(:@users) do + attributes :id, :name +end ``` ### Custom nodes @@ -153,23 +153,23 @@ You can also use arbitrary data source with child 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" } } +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 +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) } +node(:url) { |post| post_url(post) } ``` Custom nodes are really usefull to create flexible representations of your resources. @@ -208,14 +208,14 @@ end 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 +object :@thread +attribute :caption +child :posts do + attribute :title + child :comments do + extends 'comments/base' end +end ``` ## Performance From a9143693d416b6c79e181ca1eb6cc853ec9b2357 Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Sun, 15 Jul 2012 23:31:51 +0200 Subject: [PATCH 04/38] Update gemspec to be valid --- Gemfile | 14 +------------- Gemfile.lock | 46 +++++++++++++++++++++++----------------------- rabl-rails.gemspec | 25 ++++++++++++------------- 3 files changed, 36 insertions(+), 49 deletions(-) diff --git a/Gemfile b/Gemfile index 577d5f7..3dd2f20 100644 --- a/Gemfile +++ b/Gemfile @@ -1,18 +1,6 @@ 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 - +# Mocking library 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' diff --git a/Gemfile.lock b/Gemfile.lock index a07074c..881f7c6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,66 +2,66 @@ PATH remote: . specs: rabl-rails (0.1.0) - activesupport (~> 3.2.1) + activesupport (~> 3.0) GEM remote: http://rubygems.org/ specs: - actionpack (3.2.1) - activemodel (= 3.2.1) - activesupport (= 3.2.1) + actionpack (3.2.6) + activemodel (= 3.2.6) + activesupport (= 3.2.6) builder (~> 3.0.0) erubis (~> 2.7.0) journey (~> 1.0.1) rack (~> 1.4.0) - rack-cache (~> 1.1) + rack-cache (~> 1.2) rack-test (~> 0.6.1) - sprockets (~> 2.1.2) - activemodel (3.2.1) - activesupport (= 3.2.1) + sprockets (~> 2.1.3) + activemodel (3.2.6) + activesupport (= 3.2.6) builder (~> 3.0.0) - activesupport (3.2.1) + activesupport (3.2.6) 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) + journey (1.0.4) + json (1.7.3) + multi_json (1.3.6) rack (1.4.1) - rack-cache (1.1) + rack-cache (1.2) 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) + railties (3.2.6) + actionpack (= 3.2.6) + activesupport (= 3.2.6) rack-ssl (~> 1.3.2) rake (>= 0.8.7) rdoc (~> 3.4) - thor (~> 0.14.6) + thor (>= 0.14.6, < 2.0) rake (0.9.2.2) rdoc (3.12) json (~> 1.4) - rspec-mocks (2.8.0) - sprockets (2.1.2) + rspec-mocks (2.11.1) + sprockets (2.1.3) hike (~> 1.2) rack (~> 1.0) tilt (~> 1.1, != 1.3.0) - sqlite3 (1.3.5) - thor (0.14.6) + sqlite3 (1.3.6) + thor (0.15.4) tilt (1.3.3) PLATFORMS ruby DEPENDENCIES - actionpack (~> 3.2.1) + actionpack (~> 3.0) rabl-rails! - railties (~> 3.2.1) + railties (~> 3.0) rspec-mocks sqlite3 diff --git a/rabl-rails.gemspec b/rabl-rails.gemspec index 0748115..4707712 100644 --- a/rabl-rails.gemspec +++ b/rabl-rails.gemspec @@ -1,24 +1,23 @@ $:.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_development_dependency "sqlite3" - s.add_development_dependency "railties", "~> 3.2.1" - s.add_development_dependency "actionpack", "~> 3.2.1" + s.add_development_dependency "railties", "~> 3.0" + s.add_development_dependency "actionpack", "~> 3.0" end From 1db4cae8079e9622d6f01d845349690d49e76171 Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Wed, 18 Jul 2012 23:42:09 +0200 Subject: [PATCH 05/38] Allow to specify json engine used. Default to MultiJson (with Yajl engine) instead of ActiveSupport::JSON --- Gemfile | 7 +++++-- Gemfile.lock | 4 +++- lib/rabl-rails.rb | 17 ++++++++++++----- lib/rabl-rails/renderers/json.rb | 2 +- rabl-rails.gemspec | 2 +- 5 files changed, 22 insertions(+), 10 deletions(-) diff --git a/Gemfile b/Gemfile index 3dd2f20..46ed377 100644 --- a/Gemfile +++ b/Gemfile @@ -2,5 +2,8 @@ source "http://rubygems.org" gemspec -# Mocking library -gem 'rspec-mocks' +gem 'yajl-ruby' + +group :test do + gem 'rspec-mocks' +end diff --git a/Gemfile.lock b/Gemfile.lock index 881f7c6..707de63 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,6 +3,7 @@ PATH specs: rabl-rails (0.1.0) activesupport (~> 3.0) + railties (~> 3.0) GEM remote: http://rubygems.org/ @@ -55,6 +56,7 @@ GEM sqlite3 (1.3.6) thor (0.15.4) tilt (1.3.3) + yajl-ruby (1.1.0) PLATFORMS ruby @@ -62,6 +64,6 @@ PLATFORMS DEPENDENCIES actionpack (~> 3.0) rabl-rails! - railties (~> 3.0) rspec-mocks sqlite3 + yajl-ruby diff --git a/lib/rabl-rails.rb b/lib/rabl-rails.rb index 289746b..47eee41 100644 --- a/lib/rabl-rails.rb +++ b/lib/rabl-rails.rb @@ -14,22 +14,29 @@ require 'rabl-rails/library' require 'rabl-rails/handler' require 'rabl-rails/railtie' - +require 'multi_json' module RablRails - extend self - mattr_accessor :cache_templates @@cache_templates = true mattr_accessor :include_json_root @@include_json_root = true - def configure + mattr_accessor :json_engine + @@json_engine = :yajl + + def self.configure yield self + post_configure end - def cache_templates? + def self.cache_templates? ActionController::Base.perform_caching && @@cache_templates end + + private + def self.post_configure + MultiJson.engine = self.json_engine + end end diff --git a/lib/rabl-rails/renderers/json.rb b/lib/rabl-rails/renderers/json.rb index 29f528c..ef42fb5 100644 --- a/lib/rabl-rails/renderers/json.rb +++ b/lib/rabl-rails/renderers/json.rb @@ -3,7 +3,7 @@ module RablRails class JSON < Base def format_output(hash) hash = { options[:root_name] => hash } if options[:root_name] && RablRails.include_json_root - ActiveSupport::JSON.encode(hash) + MultiJson.encode(hash) end end end diff --git a/rabl-rails.gemspec b/rabl-rails.gemspec index 4707712..5fd4a42 100644 --- a/rabl-rails.gemspec +++ b/rabl-rails.gemspec @@ -16,8 +16,8 @@ Gem::Specification.new do |s| s.require_paths = ["lib"] s.add_dependency "activesupport", "~> 3.0" + s.add_dependency "railties", "~> 3.0" s.add_development_dependency "sqlite3" - s.add_development_dependency "railties", "~> 3.0" s.add_development_dependency "actionpack", "~> 3.0" end From cfd9e3e65dee6b0ac350077a105181741c7a64e1 Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Wed, 18 Jul 2012 23:43:35 +0200 Subject: [PATCH 06/38] Remove useless dependency --- lib/rabl-rails.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/rabl-rails.rb b/lib/rabl-rails.rb index 47eee41..471fa00 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' From b61fdec32680a2d4b32ef28b8d309d212bdd8590 Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Fri, 20 Jul 2012 15:14:46 +0200 Subject: [PATCH 07/38] Allow to set root_name to false in template --- lib/rabl-rails/compiler.rb | 4 ++-- test/compiler_test.rb | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/rabl-rails/compiler.rb b/lib/rabl-rails/compiler.rb index ea291bb..7e78ea2 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) 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 From 8f387a74c9f2e193784ed3ace23a6d32f8c28994 Mon Sep 17 00:00:00 2001 From: Christopher Cocchi-Perrier <cocchi.c@gmail.com> Date: Mon, 23 Jul 2012 19:22:27 +0300 Subject: [PATCH 08/38] Update `performance` block in README --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 6843d66..4bbc517 100644 --- a/README.md +++ b/README.md @@ -220,11 +220,11 @@ end ## Performance -Performance tests have been made using this [application](http://github.com/ccocchi/rabl-benchmark), with Rabl 0.9 and Rabl-rails 0.2.0 +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 15% less memory**. +Overall, Rabl-rails is **20% faster and use 10% less memory**. -You can see full tests and graphic on the benchmark application repository. +You can see full tests on test application repository. ## Caching @@ -232,8 +232,6 @@ Caching is not a part of Rabl-rails. It is already in Rails itself, because cach And 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. -## Resources - ## Authors and contributors * [Christopher Cocchi-Perrier](http://github.com/ccocchi) - Creator of the project @@ -244,6 +242,6 @@ Want to make another change ? Just fork and contribute, any help is very much ap ## Original idea -* [RABL](http://github.com/nesquena/rabl) Standart RABL gem is used a lot before deciding I wanted faster views +* [RABL](http://github.com/nesquena/rabl) Standart RABL gem. I used it a lot before deciding I wanted faster views ## Copyright From c3758c0cc785f73d95153a67b527d80be8b8dd57 Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Mon, 23 Jul 2012 18:29:36 +0200 Subject: [PATCH 09/38] Add LICENSE and update README --- LICENSE | 20 ++++++++++++++++++++ README.md | 15 ++++++++------- 2 files changed, 28 insertions(+), 7 deletions(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c752847 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2010 Christopher Cocchi-Perrier + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index 4bbc517..e67a553 100644 --- a/README.md +++ b/README.md @@ -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 + respond_to :html, :json, :xml - def index - @posts = Post.order('created_at DESC') - respond_with(@posts) - end + def index + @posts = Post.order('created_at DESC') + respond_with(@posts) + end end ``` @@ -211,7 +211,7 @@ Rabl allow you to define easily your templates, even with hierarchy of 2 or 3 le object :@thread attribute :caption child :posts do - attribute :title + attribute :title child :comments do extends 'comments/base' end @@ -237,7 +237,6 @@ And caching each object in a collection can be really not effective with big col * [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 @@ -245,3 +244,5 @@ Want to make another change ? Just fork and contribute, any help is very much ap * [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 [LICENSE](http://github.com/ccocchi/rabl-rails/blob/master/LICENSE) for details. From 959192e275dd7c94f64d53e62cfc12d4e5258773 Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Tue, 24 Jul 2012 12:07:26 +0200 Subject: [PATCH 10/38] Update LICENSE --- LICENSE | 20 -------------------- MIT-LICENSE | 2 +- README.md | 2 +- 3 files changed, 2 insertions(+), 22 deletions(-) delete mode 100644 LICENSE diff --git a/LICENSE b/LICENSE deleted file mode 100644 index c752847..0000000 --- a/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2010 Christopher Cocchi-Perrier - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file 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 e67a553..d31f197 100644 --- a/README.md +++ b/README.md @@ -245,4 +245,4 @@ Want to make another change ? Just fork and contribute, any help is very much ap ## Copyright -Copyright © 2011-2012 Christopher Cocchi-Perrier. See [LICENSE](http://github.com/ccocchi/rabl-rails/blob/master/LICENSE) for details. +Copyright © 2011-2012 Christopher Cocchi-Perrier. See [MIT-LICENSE](http://github.com/ccocchi/rabl-rails/blob/master/MIT-LICENSE) for details. From 8ef2ab65777a12acee6c721cd6915303edb515e8 Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Tue, 24 Jul 2012 12:13:01 +0200 Subject: [PATCH 11/38] Use MultiJson in test also --- test/deep_nesting_test.rb | 2 +- test/non_restful_response_test.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/deep_nesting_test.rb b/test/deep_nesting_test.rb index 7aac9e8..ac9265a 100644 --- a/test/deep_nesting_test.rb +++ b/test/deep_nesting_test.rb @@ -40,7 +40,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, From 042609b5d4247ec8c0b10f3a23a8b7e4ca57d4f5 Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Tue, 24 Jul 2012 16:32:32 +0200 Subject: [PATCH 12/38] Add RablRails#render --- lib/rabl-rails/renderer.rb | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/lib/rabl-rails/renderer.rb b/lib/rabl-rails/renderer.rb index 0ee0acf..f149b4f 100644 --- a/lib/rabl-rails/renderer.rb +++ b/lib/rabl-rails/renderer.rb @@ -1,2 +1,24 @@ require 'rabl-rails/renderers/base' -require 'rabl-rails/renderers/json' \ No newline at end of file +require 'rabl-rails/renderers/json' + +module Renderer + mattr_reader :view_path + @@view_path = 'app/views' + + def render(object, template, options = {}) + format = options.delete(:format) || 'json' + source = find_template(template, format, options.delete(:view_path)) + compiled_template = Compiler.new.compile_source(source) + + # TODO: context needs to be set from options + Renderers.const_get(format.upcase!).new(context).render(compiled_template) + end + + private + + def find_template(name, format, view_path = nil) + view_path ||= self.view_path + path = File.join(view_path, "#{name}.#{format}.rabl") + File.exists?(path) : File.read(path) : nil + end +end \ No newline at end of file From 36fe3ec56ef415cfe575e5dc0274b8056996ab75 Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Tue, 24 Jul 2012 18:29:03 +0200 Subject: [PATCH 13/38] Remove test against undefined variable --- lib/rabl-rails/renderers/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rabl-rails/renderers/base.rb b/lib/rabl-rails/renderers/base.rb index 2d04abd..c96ce02 100644 --- a/lib/rabl-rails/renderers/base.rb +++ b/lib/rabl-rails/renderers/base.rb @@ -108,7 +108,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 From 0034e054868451eb510e02ec1822ee7a67248dfc Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Wed, 25 Jul 2012 18:23:41 +0200 Subject: [PATCH 14/38] Don't need to retrieve data variable from context since it's has already been copied from assigns. Modify test Context class to reflect this change --- lib/rabl-rails/renderers/base.rb | 4 ++-- test/deep_nesting_test.rb | 5 ++--- test/renderers/json_renderer_test.rb | 9 ++++----- test/test_helper.rb | 9 +++++++-- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/lib/rabl-rails/renderers/base.rb b/lib/rabl-rails/renderers/base.rb index c96ce02..e053a19 100644 --- a/lib/rabl-rails/renderers/base.rb +++ b/lib/rabl-rails/renderers/base.rb @@ -18,7 +18,7 @@ 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 = 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 @@ -54,7 +54,7 @@ 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 = data_symbol.nil? ? data : data_symbol.to_s.start_with?('@') ? instance_variable_get(data_symbol) : data.send(data_symbol) if key.to_s.start_with?('_') # glue current_value.each_pair { |k, v| diff --git a/test/deep_nesting_test.rb b/test/deep_nesting_test.rb index ac9265a..e689233 100644 --- a/test/deep_nesting_test.rb +++ b/test/deep_nesting_test.rb @@ -22,9 +22,8 @@ class DeepNestingTest < ActiveSupport::TestCase @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 diff --git a/test/renderers/json_renderer_test.rb b/test/renderers/json_renderer_test.rb index 8b376d5..c04052d 100644 --- a/test/renderers/json_renderer_test.rb +++ b/test/renderers/json_renderer_test.rb @@ -7,8 +7,7 @@ class TestJsonRenderer < ActiveSupport::TestCase @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,7 +23,7 @@ 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 @@ -52,7 +51,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 @@ -87,8 +86,8 @@ 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 diff --git a/test/test_helper.rb b/test/test_helper.rb index 1a054dc..26b144e 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 From 9e6b4db8ebd1654229560c38bff58c4473ea112d Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Wed, 25 Jul 2012 19:22:10 +0200 Subject: [PATCH 15/38] Make render work with same signature as standard RABL gem --- lib/rabl-rails.rb | 2 ++ lib/rabl-rails/renderer.rb | 44 +++++++++++++++++++++++++------------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/lib/rabl-rails.rb b/lib/rabl-rails.rb index 471fa00..5fbf960 100644 --- a/lib/rabl-rails.rb +++ b/lib/rabl-rails.rb @@ -16,6 +16,8 @@ require 'rabl-rails/railtie' require 'multi_json' module RablRails + extend Renderer + mattr_accessor :cache_templates @@cache_templates = true diff --git a/lib/rabl-rails/renderer.rb b/lib/rabl-rails/renderer.rb index f149b4f..7590150 100644 --- a/lib/rabl-rails/renderer.rb +++ b/lib/rabl-rails/renderer.rb @@ -1,24 +1,38 @@ require 'rabl-rails/renderers/base' require 'rabl-rails/renderers/json' -module Renderer - mattr_reader :view_path - @@view_path = 'app/views' +module RablRails + module Renderer + mattr_reader :view_path + @@view_path = 'app/views' - def render(object, template, options = {}) - format = options.delete(:format) || 'json' - source = find_template(template, format, options.delete(:view_path)) - compiled_template = Compiler.new.compile_source(source) + class Context + def initialize + @_assigns = {} + end + + def assigns + @_assigns + end + end + + def render(object, template, options = {}) + format = options.delete(:format) || 'json' + source = find_template(template, format, options.delete(:view_path)) + compiled_template = Compiler.new.compile_source(source) - # TODO: context needs to be set from options - Renderers.const_get(format.upcase!).new(context).render(compiled_template) - end + c = Context.new + c.assigns[compiled_template.data.to_s[1..-1]] = object if compiled_template.data + + Renderers.const_get(format.upcase!).new(c).render(compiled_template) + end - private + private - def find_template(name, format, view_path = nil) - view_path ||= self.view_path - path = File.join(view_path, "#{name}.#{format}.rabl") - File.exists?(path) : File.read(path) : nil + def find_template(name, format, view_path = nil) + view_path ||= self.view_path + path = File.join(view_path, "#{name}.#{format}.rabl") + File.exists?(path) ? File.read(path) : nil + end end end \ No newline at end of file From c60306eee54b07985d010cb0ef28b4e0d1eb7680 Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Wed, 25 Jul 2012 22:46:20 +0200 Subject: [PATCH 16/38] Add documentation for RablRails#render --- lib/rabl-rails/renderer.rb | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/rabl-rails/renderer.rb b/lib/rabl-rails/renderer.rb index 7590150..5f2c081 100644 --- a/lib/rabl-rails/renderer.rb +++ b/lib/rabl-rails/renderer.rb @@ -6,6 +6,10 @@ module RablRails mattr_reader :view_path @@view_path = 'app/views' + # + # Context class to emulate normal rendering view + # context + # class Context def initialize @_assigns = {} @@ -16,11 +20,22 @@ module RablRails 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' } + # def render(object, template, options = {}) format = options.delete(:format) || 'json' + object = options[:locals].delete(:object) if !object && options[:locals] + source = find_template(template, format, options.delete(:view_path)) compiled_template = Compiler.new.compile_source(source) - + c = Context.new c.assigns[compiled_template.data.to_s[1..-1]] = object if compiled_template.data @@ -29,6 +44,11 @@ module RablRails private + # + # 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, format, view_path = nil) view_path ||= self.view_path path = File.join(view_path, "#{name}.#{format}.rabl") From 1c466a362509867617cb4e5bddebd7b95de69ba0 Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Wed, 25 Jul 2012 22:53:09 +0200 Subject: [PATCH 17/38] Underscore private instance variable @options to avoid conflicts with view variables --- lib/rabl-rails/renderers/base.rb | 6 +++--- lib/rabl-rails/renderers/json.rb | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/rabl-rails/renderers/base.rb b/lib/rabl-rails/renderers/base.rb index e053a19..00c85d7 100644 --- a/lib/rabl-rails/renderers/base.rb +++ b/lib/rabl-rails/renderers/base.rb @@ -3,11 +3,11 @@ module RablRails class PartialError < StandardError; end class Base - attr_accessor :options + attr_accessor :_options def initialize(context) # :nodoc: @_context = context - @options = {} + @_options = {} setup_render_context end @@ -21,7 +21,7 @@ module RablRails collection_or_resource = 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 + _options[:root_name] = template.root_name format_output(output_hash) end diff --git a/lib/rabl-rails/renderers/json.rb b/lib/rabl-rails/renderers/json.rb index ef42fb5..632aaec 100644 --- a/lib/rabl-rails/renderers/json.rb +++ b/lib/rabl-rails/renderers/json.rb @@ -2,7 +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 + hash = { _options[:root_name] => hash } if _options[:root_name] && RablRails.include_json_root MultiJson.encode(hash) end end From 0dc4d16b7e3dadced979aeb5ed5677263d850572 Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Wed, 25 Jul 2012 23:48:21 +0200 Subject: [PATCH 18/38] Add CHANGELOG --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..75f47cd --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ +# CHANGELOG + +## 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 \ No newline at end of file From f9709dd0347cf2e91d70c1f10acf25b0cdb8b550 Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Wed, 25 Jul 2012 23:52:42 +0200 Subject: [PATCH 19/38] Remove sqlite3 from developent dependencies --- CHANGELOG.md | 3 ++- Gemfile.lock | 2 -- rabl-rails.gemspec | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75f47cd..037fa0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,4 +5,5 @@ * Add CHANGELOG * Remove unnused test in loop * Speed up rendering by not double copying variable from context - * Rename private variable to avoid name conflict \ No newline at end of file + * Rename private variable to avoid name conflict + * Remove sqlite3 development dependency \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index 707de63..5ed9125 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -53,7 +53,6 @@ GEM hike (~> 1.2) rack (~> 1.0) tilt (~> 1.1, != 1.3.0) - sqlite3 (1.3.6) thor (0.15.4) tilt (1.3.3) yajl-ruby (1.1.0) @@ -65,5 +64,4 @@ DEPENDENCIES actionpack (~> 3.0) rabl-rails! rspec-mocks - sqlite3 yajl-ruby diff --git a/rabl-rails.gemspec b/rabl-rails.gemspec index 5fd4a42..bf7c0bd 100644 --- a/rabl-rails.gemspec +++ b/rabl-rails.gemspec @@ -18,6 +18,5 @@ Gem::Specification.new do |s| s.add_dependency "activesupport", "~> 3.0" s.add_dependency "railties", "~> 3.0" - s.add_development_dependency "sqlite3" s.add_development_dependency "actionpack", "~> 3.0" end From 69a650c673d992d4dc994ef1a4c0a6a1d434ecd3 Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Thu, 26 Jul 2012 00:00:13 +0200 Subject: [PATCH 20/38] Bump to 0.1.1 --- lib/rabl-rails/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rabl-rails/version.rb b/lib/rabl-rails/version.rb index f73f1e5..defbe55 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.1' end From d4c434e6b0c0345046d7b00a0d4a5f17892d0757 Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Thu, 26 Jul 2012 02:21:33 +0200 Subject: [PATCH 21/38] Refactor RablRails#render to use standard library methods. Emulate a render and lookup context like Rails. --- lib/rabl-rails/renderer.rb | 62 +++++++++++++++++++++----------- lib/rabl-rails/renderers/base.rb | 1 + test/render_test.rb | 52 +++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 21 deletions(-) create mode 100644 test/render_test.rb diff --git a/lib/rabl-rails/renderer.rb b/lib/rabl-rails/renderer.rb index 5f2c081..6fb6efe 100644 --- a/lib/rabl-rails/renderer.rb +++ b/lib/rabl-rails/renderer.rb @@ -5,19 +5,54 @@ module RablRails module Renderer 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 rendering view # context # class Context - def initialize + attr_reader :format + attr_accessor :target_object + + 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 # @@ -30,29 +65,14 @@ module RablRails # an option: { format: 'xml' } # def render(object, template, options = {}) - format = options.delete(:format) || 'json' object = options[:locals].delete(:object) if !object && options[:locals] - source = find_template(template, format, options.delete(:view_path)) - compiled_template = Compiler.new.compile_source(source) + c = Context.new(template, options) + c.target_object = object - c = Context.new - c.assigns[compiled_template.data.to_s[1..-1]] = object if compiled_template.data + t = c.lookup_context.find_template(template, [], false) - Renderers.const_get(format.upcase!).new(c).render(compiled_template) - end - - private - - # - # 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, format, view_path = nil) - view_path ||= self.view_path - path = File.join(view_path, "#{name}.#{format}.rabl") - File.exists?(path) ? File.read(path) : nil + Library.instance.get_rendered_template(t.source, c) 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 e053a19..b8b3316 100644 --- a/lib/rabl-rails/renderers/base.rb +++ b/lib/rabl-rails/renderers/base.rb @@ -19,6 +19,7 @@ module RablRails # def render(template) collection_or_resource = instance_variable_get(template.data) if template.data + collection_or_resource = @_context.target_object unless collection_or_resource || template.data == false || !@_context.respond_to?(:target_object) 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 diff --git a/test/render_test.rb b/test/render_test.rb new file mode 100644 index 0000000..f0b543e --- /dev/null +++ b/test/render_test.rb @@ -0,0 +1,52 @@ +require 'test_helper' +require 'pathname' +require 'tmpdir' + +class RenderTest < ActiveSupport::TestCase + + setup do + @user = User.new(1, 'Marty') + @user.stub(:respond_to?).with(:each).and_return(false) + @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 "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 From b42f63788f3c1d7a730f46c10ce9de575985de4a Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Thu, 26 Jul 2012 14:59:28 +0200 Subject: [PATCH 22/38] Raise error when template is not found --- lib/rabl-rails/renderer.rb | 3 +++ test/render_test.rb | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/lib/rabl-rails/renderer.rb b/lib/rabl-rails/renderer.rb index 6fb6efe..2a04493 100644 --- a/lib/rabl-rails/renderer.rb +++ b/lib/rabl-rails/renderer.rb @@ -3,6 +3,8 @@ require 'rabl-rails/renderers/json' module RablRails module Renderer + class TemplateNotFound < StandardError; end + mattr_reader :view_path @@view_path = 'app/views' @@ -71,6 +73,7 @@ module RablRails c.target_object = object t = c.lookup_context.find_template(template, [], false) + raise TemplateNotFound unless t Library.instance.get_rendered_template(t.source, c) end diff --git a/test/render_test.rb b/test/render_test.rb index f0b543e..c9dd417 100644 --- a/test/render_test.rb +++ b/test/render_test.rb @@ -31,6 +31,10 @@ class RenderTest < ActiveSupport::TestCase 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, 'show') } + end test "handle path for extends" do File.open(@tmp_path + "extend.json.rabl", "w") do |f| From 8f5ebfac03ad32d7f4053f7284165c7a033c3664 Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Thu, 26 Jul 2012 15:24:20 +0200 Subject: [PATCH 23/38] Add test for RablRails#render with instance variables --- lib/rabl-rails/renderer.rb | 12 +++++++++++- test/render_test.rb | 16 ++++++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/lib/rabl-rails/renderer.rb b/lib/rabl-rails/renderer.rb index 2a04493..168b409 100644 --- a/lib/rabl-rails/renderer.rb +++ b/lib/rabl-rails/renderer.rb @@ -28,7 +28,7 @@ module RablRails end # - # Context class to emulate normal rendering view + # Context class to emulate normal Rails view # context # class Context @@ -66,6 +66,16 @@ module RablRails # 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] diff --git a/test/render_test.rb b/test/render_test.rb index c9dd417..8108f7c 100644 --- a/test/render_test.rb +++ b/test/render_test.rb @@ -17,7 +17,6 @@ class RenderTest < ActiveSupport::TestCase attributes :name } end - assert_equal %q({"user":{"name":"Marty"}}), RablRails.render(nil, 'nil', locals: { object: @user }, view_path: @tmp_path) end @@ -28,12 +27,21 @@ class RenderTest < ActiveSupport::TestCase 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, 'show') } + 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 @@ -43,7 +51,7 @@ class RenderTest < ActiveSupport::TestCase extends 'base' } end - + File.open(@tmp_path + "base.json.rabl", "w") do |f| f.puts %q{ attribute :name, as: :extended_name From 6e5fc9c83323cddbff0c0a3f0c37e841c4375faf Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Thu, 26 Jul 2012 16:37:09 +0200 Subject: [PATCH 24/38] Update CHANGELOG --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 037fa0d..e6d957f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # CHANGELOG +## 0.1.2 (unreleased) + * Add RablRails#render method (see README or source code) + ## 0.1.1 * Add CHANGELOG From 8a49001b0f5c5b45af6cf57f5ea553b1e1ac9117 Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Thu, 26 Jul 2012 16:44:51 +0200 Subject: [PATCH 25/38] Do not fail when JSON engine is not found and fallback to default --- CHANGELOG.md | 1 + lib/rabl-rails.rb | 8 +++----- lib/rabl-rails/railtie.rb | 1 + 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6d957f..4928e22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## 0.1.2 (unreleased) * Add RablRails#render method (see README or source code) + * Fix fail when JSON engine is not found. Now fallback to MultiJson.default_adapter ## 0.1.1 diff --git a/lib/rabl-rails.rb b/lib/rabl-rails.rb index 5fbf960..fe297c2 100644 --- a/lib/rabl-rails.rb +++ b/lib/rabl-rails.rb @@ -29,15 +29,13 @@ module RablRails def self.configure yield self - post_configure end def self.cache_templates? ActionController::Base.perform_caching && @@cache_templates end - - private - def self.post_configure - MultiJson.engine = self.json_engine + + def self.set_conversion_engines! + MultiJson.use(self.json_engine) rescue nil end end diff --git a/lib/rabl-rails/railtie.rb b/lib/rabl-rails/railtie.rb index 207bd61..5201adb 100644 --- a/lib/rabl-rails/railtie.rb +++ b/lib/rabl-rails/railtie.rb @@ -3,6 +3,7 @@ module RablRails initializer "rabl.initialize" do |app| ActiveSupport.on_load(:action_view) do ActionView::Template.register_template_handler :rabl, RablRails::Handlers::Rabl + RablRails.set_conversion_engines! end end end From d6f71ad6cfbb9f3572a530b01ed1870d1d2dcb55 Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Thu, 26 Jul 2012 16:54:53 +0200 Subject: [PATCH 26/38] Add .gitignore and remove Gemfile.lock from git --- .gitignore | 17 +++++++------ Gemfile.lock | 67 ---------------------------------------------------- 2 files changed, 10 insertions(+), 74 deletions(-) delete mode 100644 Gemfile.lock 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/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 5ed9125..0000000 --- a/Gemfile.lock +++ /dev/null @@ -1,67 +0,0 @@ -PATH - remote: . - specs: - rabl-rails (0.1.0) - activesupport (~> 3.0) - railties (~> 3.0) - -GEM - remote: http://rubygems.org/ - specs: - actionpack (3.2.6) - activemodel (= 3.2.6) - activesupport (= 3.2.6) - builder (~> 3.0.0) - erubis (~> 2.7.0) - journey (~> 1.0.1) - rack (~> 1.4.0) - rack-cache (~> 1.2) - rack-test (~> 0.6.1) - sprockets (~> 2.1.3) - activemodel (3.2.6) - activesupport (= 3.2.6) - builder (~> 3.0.0) - activesupport (3.2.6) - 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.4) - json (1.7.3) - multi_json (1.3.6) - rack (1.4.1) - rack-cache (1.2) - rack (>= 0.4) - rack-ssl (1.3.2) - rack - rack-test (0.6.1) - rack (>= 1.0) - railties (3.2.6) - actionpack (= 3.2.6) - activesupport (= 3.2.6) - rack-ssl (~> 1.3.2) - rake (>= 0.8.7) - rdoc (~> 3.4) - thor (>= 0.14.6, < 2.0) - rake (0.9.2.2) - rdoc (3.12) - json (~> 1.4) - rspec-mocks (2.11.1) - sprockets (2.1.3) - hike (~> 1.2) - rack (~> 1.0) - tilt (~> 1.1, != 1.3.0) - thor (0.15.4) - tilt (1.3.3) - yajl-ruby (1.1.0) - -PLATFORMS - ruby - -DEPENDENCIES - actionpack (~> 3.0) - rabl-rails! - rspec-mocks - yajl-ruby From 9ce0e2b35090bfbbfaed89f3268eca6912181d43 Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Thu, 26 Jul 2012 17:36:36 +0200 Subject: [PATCH 27/38] Update README (configuration and render) --- README.md | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d31f197..1df7dda 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,20 @@ 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 (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 @@ -218,6 +232,22 @@ child :posts do 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 @@ -226,12 +256,6 @@ Overall, Rabl-rails is **20% faster and use 10% less memory**. You can see full tests on test application repository. -## 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). - -And 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. - ## Authors and contributors * [Christopher Cocchi-Perrier](http://github.com/ccocchi) - Creator of the project From 62dfd3b7d58968ff9ac3fa000f7555cb31af31b5 Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Fri, 3 Aug 2012 15:38:43 +0200 Subject: [PATCH 28/38] Fix engines loading. Load default engines once and setter also set up engine --- lib/rabl-rails.rb | 15 +++++++++++---- lib/rabl-rails/railtie.rb | 3 ++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/rabl-rails.rb b/lib/rabl-rails.rb index fe297c2..7426b84 100644 --- a/lib/rabl-rails.rb +++ b/lib/rabl-rails.rb @@ -24,18 +24,25 @@ module RablRails mattr_accessor :include_json_root @@include_json_root = true - mattr_accessor :json_engine + mattr_reader :json_engine @@json_engine = :yajl def self.configure yield self end - + + 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.set_conversion_engines! - MultiJson.use(self.json_engine) rescue nil + def self.load_default_engines! + self.json_engine = :yajl end end diff --git a/lib/rabl-rails/railtie.rb b/lib/rabl-rails/railtie.rb index 5201adb..eee7e22 100644 --- a/lib/rabl-rails/railtie.rb +++ b/lib/rabl-rails/railtie.rb @@ -1,9 +1,10 @@ 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 - RablRails.set_conversion_engines! end end end From cf39a3f7480f2e9b45c15043a81e2390203d7432 Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Fri, 3 Aug 2012 15:39:08 +0200 Subject: [PATCH 29/38] Bump 0.1.2 --- CHANGELOG.md | 3 ++- lib/rabl-rails/version.rb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4928e22..7e43728 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,9 @@ # CHANGELOG -## 0.1.2 (unreleased) +## 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 diff --git a/lib/rabl-rails/version.rb b/lib/rabl-rails/version.rb index defbe55..50534f0 100644 --- a/lib/rabl-rails/version.rb +++ b/lib/rabl-rails/version.rb @@ -1,3 +1,3 @@ module RablRails - VERSION = '0.1.1' + VERSION = '0.1.2' end From 5c1490c24600f302c8c7d9e3c2ac76711cdc9d75 Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Tue, 11 Sep 2012 11:49:07 +0200 Subject: [PATCH 30/38] Update CHANGELOG --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e43728..c443a3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # CHANGELOG +## 0.1.3 (unreleased) + * 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 From dc66b43638107b586b9dfc694bc094ebece051d0 Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Wed, 12 Sep 2012 01:33:04 +0200 Subject: [PATCH 31/38] Replace Struct by Class to avoid stubbing respond_to? Add some tests with local methods --- test/deep_nesting_test.rb | 1 - test/renderers/json_renderer_test.rb | 25 ++++++++++++++++++------- test/test_helper.rb | 13 ++++++++++--- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/test/deep_nesting_test.rb b/test/deep_nesting_test.rb index e689233..4292877 100644 --- a/test/deep_nesting_test.rb +++ b/test/deep_nesting_test.rb @@ -19,7 +19,6 @@ 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 diff --git a/test/renderers/json_renderer_test.rb b/test/renderers/json_renderer_test.rb index c04052d..b2ab458 100644 --- a/test/renderers/json_renderer_test.rb +++ b/test/renderers/json_renderer_test.rb @@ -4,7 +4,6 @@ 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 @@ -28,6 +27,13 @@ class TestJsonRenderer < ActiveSupport::TestCase 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 @@ -44,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 @@ -75,7 +87,7 @@ 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(:context_method).and_return(true) @context.stub(:context_method).and_return('marty') @@ -86,7 +98,6 @@ class TestJsonRenderer < ActiveSupport::TestCase test "partial should be evaluated at rendering time" do # Set assigns - @data.stub(:respond_to?).with(:empty?).and_return(false) @context.assigns['user'] = @data # Stub Library#get @@ -114,17 +125,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 26b144e..7468f73 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -32,12 +32,12 @@ end class Context attr_writer :virtual_path - + def initialize @_assigns = {} @virtual_path = nil end - + def assigns @_assigns end @@ -47,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 From 8376c0d974cb21bd608bebc0a9dde52aae7aa5c2 Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Wed, 12 Sep 2012 01:34:54 +0200 Subject: [PATCH 32/38] RablRails now accepts local methods for child nodes and root objects. Local methods are used by decent_exposure or focused_controller --- lib/rabl-rails/compiler.rb | 2 +- lib/rabl-rails/renderers/base.rb | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/rabl-rails/compiler.rb b/lib/rabl-rails/compiler.rb index 7e78ea2..eb2f9a5 100644 --- a/lib/rabl-rails/compiler.rb +++ b/lib/rabl-rails/compiler.rb @@ -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/renderers/base.rb b/lib/rabl-rails/renderers/base.rb index cb9bcac..2f09f30 100644 --- a/lib/rabl-rails/renderers/base.rb +++ b/lib/rabl-rails/renderers/base.rb @@ -18,7 +18,9 @@ module RablRails # method defined by the renderer. # def render(template) - collection_or_resource = instance_variable_get(template.data) if template.data + collection_or_resource = if template.data + template.data.to_s.start_with?('@') ? instance_variable_get(template.data) : @_context.send(template.data) + end collection_or_resource = @_context.target_object unless collection_or_resource || template.data == false || !@_context.respond_to?(:target_object) output_hash = collection_or_resource.respond_to?(:each) ? render_collection(collection_or_resource, template.source) : render_resource(collection_or_resource, template.source) @@ -55,7 +57,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?('@') ? 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| From ed3592566dd8b9bebf89b721f184f98511626155 Mon Sep 17 00:00:00 2001 From: Christopher Cocchi-Perrier <cocchi.c@gmail.com> Date: Wed, 12 Sep 2012 02:47:20 +0300 Subject: [PATCH 33/38] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c443a3c..39cd874 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # CHANGELOG ## 0.1.3 (unreleased) + * 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 From 7e6da1a619a823ab5e372ab47bcf0c2e75b759f8 Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Wed, 12 Sep 2012 10:19:29 +0200 Subject: [PATCH 34/38] Bump to 0.1.3 --- CHANGELOG.md | 2 +- lib/rabl-rails/version.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39cd874..e839416 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # CHANGELOG -## 0.1.3 (unreleased) +## 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 diff --git a/lib/rabl-rails/version.rb b/lib/rabl-rails/version.rb index 50534f0..e19060c 100644 --- a/lib/rabl-rails/version.rb +++ b/lib/rabl-rails/version.rb @@ -1,3 +1,3 @@ module RablRails - VERSION = '0.1.2' + VERSION = '0.1.3' end From 4b61edad644e66d9bfa0b83f0e40e3c0c3c6c717 Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Thu, 13 Sep 2012 15:57:05 +0200 Subject: [PATCH 35/38] Locals are now passed to the renderer object. Allow to skip object or collection definition when using `respond_to` block --- lib/rabl-rails/handler.rb | 2 +- lib/rabl-rails/library.rb | 4 ++-- lib/rabl-rails/renderer.rb | 14 ++++++-------- lib/rabl-rails/renderers/base.rb | 11 ++++++++--- test/render_test.rb | 9 ++++----- test/renderers/json_renderer_test.rb | 1 + 6 files changed, 22 insertions(+), 19 deletions(-) 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/renderer.rb b/lib/rabl-rails/renderer.rb index 168b409..b74d3d0 100644 --- a/lib/rabl-rails/renderer.rb +++ b/lib/rabl-rails/renderer.rb @@ -4,7 +4,7 @@ require 'rabl-rails/renderers/json' module RablRails module Renderer class TemplateNotFound < StandardError; end - + mattr_reader :view_path @@view_path = 'app/views' @@ -33,7 +33,6 @@ module RablRails # class Context attr_reader :format - attr_accessor :target_object def initialize(path, options) @virtual_path = path @@ -56,17 +55,17 @@ module RablRails @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 + # 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: @@ -80,12 +79,11 @@ module RablRails object = options[:locals].delete(:object) if !object && options[:locals] c = Context.new(template, options) - c.target_object = object - t = c.lookup_context.find_template(template, [], false) + raise TemplateNotFound unless t - Library.instance.get_rendered_template(t.source, c) + 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 2f09f30..c336de3 100644 --- a/lib/rabl-rails/renderers/base.rb +++ b/lib/rabl-rails/renderers/base.rb @@ -5,9 +5,10 @@ module RablRails class Base attr_accessor :_options - def initialize(context) # :nodoc: + def initialize(context, locals = nil) # :nodoc: @_context = context @_options = {} + @_resource = locals[:resource] if locals setup_render_context end @@ -19,9 +20,13 @@ module RablRails # def render(template) collection_or_resource = if template.data - template.data.to_s.start_with?('@') ? instance_variable_get(template.data) : @_context.send(template.data) + if @_context.respond_to?(template.data) + @_context.send(template.data) + else + instance_variable_get(template.data) + end end - collection_or_resource = @_context.target_object unless collection_or_resource || template.data == false || !@_context.respond_to?(:target_object) + 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 diff --git a/test/render_test.rb b/test/render_test.rb index 8108f7c..9b4b26d 100644 --- a/test/render_test.rb +++ b/test/render_test.rb @@ -6,7 +6,6 @@ class RenderTest < ActiveSupport::TestCase setup do @user = User.new(1, 'Marty') - @user.stub(:respond_to?).with(:each).and_return(false) @tmp_path = Pathname.new(Dir.mktmpdir) end @@ -29,16 +28,16 @@ class RenderTest < ActiveSupport::TestCase 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 } + node(:username) { |_| @user.name } } end assert_equal %q({"username":"Marty"}), RablRails.render(nil, 'instance', view_path: @tmp_path, locals: { user: @user }) @@ -51,7 +50,7 @@ class RenderTest < ActiveSupport::TestCase extends 'base' } end - + File.open(@tmp_path + "base.json.rabl", "w") do |f| f.puts %q{ attribute :name, as: :extended_name diff --git a/test/renderers/json_renderer_test.rb b/test/renderers/json_renderer_test.rb index b2ab458..93d3ab0 100644 --- a/test/renderers/json_renderer_test.rb +++ b/test/renderers/json_renderer_test.rb @@ -89,6 +89,7 @@ class TestJsonRenderer < ActiveSupport::TestCase end test "node with context method call" do + @context.stub(:respond_to?).with(:@data).and_return(false) @context.stub(:respond_to?).with(:context_method).and_return(true) @context.stub(:context_method).and_return('marty') proc = lambda { |object| context_method } From 395a3d743969cddde7b31ff905793f31570174d6 Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Thu, 13 Sep 2012 16:04:43 +0200 Subject: [PATCH 36/38] Use locals in responder. Responder now works out of the box with Devise --- lib/rabl-rails/responder.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/rabl-rails/responder.rb b/lib/rabl-rails/responder.rb index fb094bc..0a03c3c 100644 --- a/lib/rabl-rails/responder.rb +++ b/lib/rabl-rails/responder.rb @@ -6,14 +6,14 @@ module RablRails # class Responder < ActionController::Responder protected - + def api_behavior(error) rabl_options = options.merge(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) + controller.default_render rabl_options.merge!(status: :created, location: api_location, locals: { resource: resource }) else head :no_content end From e9f0c69f155f40a4a4127e876e27305dfc6e3f53 Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Thu, 13 Sep 2012 16:05:29 +0200 Subject: [PATCH 37/38] Default template to render can be defined per controller --- lib/rabl-rails/responder.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/rabl-rails/responder.rb b/lib/rabl-rails/responder.rb index 0a03c3c..997bf80 100644 --- a/lib/rabl-rails/responder.rb +++ b/lib/rabl-rails/responder.rb @@ -5,10 +5,15 @@ module RablRails # 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: RablRails.responder_default_template) + rabl_options = options.merge(template: @api_template || RablRails.responder_default_template) if get? controller.default_render rabl_options From 04defc9a1906b5cb172489b1dc2a33e584fd0bc8 Mon Sep 17 00:00:00 2001 From: ccocchi <cocchi.c@gmail.com> Date: Thu, 13 Sep 2012 16:08:42 +0200 Subject: [PATCH 38/38] Add CHANGELOG entries --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e839416..7668f0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # 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)