--- /dev/null
+tmp
+Gemfile.lock
+Gemfile.old.lock
+.ruby-version
--- /dev/null
+language: ruby
+
+rvm:
+ - 1.9.3
+ - 2.0.0
+ - 2.1.0
+ - ruby-head
+ - jruby-19mode
+ - rbx
+
+gemfile:
+ - Gemfile.old
+ - Gemfile
+
+matrix:
+ allow_failures:
+ - rvm: 2.1.0
+ - rvm: ruby-head
+ - rvm: jruby-19mode
+ - rvm: rbx
+
+notifications:
+ email: false
--- /dev/null
+# Changelog
+
+2.0.4
+-----
+* [Add cache_if! to conditionally cache JSON fragments](https://github.com/rails/jbuilder/commit/commit/14a5afd8a2c939a6fd710d355a194c114db96eb2)
+
+2.0.3
+-----
+* [Pass options when calling cache_fragment_name](https://github.com/rails/jbuilder/commit/07c2cc7486fe9ef423d7bc821b83f6d485f330e0)
+
+2.0.2
+-----
+* [Fix Dependency Tracking fail to detect single-quoted partial correctly](https://github.com/rails/jbuilder/commit/448679a6d3098eb34d137f782a05f1767711991a)
+* [Prevent Dependency Tracker constants leaking into global namespace](https://github.com/rails/jbuilder/commit/3544b288b63f504f46fa8aafd1d17ee198d77536)
+
+2.0.1
+-----
+* [Dependency tracking support for Rails 3 with cache_digest gem](https://github.com/rails/jbuilder/commit/6b471d7a38118e8f7645abec21955ef793401daf)
+
+2.0.0
+-----
+* [Respond to PUT/PATCH API request with :ok](https://github.com/rails/jbuilder/commit/9dbce9c12181e89f8f472ac23c764ffe8438040a)
+* [Remove Ruby 1.8 support](https://github.com/rails/jbuilder/commit/d53fff42d91f33d662eafc2561c4236687ecf6c9)
+* [Remove deprecated two argument block call](https://github.com/rails/jbuilder/commit/07a35ee7e79ae4b06dba9dbff5c4e07c1e627218)
+* [Make Jbuilder object initialize with single hash](https://github.com/rails/jbuilder/commit/38bf551db0189327aaa90b9be010c0d1b792c007)
+* [Track template dependencies](https://github.com/rails/jbuilder/commit/8e73cea39f60da1384afd687cc8e5e399630d8cc)
+* [Expose merge! method](https://github.com/rails/jbuilder/commit/0e2eb47f6f3c01add06a1a59b37cdda8baf24f29)
+
+1.5.3
+-----
+* [Generators add `:id` column by default](https://github.com/rails/jbuilder/commit/0b52b86773e48ac2ce35d4155c7b70ad8b3e8937)
+
+1.5.2
+-----
+* [Nil-collection should be treated as empty array](https://github.com/rails/jbuilder/commit/2f700bb00ab663c6b7fcb28d2967aeb989bd43c7)
+
+1.5.1
+-----
+* [Expose template lookup options](https://github.com/rails/jbuilder/commit/404c18dee1af96ac6d8052a04062629ef1db2945)
+
+1.5.0
+-----
+* [Do not perform any caching when `controller.perform_caching` is false](https://github.com/rails/jbuilder/commit/94633facde1ac43580f8cd5e13ae9cc83e1da8f4)
+* [Add partial collection rendering](https://github.com/rails/jbuilder/commit/e8c10fc885e41b18178aaf4dcbc176961c928d76)
+* [Deprecate extract! calling private methods](https://github.com/rails/jbuilder/commit/b9e19536c2105d7f2e813006bbcb8ca5730d28a3)
+* [Add array of partials rendering](https://github.com/rails/jbuilder/commit/7d7311071720548047f98f14ad013c560b8d9c3a)
+
+1.4.2
+-----
+* [Require MIME dependency explicitly](https://github.com/rails/jbuilder/commit/b1ed5ac4f08b056f8839b4b19b43562e81e02a59)
+
+1.4.1
+-----
+* [Removed deprecated positioned arguments initializer support](https://github.com/rails/jbuilder/commit/6e03e0452073eeda77e6dfe66aa31e5ec67a3531)
+* [Deprecate two-arguments block calling](https://github.com/rails/jbuilder/commit/2b10bb058bb12bc782cbcc16f6ec67b489e5ed43)
+
+1.4.0
+-----
+* [Add quick collection attribute extraction](https://github.com/rails/jbuilder/commit/c2b966cf653ea4264fbb008b8cc6ce5359ebe40a)
+* [Block has priority over attributes extraction](https://github.com/rails/jbuilder/commit/77c24766362c02769d81dac000b1879a9e4d4a00)
+* [Meaningfull error messages when adding properties to null](https://github.com/rails/jbuilder/commit/e26764602e34b3772e57e730763d512e59489e3b)
+* [Do not enforce template format, enforce handlers instead](https://github.com/rails/jbuilder/commit/72576755224b15da45e50cbea877679800ab1398)
+
+1.3.0
+-----
+* [Add nil! method for nil JSON](https://github.com/rails/jbuilder/commit/822a906f68664f61a1209336bb681077692c8475)
+
+1.2.1
+-----
+* [Added explicit dependency for MultiJson](https://github.com/rails/jbuilder/commit/4d58eacb6cd613679fb243484ff73a79bbbff2d2
+
+1.2.0
+-----
+* Multiple documentation improvements and internal refactoring
+* [Fixes fragment caching to work with latest digests](https://github.com/rails/jbuilder/commit/da937d6b8732124074c612abb7ff38868d1d96c0)
+
+1.0.2
+-----
+* [Support non-Enumerable collections](https://github.com/rails/jbuilder/commit/4c20c59bf8131a1e419bb4ebf84f2b6bdcb6b0cf)
+* [Ensure that the default URL is in json format](https://github.com/rails/jbuilder/commit/0b46782fb7b8c34a3c96afa801fe27a5a97118a4)
+
+1.0.0
+-----
+* Adopt Semantic Versioning
+* Add rails generators
--- /dev/null
+source 'https://rubygems.org'
+
+gemspec
+
+gem 'rake'
+gem 'railties', '~> 4.0.0'
+gem 'actionpack', '~> 4.0.0'
+gem 'mocha', require: false
+
+platforms :rbx do
+ gem 'rubysl', '~> 2.0'
+ gem 'json', '~> 1.8'
+ gem 'rubysl-test-unit'
+ gem 'rubinius-developer_tools'
+end
--- /dev/null
+source 'https://rubygems.org'
+
+gemspec
+
+gem 'rake'
+gem 'mocha', :require => false
+gem 'actionpack', '~> 3.0'
+
+platforms :rbx do
+ gem 'rubysl', '~> 2.0'
+ gem 'json', '~> 1.8'
+ gem 'rubysl-test-unit'
+ gem 'rubinius-developer_tools'
+end
--- /dev/null
+Copyright (c) 2011-2014 David Heinemeier Hansson, 37signals
+
+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.
--- /dev/null
+Jbuilder [](https://travis-ci.org/rails/jbuilder) [](https://rubygems.org/gems/jbuilder) [](https://codeclimate.com/github/rails/jbuilder)
+========
+
+Jbuilder gives you a simple DSL for declaring JSON structures that beats massaging giant hash structures. This is particularly helpful when the generation process is fraught with conditionals and loops. Here's a simple example:
+
+``` ruby
+Jbuilder.encode do |json|
+ json.content format_content(@message.content)
+ json.(@message, :created_at, :updated_at)
+
+ json.author do
+ json.name @message.creator.name.familiar
+ json.email_address @message.creator.email_address_with_name
+ json.url url_for(@message.creator, format: :json)
+ end
+
+ if current_user.admin?
+ json.visitors calculate_visitors(@message)
+ end
+
+ json.comments @message.comments, :content, :created_at
+
+ json.attachments @message.attachments do |attachment|
+ json.filename attachment.filename
+ json.url url_for(attachment)
+ end
+end
+```
+
+This will build the following structure:
+
+``` javascript
+{
+ "content": "<p>This is <i>serious</i> monkey business</p>",
+ "created_at": "2011-10-29T20:45:28-05:00",
+ "updated_at": "2011-10-29T20:45:28-05:00",
+
+ "author": {
+ "name": "David H.",
+ "email_address": "'David Heinemeier Hansson' <david@heinemeierhansson.com>",
+ "url": "http://example.com/users/1-david.json"
+ },
+
+ "visitors": 15,
+
+ "comments": [
+ { "content": "Hello everyone!", "created_at": "2011-10-29T20:45:28-05:00" },
+ { "content": "To you my good sir!", "created_at": "2011-10-29T20:47:28-05:00" }
+ ],
+
+ "attachments": [
+ { "filename": "forecast.xls", "url": "http://example.com/downloads/forecast.xls" },
+ { "filename": "presentation.pdf", "url": "http://example.com/downloads/presentation.pdf" }
+ ]
+}
+```
+
+To define attribute and structure names dynamically, use the `set!` method:
+
+``` ruby
+json.set! :author do
+ json.set! :name, 'David'
+end
+
+# => "author": { "name": "David" }
+```
+
+Top level arrays can be handled directly. Useful for index and other collection actions.
+
+``` ruby
+# @people = People.all
+json.array! @people do |person|
+ json.name person.name
+ json.age calculate_age(person.birthday)
+end
+
+# => [ { "name": "David", "age": 32 }, { "name": "Jamie", "age": 31 } ]
+```
+
+You can also extract attributes from array directly.
+
+``` ruby
+# @people = People.all
+json.array! @people, :id, :name
+
+# => [ { "id": 1, "name": "David" }, { "id": 2, "name": "Jamie" } ]
+```
+
+Jbuilder objects can be directly nested inside each other. Useful for composing objects.
+
+``` ruby
+class Person
+ # ... Class Definition ... #
+ def to_builder
+ Jbuilder.new do |person|
+ person.(self, :name, :age)
+ end
+ end
+end
+
+class Company
+ # ... Class Definition ... #
+ def to_builder
+ Jbuilder.new do |company|
+ company.name name
+ company.president president.to_builder
+ end
+ end
+end
+
+company = Company.new('Doodle Corp', Person.new('John Stobs', 58))
+company.to_builder.target!
+
+# => {"name":"Doodle Corp","president":{"name":"John Stobs","age":58}}
+```
+
+You can either use Jbuilder stand-alone or directly as an ActionView template language. When required in Rails, you can create views ala show.json.jbuilder (the json is already yielded):
+
+``` ruby
+# Any helpers available to views are available to the builder
+json.content format_content(@message.content)
+json.(@message, :created_at, :updated_at)
+
+json.author do
+ json.name @message.creator.name.familiar
+ json.email_address @message.creator.email_address_with_name
+ json.url url_for(@message.creator, format: :json)
+end
+
+if current_user.admin?
+ json.visitors calculate_visitors(@message)
+end
+```
+
+
+You can use partials as well. The following will render the file
+`views/comments/_comments.json.jbuilder`, and set a local variable
+`comments` with all this message's comments, which you can use inside
+the partial.
+
+```ruby
+json.partial! 'comments/comments', comments: @message.comments
+```
+
+It's also possible to render collections of partials:
+
+```ruby
+json.array! @posts, partial: 'posts/post', as: :post
+
+# or
+
+json.partial! 'posts/post', collection: @posts, as: :post
+
+# or
+
+json.partial! partial: 'posts/post', collection: @posts, as: :post
+
+# or
+
+json.comments @post.comments, partial: 'comment/comment', as: :comment
+```
+
+You can explicitly make Jbuilder object return null if you want:
+
+``` ruby
+json.extract! @post, :id, :title, :content, :published_at
+json.author do
+ if @post.anonymous?
+ json.null! # or json.nil!
+ else
+ json.first_name @post.author_first_name
+ json.last_name @post.author_last_name
+ end
+end
+```
+
+Fragment caching is supported, it uses `Rails.cache` and works like caching in HTML templates:
+
+```ruby
+json.cache! ['v1', @person], expires_in: 10.minutes do
+ json.extract! @person, :name, :age
+end
+```
+
+You can also conditionally cache a block by using `cache_if!` like this:
+
+```ruby
+json.cache_if! !admin?, ['v1', @person], expires_in: 10.minutes do
+ json.extract! @person, :name, :age
+end
+```
+
+Keys can be auto formatted using `key_format!`, this can be used to convert keynames from the standard ruby_format to CamelCase:
+
+``` ruby
+json.key_format! camelize: :lower
+json.first_name 'David'
+
+# => { "firstName": "David" }
+```
+
+You can set this globally with the class method `key_format` (from inside your environment.rb for example):
+
+``` ruby
+Jbuilder.key_format camelize: :lower
+```
+
+Faster JSON backends
+--------------------
+
+Jbuilder uses MultiJson, which by default will use the JSON gem. That gem is currently tangled with ActiveSupport's all-Ruby #to_json implementation, which is slow (work is being done to correct this for a future version of Rails). For faster Jbuilder rendering, you can specify something like the Yajl JSON generator instead. You'll need to include the yajl-ruby gem in your Gemfile and then set the following configuration for MultiJson:
+
+``` ruby
+require 'multi_json'
+MultiJson.use :yajl
+ ```
--- /dev/null
+require 'bundler'
+require 'rake/testtask'
+
+Bundler.require
+
+Rake::TestTask.new do |test|
+ if /old$/ === ENV['BUNDLE_GEMFILE']
+ test.test_files = %w(test/jbuilder_template_test.rb test/jbuilder_test.rb)
+ else
+ test.test_files = FileList['test/*_test.rb']
+ end
+end
+
+task :default => :test
--- /dev/null
+ruby-jbuilder (2.0.4-1) UNRELEASED; urgency=medium
+
+ * Initial release (Closes: #nnnn)
+
+ -- MAINTAINER <valtri@myriad14.zcu.cz> Wed, 12 Mar 2014 00:15:53 +0100
--- /dev/null
+Source: ruby-jbuilder
+Section: ruby
+Priority: optional
+Maintainer: Debian Ruby Extras Maintainers <pkg-ruby-extras-maintainers@lists.alioth.debian.org>
+Uploaders: <>
+Build-Depends: debhelper (>= 7.0.50~), gem2deb (>= 0.6.1~)
+Standards-Version: 3.9.4
+#Vcs-Git: git://anonscm.debian.org/pkg-ruby-extras/ruby-jbuilder.git
+#Vcs-Browser: http://anonscm.debian.org/gitweb/?p=pkg-ruby-extras/ruby-jbuilder.git;a=summary
+Homepage: https://github.com/rails/jbuilder
+XS-Ruby-Versions: all
+
+Package: ruby-jbuilder
+Architecture: all
+XB-Ruby-Versions: ${ruby:Versions}
+Depends: ${shlibs:Depends}, ${misc:Depends}, ruby | ruby-interpreter
+# activesupport (>= 3.0.0), multi_json (>= 1.2.0)
+Description: Create JSON structures via a Builder-style DSL
+ <insert long description, indented with spaces>
--- /dev/null
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: jbuilder
+Source: FIXME <http://example.com/>
+
+Files: *
+Copyright: <years> <put author's name and email here>
+ <years> <likewise for another author>
+License: GPL-2+ (FIXME)
+
+Files: debian/*
+Copyright: 2014 <>
+License: GPL-2+ (FIXME)
+Comment: the Debian packaging is licensed under the same terms as the original package.
+
+License: GPL-2+ (FIXME)
+ This program is free software; you can redistribute it
+ and/or modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later
+ version.
+ .
+ This program is distributed in the hope that it will be
+ useful, but WITHOUT ANY WARRANTY; without even the implied
+ warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ PURPOSE. See the GNU General Public License for more
+ details.
+ .
+ You should have received a copy of the GNU General Public
+ License along with this package; if not, write to the Free
+ Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ Boston, MA 02110-1301 USA
+ .
+ On Debian systems, the full text of the GNU General Public
+ License version 2 can be found in the file
+ `/usr/share/common-licenses/GPL-2'.
--- /dev/null
+# FIXME: READMEs found
+# README.md
--- /dev/null
+---
+- test/jbuilder_dependency_tracker_test.rb
+- test/jbuilder_generator_test.rb
+- test/jbuilder_template_test.rb
+- test/jbuilder_test.rb
+- test/scaffold_controller_generator_test.rb
--- /dev/null
+#!/usr/bin/make -f
+#export DH_VERBOSE=1
+#
+# Uncomment to ignore all test failures (but the tests will run anyway)
+#export DH_RUBY_IGNORE_TESTS=all
+#
+# Uncomment to ignore some test failures (but the tests will run anyway).
+# Valid values:
+#export DH_RUBY_IGNORE_TESTS=ruby1.9.1 ruby2.0 require-rubygems
+#
+# If you need to specify the .gemspec (eg there is more than one)
+#export DH_RUBY_GEMSPEC=gem.gemspec
+
+%:
+ dh $@ --buildsystem=ruby --with ruby
--- /dev/null
+3.0 (quilt)
--- /dev/null
+version=3
+http://pkg-ruby-extras.alioth.debian.org/cgi-bin/gemwatch/jbuilder .*/jbuilder-(.*).tar.gz
--- /dev/null
+Gem::Specification.new do |s|
+ s.name = 'jbuilder'
+ s.version = '2.0.4'
+ s.author = 'David Heinemeier Hansson'
+ s.email = 'david@37signals.com'
+ s.summary = 'Create JSON structures via a Builder-style DSL'
+ s.homepage = 'https://github.com/rails/jbuilder'
+ s.license = 'MIT'
+
+ s.required_ruby_version = '>= 1.9.3'
+
+ s.add_dependency 'activesupport', '>= 3.0.0'
+ s.add_dependency 'multi_json', '>= 1.2.0'
+
+ s.files = `git ls-files`.split("\n")
+ s.test_files = `git ls-files -- test/*`.split("\n")
+end
--- /dev/null
+require 'rails/generators/named_base'
+require 'rails/generators/resource_helpers'
+
+module Rails
+ module Generators
+ class JbuilderGenerator < NamedBase # :nodoc:
+ include Rails::Generators::ResourceHelpers
+
+ source_root File.expand_path('../templates', __FILE__)
+
+ argument :attributes, type: :array, default: [], banner: 'field:type field:type'
+
+ def create_root_folder
+ path = File.join('app/views', controller_file_path)
+ empty_directory path unless File.directory?(path)
+ end
+
+ def copy_view_files
+ %w(index show).each do |view|
+ filename = filename_with_extensions(view)
+ template filename, File.join('app/views', controller_file_path, filename)
+ end
+ end
+
+
+ protected
+ def attributes_names
+ [:id] + super
+ end
+
+ def filename_with_extensions(name)
+ [name, :json, :jbuilder] * '.'
+ end
+
+ def attributes_list_with_timestamps
+ attributes_list(attributes_names + %w(created_at updated_at))
+ end
+
+ def attributes_list(attributes = attributes_names)
+ if self.attributes.any? {|attr| attr.name == 'password' && attr.type == :digest}
+ attributes = attributes.reject {|name| %w(password password_confirmation).include? name}
+ end
+
+ attributes.map { |a| ":#{a}"} * ', '
+ end
+ end
+ end
+end
--- /dev/null
+require 'rails/generators'
+require 'rails/generators/rails/scaffold_controller/scaffold_controller_generator'
+
+module Rails
+ module Generators
+ class ScaffoldControllerGenerator
+ source_root File.expand_path('../templates', __FILE__)
+
+ hook_for :jbuilder, default: true
+ end
+ end
+end
\ No newline at end of file
--- /dev/null
+<% if namespaced? -%>
+require_dependency "<%= namespaced_file_path %>/application_controller"
+
+<% end -%>
+<% module_namespacing do -%>
+class <%= controller_class_name %>Controller < ApplicationController
+ before_action :set_<%= singular_table_name %>, only: [:show, :edit, :update, :destroy]
+
+ # GET <%= route_url %>
+ # GET <%= route_url %>.json
+ def index
+ @<%= plural_table_name %> = <%= orm_class.all(class_name) %>
+ end
+
+ # GET <%= route_url %>/1
+ # GET <%= route_url %>/1.json
+ def show
+ end
+
+ # GET <%= route_url %>/new
+ def new
+ @<%= singular_table_name %> = <%= orm_class.build(class_name) %>
+ end
+
+ # GET <%= route_url %>/1/edit
+ def edit
+ end
+
+ # POST <%= route_url %>
+ # POST <%= route_url %>.json
+ def create
+ @<%= singular_table_name %> = <%= orm_class.build(class_name, "#{singular_table_name}_params") %>
+
+ respond_to do |format|
+ if @<%= orm_instance.save %>
+ format.html { redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully created.'" %> }
+ format.json { render action: 'show', status: :created, location: <%= "@#{singular_table_name}" %> }
+ else
+ format.html { render action: 'new' }
+ format.json { render json: <%= "@#{orm_instance.errors}" %>, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ # PATCH/PUT <%= route_url %>/1
+ # PATCH/PUT <%= route_url %>/1.json
+ def update
+ respond_to do |format|
+ if @<%= orm_instance.update("#{singular_table_name}_params") %>
+ format.html { redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully updated.'" %> }
+ format.json { render action: 'show', status: :ok, location: <%= "@#{singular_table_name}" %> }
+ else
+ format.html { render action: 'edit' }
+ format.json { render json: <%= "@#{orm_instance.errors}" %>, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ # DELETE <%= route_url %>/1
+ # DELETE <%= route_url %>/1.json
+ def destroy
+ @<%= orm_instance.destroy %>
+ respond_to do |format|
+ format.html { redirect_to <%= index_helper %>_url }
+ format.json { head :no_content }
+ end
+ end
+
+ private
+ # Use callbacks to share common setup or constraints between actions.
+ def set_<%= singular_table_name %>
+ @<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
+ end
+
+ # Never trust parameters from the scary internet, only allow the white list through.
+ def <%= "#{singular_table_name}_params" %>
+ <%- if attributes_names.empty? -%>
+ params[<%= ":#{singular_table_name}" %>]
+ <%- else -%>
+ params.require(<%= ":#{singular_table_name}" %>).permit(<%= attributes_names.map { |name| ":#{name}" }.join(', ') %>)
+ <%- end -%>
+ end
+end
+<% end -%>
--- /dev/null
+json.array!(@<%= plural_table_name %>) do |<%= singular_table_name %>|
+ json.extract! <%= singular_table_name %>, <%= attributes_list %>
+ json.url <%= singular_table_name %>_url(<%= singular_table_name %>, format: :json)
+end
--- /dev/null
+json.extract! @<%= singular_table_name %>, <%= attributes_list_with_timestamps %>
--- /dev/null
+require 'active_support/core_ext/array/access'
+require 'active_support/core_ext/enumerable'
+require 'active_support/core_ext/hash'
+require 'multi_json'
+
+begin
+ require 'active_support/proxy_object'
+ JbuilderProxy = ActiveSupport::ProxyObject
+rescue LoadError
+ require 'active_support/basic_object'
+ JbuilderProxy = ActiveSupport::BasicObject
+end
+
+class Jbuilder < JbuilderProxy
+ class NullError < ::NoMethodError
+ def initialize(key)
+ super "Failed to add #{key.to_s.inspect} property to null object"
+ end
+ end
+
+ class KeyFormatter
+ def initialize(*args)
+ @format = {}
+ @cache = {}
+
+ options = args.extract_options!
+ args.each do |name|
+ @format[name] = []
+ end
+ options.each do |name, paramaters|
+ @format[name] = paramaters
+ end
+ end
+
+ def initialize_copy(original)
+ @cache = {}
+ end
+
+ def format(key)
+ @cache[key] ||= @format.inject(key.to_s) do |result, args|
+ func, args = args
+ if ::Proc === func
+ func.call result, *args
+ else
+ result.send func, *args
+ end
+ end
+ end
+ end
+
+ # Yields a builder and automatically turns the result into a JSON string
+ def self.encode(*args, &block)
+ new(*args, &block).target!
+ end
+
+ @@key_formatter = KeyFormatter.new
+ @@ignore_nil = false
+
+ def initialize(options = {}, &block)
+ @attributes = {}
+
+ @key_formatter = options.fetch(:key_formatter){ @@key_formatter.clone }
+ @ignore_nil = options.fetch(:ignore_nil, @@ignore_nil)
+ yield self if block
+ end
+
+ BLANK = ::Object.new
+
+ def set!(key, value = BLANK, *args, &block)
+
+ result = if block
+ if BLANK != value
+ # json.comments @post.comments { |comment| ... }
+ # { "comments": [ { ... }, { ... } ] }
+ _scope{ array! value, &block }
+ else
+ # json.comments { ... }
+ # { "comments": ... }
+ _scope { yield self }
+ end
+ elsif args.empty?
+ if ::Jbuilder === value
+ # json.age 32
+ # json.person another_jbuilder
+ # { "age": 32, "person": { ... }
+ value.attributes!
+ else
+ # json.age 32
+ # { "age": 32 }
+ value
+ end
+ elsif _mapable_arguments?(value, *args)
+ # json.comments @post.comments, :content, :created_at
+ # { "comments": [ { "content": "hello", "created_at": "..." }, { "content": "world", "created_at": "..." } ] }
+ _scope{ array! value, *args }
+ else
+ # json.author @post.creator, :name, :email_address
+ # { "author": { "name": "David", "email_address": "david@loudthinking.com" } }
+ _scope { extract! value, *args }
+ end
+
+ _set_value key, result
+ end
+
+ alias_method :method_missing, :set!
+ private :method_missing
+
+
+ # Specifies formatting to be applied to the key. Passing in a name of a function
+ # will cause that function to be called on the key. So :upcase will upper case
+ # the key. You can also pass in lambdas for more complex transformations.
+ #
+ # Example:
+ #
+ # json.key_format! :upcase
+ # json.author do
+ # json.name "David"
+ # json.age 32
+ # end
+ #
+ # { "AUTHOR": { "NAME": "David", "AGE": 32 } }
+ #
+ # You can pass parameters to the method using a hash pair.
+ #
+ # json.key_format! camelize: :lower
+ # json.first_name "David"
+ #
+ # { "firstName": "David" }
+ #
+ # Lambdas can also be used.
+ #
+ # json.key_format! ->(key){ "_" + key }
+ # json.first_name "David"
+ #
+ # { "_first_name": "David" }
+ #
+ def key_format!(*args)
+ @key_formatter = KeyFormatter.new(*args)
+ end
+
+ # Same as the instance method key_format! except sets the default.
+ def self.key_format(*args)
+ @@key_formatter = KeyFormatter.new(*args)
+ end
+
+ # If you want to skip adding nil values to your JSON hash. This is useful
+ # for JSON clients that don't deal well with nil values, and would prefer
+ # not to receive keys which have null values.
+ #
+ # Example:
+ # json.ignore_nil! false
+ # json.id User.new.id
+ #
+ # { "id": null }
+ #
+ # json.ignore_nil!
+ # json.id User.new.id
+ #
+ # {}
+ #
+ def ignore_nil!(value = true)
+ @ignore_nil = value
+ end
+
+ # Same as instance method ignore_nil! except sets the default.
+ def self.ignore_nil(value = true)
+ @@ignore_nil = value
+ end
+
+ # Turns the current element into an array and yields a builder to add a hash.
+ #
+ # Example:
+ #
+ # json.comments do
+ # json.child! { json.content "hello" }
+ # json.child! { json.content "world" }
+ # end
+ #
+ # { "comments": [ { "content": "hello" }, { "content": "world" } ]}
+ #
+ # More commonly, you'd use the combined iterator, though:
+ #
+ # json.comments(@post.comments) do |comment|
+ # json.content comment.formatted_content
+ # end
+ def child!
+ @attributes = [] unless ::Array === @attributes
+ @attributes << _scope { yield self }
+ end
+
+ # Turns the current element into an array and iterates over the passed collection, adding each iteration as
+ # an element of the resulting array.
+ #
+ # Example:
+ #
+ # json.array!(@people) do |person|
+ # json.name person.name
+ # json.age calculate_age(person.birthday)
+ # end
+ #
+ # [ { "name": David", "age": 32 }, { "name": Jamie", "age": 31 } ]
+ #
+ # If you are using Ruby 1.9+, you can use the call syntax instead of an explicit extract! call:
+ #
+ # json.(@people) { |person| ... }
+ #
+ # It's generally only needed to use this method for top-level arrays. If you have named arrays, you can do:
+ #
+ # json.people(@people) do |person|
+ # json.name person.name
+ # json.age calculate_age(person.birthday)
+ # end
+ #
+ # { "people": [ { "name": David", "age": 32 }, { "name": Jamie", "age": 31 } ] }
+ #
+ # If you omit the block then you can set the top level array directly:
+ #
+ # json.array! [1, 2, 3]
+ #
+ # [1,2,3]
+ def array!(collection = [], *attributes, &block)
+ @attributes = if block
+ _map_collection(collection, &block)
+ elsif attributes.any?
+ _map_collection(collection) { |element| extract! element, *attributes }
+ else
+ collection
+ end
+ end
+
+ # Extracts the mentioned attributes or hash elements from the passed object and turns them into attributes of the JSON.
+ #
+ # Example:
+ #
+ # @person = Struct.new(:name, :age).new('David', 32)
+ #
+ # or you can utilize a Hash
+ #
+ # @person = { name: 'David', age: 32 }
+ #
+ # json.extract! @person, :name, :age
+ #
+ # { "name": David", "age": 32 }, { "name": Jamie", "age": 31 }
+ #
+ # You can also use the call syntax instead of an explicit extract! call:
+ #
+ # json.(@person, :name, :age)
+ def extract!(object, *attributes)
+ if ::Hash === object
+ _extract_hash_values(object, *attributes)
+ else
+ _extract_method_values(object, *attributes)
+ end
+ end
+
+ def call(object, *attributes, &block)
+ if block
+ array! object, &block
+ else
+ extract! object, *attributes
+ end
+ end
+
+ # Returns the nil JSON.
+ def nil!
+ @attributes = nil
+ end
+
+ alias_method :null!, :nil!
+
+ # Returns the attributes of the current builder.
+ def attributes!
+ @attributes
+ end
+
+ # Merges hash or array into current builder.
+ def merge!(hash_or_array)
+ if ::Array === hash_or_array
+ @attributes = [] unless ::Array === @attributes
+ @attributes.concat hash_or_array
+ else
+ @attributes.update hash_or_array
+ end
+ end
+
+ # Encodes the current builder as JSON.
+ def target!
+ ::MultiJson.dump(@attributes)
+ end
+
+ private
+
+ def _extract_hash_values(object, *attributes)
+ attributes.each{ |key| _set_value key, object.fetch(key) }
+ end
+
+ def _extract_method_values(object, *attributes)
+ attributes.each{ |key| _set_value key, object.public_send(key) }
+ end
+
+ def _set_value(key, value)
+ raise NullError, key if @attributes.nil?
+ unless @ignore_nil && value.nil?
+ @attributes[@key_formatter.format(key)] = value
+ end
+ end
+
+ def _map_collection(collection)
+ return [] if collection.nil?
+
+ collection.map do |element|
+ _scope { yield element }
+ end
+ end
+
+ def _scope
+ parent_attributes, parent_formatter = @attributes, @key_formatter
+ @attributes = {}
+ yield
+ @attributes
+ ensure
+ @attributes, @key_formatter = parent_attributes, parent_formatter
+ end
+
+ def _mapable_arguments?(value, *args)
+ value.respond_to?(:map)
+ end
+end
+
+require 'jbuilder/jbuilder_template' if defined?(ActionView::Template)
+require 'jbuilder/dependency_tracker'
+require 'jbuilder/railtie' if defined?(Rails::VERSION::MAJOR) && Rails::VERSION::MAJOR == 4
--- /dev/null
+require 'jbuilder'
+
+dependency_tracker = false
+
+begin
+ require 'action_view'
+ require 'action_view/dependency_tracker'
+ dependency_tracker = ::ActionView::DependencyTracker
+rescue LoadError
+ begin
+ require 'cache_digests'
+ dependency_tracker = ::CacheDigests::DependencyTracker
+ rescue LoadError
+ end
+end
+
+if dependency_tracker
+ class Jbuilder
+ module DependencyTrackerMethods
+ # Matches:
+ # json.partial! "messages/message"
+ # json.partial!('messages/message')
+ #
+ DIRECT_RENDERS = /
+ \w+\.partial! # json.partial!
+ \(?\s* # optional parenthesis
+ (['"])([^'"]+)\1 # quoted value
+ /x
+
+ # Matches:
+ # json.partial! partial: "comments/comment"
+ # json.comments @post.comments, partial: "comments/comment", as: :comment
+ # json.array! @posts, partial: "posts/post", as: :post
+ # = render partial: "account"
+ #
+ INDIRECT_RENDERS = /
+ (?::partial\s*=>|partial:) # partial: or :partial =>
+ \s* # optional whitespace
+ (['"])([^'"]+)\1 # quoted value
+ /x
+
+ def dependencies
+ direct_dependencies + indirect_dependencies + explicit_dependencies
+ end
+
+ private
+
+ def direct_dependencies
+ source.scan(DIRECT_RENDERS).map(&:second)
+ end
+
+ def indirect_dependencies
+ source.scan(INDIRECT_RENDERS).map(&:second)
+ end
+ end
+ end
+
+ ::Jbuilder::DependencyTracker = Class.new(dependency_tracker::ERBTracker)
+ ::Jbuilder::DependencyTracker.send :include, ::Jbuilder::DependencyTrackerMethods
+ dependency_tracker.register_tracker :jbuilder, ::Jbuilder::DependencyTracker
+end
--- /dev/null
+require 'jbuilder'
+require 'action_dispatch/http/mime_type'
+require 'active_support/cache'
+
+class JbuilderTemplate < Jbuilder
+ class << self
+ attr_accessor :template_lookup_options
+ end
+
+ self.template_lookup_options = { handlers: [:jbuilder] }
+
+ def initialize(context, *args, &block)
+ @context = context
+ super(*args, &block)
+ end
+
+ def partial!(name_or_options, locals = {})
+ case name_or_options
+ when ::Hash
+ # partial! partial: 'name', locals: { foo: 'bar' }
+ options = name_or_options
+ else
+ # partial! 'name', foo: 'bar'
+ options = { partial: name_or_options, locals: locals }
+ as = locals.delete(:as)
+ options[:as] = as if as.present?
+ options[:collection] = locals[:collection] if locals.key?(:collection)
+ end
+
+ _handle_partial_options options
+ end
+
+ def array!(collection = [], *attributes, &block)
+ options = attributes.extract_options!
+
+ if options.key?(:partial)
+ partial! options[:partial], options.merge(collection: collection)
+ else
+ super
+ end
+ end
+
+ # Caches the json constructed within the block passed. Has the same signature as the `cache` helper
+ # method in `ActionView::Helpers::CacheHelper` and so can be used in the same way.
+ #
+ # Example:
+ #
+ # json.cache! ['v1', @person], expires_in: 10.minutes do
+ # json.extract! @person, :name, :age
+ # end
+ def cache!(key=nil, options={}, &block)
+ if @context.controller.perform_caching
+ value = ::Rails.cache.fetch(_cache_key(key, options), options) do
+ _scope { yield self }
+ end
+
+ merge! value
+ else
+ yield
+ end
+ end
+
+ # Conditionally catches the json depending in the condition given as first parameter. Has the same
+ # signature as the `cache` helper method in `ActionView::Helpers::CacheHelper` and so can be used in
+ # the same way.
+ #
+ # Example:
+ #
+ # json.cache_if! !admin?, @person, expires_in: 10.minutes do
+ # json.extract! @person, :name, :age
+ # end
+ def cache_if!(condition, *args, &block)
+ condition ? cache!(*args, &block) : yield
+ end
+
+ protected
+ def _handle_partial_options(options)
+ options.reverse_merge! locals: {}
+ options.reverse_merge! ::JbuilderTemplate.template_lookup_options
+ as = options[:as]
+
+ if as && options.key?(:collection)
+ collection = options.delete(:collection) || []
+ array!(collection) do |member|
+ options[:locals].merge! as => member
+ options[:locals].merge! collection: collection
+ _render_partial options
+ end
+ else
+ _render_partial options
+ end
+ end
+
+ def _render_partial(options)
+ options[:locals].merge! json: self
+ @context.render options
+ end
+
+ def _cache_key(key, options)
+ if @context.respond_to?(:cache_fragment_name)
+ # Current compatibility, fragment_name_with_digest is private again and cache_fragment_name
+ # should be used instead.
+ @context.cache_fragment_name(key, options)
+ elsif @context.respond_to?(:fragment_name_with_digest)
+ # Backwards compatibility for period of time when fragment_name_with_digest was made public.
+ @context.fragment_name_with_digest(key)
+ else
+ ::ActiveSupport::Cache.expand_cache_key(key.is_a?(::Hash) ? url_for(key).split('://').last : key, :jbuilder)
+ end
+ end
+
+ private
+
+ def _mapable_arguments?(value, *args)
+ return true if super
+ options = args.last
+ ::Hash === options && options.key?(:as)
+ end
+end
+
+class JbuilderHandler
+ cattr_accessor :default_format
+ self.default_format = Mime::JSON
+
+ def self.call(template)
+ # this juggling is required to keep line numbers right in the error
+ %{__already_defined = defined?(json); json||=JbuilderTemplate.new(self); #{template.source}
+ json.target! unless __already_defined}
+ end
+end
+
+ActionView::Template.register_template_handler :jbuilder, JbuilderHandler
--- /dev/null
+require 'rails/railtie'
+
+class Jbuilder
+ class Railtie < ::Rails::Railtie
+ generators do |app|
+ Rails::Generators.configure! app.config.generators
+ Rails::Generators.hidden_namespaces.uniq!
+ require 'generators/rails/scaffold_controller_generator'
+ end
+ end
+end
\ No newline at end of file
--- /dev/null
+--- !ruby/object:Gem::Specification
+name: jbuilder
+version: !ruby/object:Gem::Version
+ version: 2.0.4
+platform: ruby
+authors:
+- David Heinemeier Hansson
+autorequire:
+bindir: bin
+cert_chain: []
+date: 2014-03-04 00:00:00.000000000 Z
+dependencies:
+- !ruby/object:Gem::Dependency
+ name: activesupport
+ requirement: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: 3.0.0
+ type: :runtime
+ prerelease: false
+ version_requirements: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: 3.0.0
+- !ruby/object:Gem::Dependency
+ name: multi_json
+ requirement: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: 1.2.0
+ type: :runtime
+ prerelease: false
+ version_requirements: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: 1.2.0
+description:
+email: david@37signals.com
+executables: []
+extensions: []
+extra_rdoc_files: []
+files:
+- ".gitignore"
+- ".travis.yml"
+- CHANGELOG.md
+- Gemfile
+- Gemfile.old
+- MIT-LICENSE
+- README.md
+- Rakefile
+- jbuilder.gemspec
+- lib/generators/rails/jbuilder_generator.rb
+- lib/generators/rails/scaffold_controller_generator.rb
+- lib/generators/rails/templates/controller.rb
+- lib/generators/rails/templates/index.json.jbuilder
+- lib/generators/rails/templates/show.json.jbuilder
+- lib/jbuilder.rb
+- lib/jbuilder/dependency_tracker.rb
+- lib/jbuilder/jbuilder_template.rb
+- lib/jbuilder/railtie.rb
+- test/jbuilder_dependency_tracker_test.rb
+- test/jbuilder_generator_test.rb
+- test/jbuilder_template_test.rb
+- test/jbuilder_test.rb
+- test/scaffold_controller_generator_test.rb
+homepage: https://github.com/rails/jbuilder
+licenses:
+- MIT
+metadata: {}
+post_install_message:
+rdoc_options: []
+require_paths:
+- lib
+required_ruby_version: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: 1.9.3
+required_rubygems_version: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: '0'
+requirements: []
+rubyforge_project:
+rubygems_version: 2.2.2
+signing_key:
+specification_version: 4
+summary: Create JSON structures via a Builder-style DSL
+test_files:
+- test/jbuilder_dependency_tracker_test.rb
+- test/jbuilder_generator_test.rb
+- test/jbuilder_template_test.rb
+- test/jbuilder_test.rb
+- test/scaffold_controller_generator_test.rb
--- /dev/null
+require 'test/unit'
+require 'active_support/test_case'
+require 'jbuilder/dependency_tracker'
+
+
+class FakeTemplate
+ attr_reader :source, :handler
+ def initialize(source, handler = :jbuilder)
+ @source, @handler = source, handler
+ end
+end
+
+
+class JbuilderDependencyTrackerTest < ActiveSupport::TestCase
+ def make_tracker(name, source)
+ template = FakeTemplate.new(source)
+ Jbuilder::DependencyTracker.new(name, template)
+ end
+
+ def track_dependencies(source)
+ make_tracker('jbuilder_template', source).dependencies
+ end
+
+ test 'detects dependency via direct partial! call' do
+ dependencies = track_dependencies <<-RUBY
+ json.partial! 'path/to/partial', foo: bar
+ json.partial! 'path/to/another/partial', :fizz => buzz
+ RUBY
+
+ assert_equal %w[path/to/partial path/to/another/partial], dependencies
+ end
+
+ test 'detects dependency via direct partial! call with parens' do
+ dependencies = track_dependencies <<-RUBY
+ json.partial!("path/to/partial")
+ RUBY
+
+ assert_equal %w[path/to/partial], dependencies
+ end
+
+ test 'detects partial with options (1.9 style)' do
+ dependencies = track_dependencies <<-RUBY
+ json.partial! hello: 'world', partial: 'path/to/partial', foo: :bar
+ RUBY
+
+ assert_equal %w[path/to/partial], dependencies
+ end
+
+ test 'detects partial with options (1.8 style)' do
+ dependencies = track_dependencies <<-RUBY
+ json.partial! :hello => 'world', :partial => 'path/to/partial', :foo => :bar
+ RUBY
+
+ assert_equal %w[path/to/partial], dependencies
+ end
+
+ test 'detects partial in indirect collecton calls' do
+ dependencies = track_dependencies <<-RUBY
+ json.comments @post.comments, partial: 'comments/comment', as: :comment
+ RUBY
+
+ assert_equal %w[comments/comment], dependencies
+ end
+
+ test 'detects explicit depedency' do
+ dependencies = track_dependencies <<-RUBY
+ # Template Dependency: path/to/partial
+ json.foo 'bar'
+ RUBY
+
+ assert_equal %w[path/to/partial], dependencies
+ end
+end
--- /dev/null
+require 'rails/generators/test_case'
+require 'generators/rails/jbuilder_generator'
+
+class JbuilderGeneratorTest < Rails::Generators::TestCase
+ tests Rails::Generators::JbuilderGenerator
+ arguments %w(Post title body:text password:digest)
+ destination File.expand_path('../tmp', __FILE__)
+ setup :prepare_destination
+
+ test 'views are generated' do
+ run_generator
+
+ %w(index show).each do |view|
+ assert_file "app/views/posts/#{view}.json.jbuilder"
+ end
+ end
+
+ test 'index content' do
+ run_generator
+
+ assert_file 'app/views/posts/index.json.jbuilder' do |content|
+ assert_match /json\.array!\(@posts\) do \|post\|/, content
+ assert_match /json\.extract! post, :id, :title, :body/, content
+ assert_match /json\.url post_url\(post, format: :json\)/, content
+ end
+
+ assert_file 'app/views/posts/show.json.jbuilder' do |content|
+ assert_match /json\.extract! @post, :id, :title, :body, :created_at, :updated_at/, content
+ end
+ end
+end
--- /dev/null
+require 'test/unit'
+require 'mocha/setup'
+require 'action_view'
+require 'action_view/testing/resolvers'
+require 'active_support/cache'
+require 'jbuilder/jbuilder_template'
+
+
+BLOG_POST_PARTIAL = <<-JBUILDER
+ json.extract! blog_post, :id, :body
+ json.author do
+ name = blog_post.author_name.split(nil, 2)
+ json.first_name name[0]
+ json.last_name name[1]
+ end
+JBUILDER
+
+BlogPost = Struct.new(:id, :body, :author_name)
+blog_authors = [ 'David Heinemeier Hansson', 'Pavel Pravosud' ].cycle
+BLOG_POST_COLLECTION = 10.times.map{ |i| BlogPost.new(i+1, "post body #{i+1}", blog_authors.next) }
+
+module Rails
+ def self.cache
+ @cache ||= ActiveSupport::Cache::MemoryStore.new
+ end
+end
+
+class JbuilderTemplateTest < ActionView::TestCase
+ setup do
+ @context = self
+ Rails.cache.clear
+ end
+
+ def partials
+ {
+ '_partial.json.jbuilder' => 'json.content "hello"',
+ '_blog_post.json.jbuilder' => BLOG_POST_PARTIAL
+ }
+ end
+
+ def render_jbuilder(source)
+ @rendered = []
+ lookup_context.view_paths = [ActionView::FixtureResolver.new(partials.merge('test.json.jbuilder' => source))]
+ ActionView::Template.new(source, 'test', JbuilderHandler, :virtual_path => 'test').render(self, {}).strip
+ end
+
+ def undef_context_methods(*names)
+ self.class_eval do
+ names.each do |name|
+ undef_method name.to_sym if self.method_defined?(name.to_sym)
+ end
+ end
+ end
+
+ def assert_collection_rendered(json, context = nil)
+ result = MultiJson.load(json)
+ result = result.fetch(context) if context
+
+ assert_equal 10, result.length
+ assert_equal Array, result.class
+ assert_equal 'post body 5', result[4]['body']
+ assert_equal 'Heinemeier Hansson', result[2]['author']['last_name']
+ assert_equal 'Pavel', result[5]['author']['first_name']
+ end
+
+ test 'rendering' do
+ json = render_jbuilder <<-JBUILDER
+ json.content 'hello'
+ JBUILDER
+
+ assert_equal 'hello', MultiJson.load(json)['content']
+ end
+
+ test 'key_format! with parameter' do
+ json = render_jbuilder <<-JBUILDER
+ json.key_format! :camelize => [:lower]
+ json.camel_style 'for JS'
+ JBUILDER
+
+ assert_equal ['camelStyle'], MultiJson.load(json).keys
+ end
+
+ test 'key_format! propagates to child elements' do
+ json = render_jbuilder <<-JBUILDER
+ json.key_format! :upcase
+ json.level1 'one'
+ json.level2 do
+ json.value 'two'
+ end
+ JBUILDER
+
+ result = MultiJson.load(json)
+ assert_equal 'one', result['LEVEL1']
+ assert_equal 'two', result['LEVEL2']['VALUE']
+ end
+
+ test 'partial! renders partial' do
+ json = render_jbuilder <<-JBUILDER
+ json.partial! 'partial'
+ JBUILDER
+
+ assert_equal 'hello', MultiJson.load(json)['content']
+ end
+
+ test 'partial! renders collections' do
+ json = render_jbuilder <<-JBUILDER
+ json.partial! 'blog_post', :collection => BLOG_POST_COLLECTION, :as => :blog_post
+ JBUILDER
+
+ assert_collection_rendered json
+ end
+
+ test 'partial! renders as empty array for nil-collection' do
+ json = render_jbuilder <<-JBUILDER
+ json.partial! 'blog_post', :collection => nil, :as => :blog_post
+ JBUILDER
+
+ assert_equal '[]', json
+ end
+
+ test 'partial! renders collection (alt. syntax)' do
+ json = render_jbuilder <<-JBUILDER
+ json.partial! :partial => 'blog_post', :collection => BLOG_POST_COLLECTION, :as => :blog_post
+ JBUILDER
+
+ assert_collection_rendered json
+ end
+
+ test 'partial! renders as empty array for nil-collection (alt. syntax)' do
+ json = render_jbuilder <<-JBUILDER
+ json.partial! :partial => 'blog_post', :collection => nil, :as => :blog_post
+ JBUILDER
+
+ assert_equal '[]', json
+ end
+
+ test 'render array of partials' do
+ json = render_jbuilder <<-JBUILDER
+ json.array! BLOG_POST_COLLECTION, :partial => 'blog_post', :as => :blog_post
+ JBUILDER
+
+ assert_collection_rendered json
+ end
+
+ test 'render array of partials as empty array with nil-collection' do
+ json = render_jbuilder <<-JBUILDER
+ json.array! nil, :partial => 'blog_post', :as => :blog_post
+ JBUILDER
+
+ assert_equal '[]', json
+ end
+
+ test 'render array if partials as a value' do
+ json = render_jbuilder <<-JBUILDER
+ json.posts BLOG_POST_COLLECTION, :partial => 'blog_post', :as => :blog_post
+ JBUILDER
+
+ assert_collection_rendered json, 'posts'
+ end
+
+ test 'render as empty array if partials as a nil value' do
+ json = render_jbuilder <<-JBUILDER
+ json.posts nil, :partial => 'blog_post', :as => :blog_post
+ JBUILDER
+
+ assert_equal '{"posts":[]}', json
+ end
+
+ test 'fragment caching a JSON object' do
+ undef_context_methods :fragment_name_with_digest, :cache_fragment_name
+
+ render_jbuilder <<-JBUILDER
+ json.cache! 'cachekey' do
+ json.name 'Cache'
+ end
+ JBUILDER
+
+ json = render_jbuilder <<-JBUILDER
+ json.cache! 'cachekey' do
+ json.name 'Miss'
+ end
+ JBUILDER
+
+ parsed = MultiJson.load(json)
+ assert_equal 'Cache', parsed['name']
+ end
+
+ test 'conditionally fragment caching a JSON object' do
+ undef_context_methods :fragment_name_with_digest, :cache_fragment_name
+
+ render_jbuilder <<-JBUILDER
+ json.cache_if! true, 'cachekey' do
+ json.test1 'Cache'
+ end
+ json.cache_if! false, 'cachekey' do
+ json.test2 'Cache'
+ end
+ JBUILDER
+
+ json = render_jbuilder <<-JBUILDER
+ json.cache_if! true, 'cachekey' do
+ json.test1 'Miss'
+ end
+ json.cache_if! false, 'cachekey' do
+ json.test2 'Miss'
+ end
+ JBUILDER
+
+ parsed = MultiJson.load(json)
+ assert_equal 'Cache', parsed['test1']
+ assert_equal 'Miss', parsed['test2']
+ end
+
+ test 'fragment caching deserializes an array' do
+ undef_context_methods :fragment_name_with_digest, :cache_fragment_name
+
+ render_jbuilder <<-JBUILDER
+ json.cache! 'cachekey' do
+ json.array! %w(a b c)
+ end
+ JBUILDER
+
+ json = render_jbuilder <<-JBUILDER
+ json.cache! 'cachekey' do
+ json.array! %w(1 2 3)
+ end
+ JBUILDER
+
+ parsed = MultiJson.load(json)
+ assert_equal %w(a b c), parsed
+ end
+
+ test 'fragment caching works with previous version of cache digests' do
+ undef_context_methods :cache_fragment_name
+
+ @context.expects :fragment_name_with_digest
+
+ render_jbuilder <<-JBUILDER
+ json.cache! 'cachekey' do
+ json.name 'Cache'
+ end
+ JBUILDER
+ end
+
+ test 'fragment caching works with current cache digests' do
+ undef_context_methods :fragment_name_with_digest
+
+ @context.expects :cache_fragment_name
+
+ render_jbuilder <<-JBUILDER
+ json.cache! 'cachekey' do
+ json.name 'Cache'
+ end
+ JBUILDER
+ end
+
+ test 'current cache digest option accepts options' do
+ undef_context_methods :fragment_name_with_digest
+
+ @context.expects(:cache_fragment_name).with('cachekey', skip_digest: true)
+
+ render_jbuilder <<-JBUILDER
+ json.cache! 'cachekey', skip_digest: true do
+ json.name 'Cache'
+ end
+ JBUILDER
+ end
+
+ test 'does not perform caching when controller.perform_caching is false' do
+ controller.perform_caching = false
+ render_jbuilder <<-JBUILDER
+ json.cache! 'cachekey' do
+ json.name 'Cache'
+ end
+ JBUILDER
+
+ assert_equal Rails.cache.inspect[/entries=(\d+)/, 1], '0'
+ end
+
+ test 'fragment caching falls back on ActiveSupport::Cache.expand_cache_key' do
+ undef_context_methods :fragment_name_with_digest, :cache_fragment_name
+
+ ActiveSupport::Cache.expects :expand_cache_key
+
+ render_jbuilder <<-JBUILDER
+ json.cache! 'cachekey' do
+ json.name 'Cache'
+ end
+ JBUILDER
+ end
+
+end
\ No newline at end of file
--- /dev/null
+require 'test/unit'
+require 'active_support/test_case'
+require 'active_support/inflector'
+require 'jbuilder'
+
+Comment = Struct.new(:content, :id)
+
+class NonEnumerable
+ def initialize(collection)
+ @collection = collection
+ end
+
+ def map(&block)
+ @collection.map(&block)
+ end
+end
+
+class JbuilderTest < ActiveSupport::TestCase
+ test 'single key' do
+ json = Jbuilder.encode do |json|
+ json.content 'hello'
+ end
+
+ assert_equal 'hello', MultiJson.load(json)['content']
+ end
+
+ test 'single key with false value' do
+ json = Jbuilder.encode do |json|
+ json.content false
+ end
+
+ assert_equal false, MultiJson.load(json)['content']
+ end
+
+ test 'single key with nil value' do
+ json = Jbuilder.encode do |json|
+ json.content nil
+ end
+
+ assert MultiJson.load(json).has_key?('content')
+ assert_equal nil, MultiJson.load(json)['content']
+ end
+
+ test 'multiple keys' do
+ json = Jbuilder.encode do |json|
+ json.title 'hello'
+ json.content 'world'
+ end
+
+ parsed = MultiJson.load(json)
+ assert_equal 'hello', parsed['title']
+ assert_equal 'world', parsed['content']
+ end
+
+ test 'extracting from object' do
+ person = Struct.new(:name, :age).new('David', 32)
+
+ json = Jbuilder.encode do |json|
+ json.extract! person, :name, :age
+ end
+
+ parsed = MultiJson.load(json)
+ assert_equal 'David', parsed['name']
+ assert_equal 32, parsed['age']
+ end
+
+ test 'extracting from object using call style for 1.9' do
+ person = Struct.new(:name, :age).new('David', 32)
+
+ json = Jbuilder.encode do |json|
+ json.(person, :name, :age)
+ end
+
+ parsed = MultiJson.load(json)
+ assert_equal 'David', parsed['name']
+ assert_equal 32, parsed['age']
+ end
+
+ test 'extracting from hash' do
+ person = {:name => 'Jim', :age => 34}
+
+ json = Jbuilder.encode do |json|
+ json.extract! person, :name, :age
+ end
+
+ parsed = MultiJson.load(json)
+ assert_equal 'Jim', parsed['name']
+ assert_equal 34, parsed['age']
+ end
+
+ test 'nesting single child with block' do
+ json = Jbuilder.encode do |json|
+ json.author do
+ json.name 'David'
+ json.age 32
+ end
+ end
+
+ parsed = MultiJson.load(json)
+ assert_equal 'David', parsed['author']['name']
+ assert_equal 32, parsed['author']['age']
+ end
+
+ test 'nesting multiple children with block' do
+ json = Jbuilder.encode do |json|
+ json.comments do
+ json.child! { json.content 'hello' }
+ json.child! { json.content 'world' }
+ end
+ end
+
+ parsed = MultiJson.load(json)
+ assert_equal 'hello', parsed['comments'].first['content']
+ assert_equal 'world', parsed['comments'].second['content']
+ end
+
+ test 'nesting single child with inline extract' do
+ person = Class.new do
+ attr_reader :name, :age
+
+ def initialize(name, age)
+ @name, @age = name, age
+ end
+ end.new('David', 32)
+
+ json = Jbuilder.encode do |json|
+ json.author person, :name, :age
+ end
+
+ parsed = MultiJson.load(json)
+ assert_equal 'David', parsed['author']['name']
+ assert_equal 32, parsed['author']['age']
+ end
+
+ test 'nesting multiple children from array' do
+ comments = [ Comment.new('hello', 1), Comment.new('world', 2) ]
+
+ json = Jbuilder.encode do |json|
+ json.comments comments, :content
+ end
+
+ parsed = MultiJson.load(json)
+ assert_equal ['content'], parsed['comments'].first.keys
+ assert_equal 'hello', parsed['comments'].first['content']
+ assert_equal 'world', parsed['comments'].second['content']
+ end
+
+ test 'nesting multiple children from array when child array is empty' do
+ comments = []
+
+ json = Jbuilder.encode do |json|
+ json.name 'Parent'
+ json.comments comments, :content
+ end
+
+ parsed = MultiJson.load(json)
+ assert_equal 'Parent', parsed['name']
+ assert_equal [], parsed['comments']
+ end
+
+ test 'nesting multiple children from array with inline loop' do
+ comments = [ Comment.new('hello', 1), Comment.new('world', 2) ]
+
+ json = Jbuilder.encode do |json|
+ json.comments comments do |comment|
+ json.content comment.content
+ end
+ end
+
+ parsed = MultiJson.load(json)
+ assert_equal ['content'], parsed['comments'].first.keys
+ assert_equal 'hello', parsed['comments'].first['content']
+ assert_equal 'world', parsed['comments'].second['content']
+ end
+
+ test 'handles nil-collections as empty arrays' do
+ json = Jbuilder.encode do |json|
+ json.comments nil do |comment|
+ json.content comment.content
+ end
+ end
+
+ assert_equal [], MultiJson.load(json)['comments']
+ end
+
+ test 'nesting multiple children from a non-Enumerable that responds to #map' do
+ comments = NonEnumerable.new([ Comment.new('hello', 1), Comment.new('world', 2) ])
+
+ json = Jbuilder.encode do |json|
+ json.comments comments, :content
+ end
+
+ parsed = MultiJson.load(json)
+ assert_equal ['content'], parsed['comments'].first.keys
+ assert_equal 'hello', parsed['comments'].first['content']
+ assert_equal 'world', parsed['comments'].second['content']
+ end
+
+ test 'nesting multiple chilren from a non-Enumerable that responds to #map with inline loop' do
+ comments = NonEnumerable.new([ Comment.new('hello', 1), Comment.new('world', 2) ])
+
+ json = Jbuilder.encode do |json|
+ json.comments comments do |comment|
+ json.content comment.content
+ end
+ end
+
+ parsed = MultiJson.load(json)
+ assert_equal ['content'], parsed['comments'].first.keys
+ assert_equal 'hello', parsed['comments'].first['content']
+ assert_equal 'world', parsed['comments'].second['content']
+ end
+
+ test 'nesting multiple children from array with inline loop on root' do
+ comments = [ Comment.new('hello', 1), Comment.new('world', 2) ]
+
+ json = Jbuilder.encode do |json|
+ json.call(comments) do |comment|
+ json.content comment.content
+ end
+ end
+
+ parsed = MultiJson.load(json)
+ assert_equal 'hello', parsed.first['content']
+ assert_equal 'world', parsed.second['content']
+ end
+
+ test 'array nested inside nested hash' do
+ json = Jbuilder.encode do |json|
+ json.author do
+ json.name 'David'
+ json.age 32
+
+ json.comments do
+ json.child! { json.content 'hello' }
+ json.child! { json.content 'world' }
+ end
+ end
+ end
+
+ parsed = MultiJson.load(json)
+ assert_equal 'hello', parsed['author']['comments'].first['content']
+ assert_equal 'world', parsed['author']['comments'].second['content']
+ end
+
+ test 'array nested inside array' do
+ json = Jbuilder.encode do |json|
+ json.comments do
+ json.child! do
+ json.authors do
+ json.child! do
+ json.name 'david'
+ end
+ end
+ end
+ end
+ end
+
+ assert_equal 'david', MultiJson.load(json)['comments'].first['authors'].first['name']
+ end
+
+ test 'directly set an array nested in another array' do
+ data = [ { :department => 'QA', :not_in_json => 'hello', :names => ['John', 'David'] } ]
+ json = Jbuilder.encode do |json|
+ json.array! data do |object|
+ json.department object[:department]
+ json.names do
+ json.array! object[:names]
+ end
+ end
+ end
+
+ assert_equal 'David', MultiJson.load(json)[0]['names'].last
+ assert_not_equal 'hello', MultiJson.load(json)[0]['not_in_json']
+ end
+
+ test 'nested jbuilder objects' do
+ to_nest = Jbuilder.new
+ to_nest.nested_value 'Nested Test'
+ json = Jbuilder.encode do |json|
+ json.value 'Test'
+ json.nested to_nest
+ end
+
+ result = {'value' => 'Test', 'nested' => {'nested_value' => 'Nested Test'}}
+ assert_equal result, MultiJson.load(json)
+ end
+
+ test 'nested jbuilder object via set!' do
+ to_nest = Jbuilder.new
+ to_nest.nested_value 'Nested Test'
+ json = Jbuilder.encode do |json|
+ json.value 'Test'
+ json.set! :nested, to_nest
+ end
+
+ result = {'value' => 'Test', 'nested' => {'nested_value' => 'Nested Test'}}
+ assert_equal result, MultiJson.load(json)
+ end
+
+ test 'top-level array' do
+ comments = [ Comment.new('hello', 1), Comment.new('world', 2) ]
+
+ json = Jbuilder.encode do |json|
+ json.array!(comments) do |comment|
+ json.content comment.content
+ end
+ end
+
+ parsed = MultiJson.load(json)
+ assert_equal 'hello', parsed.first['content']
+ assert_equal 'world', parsed.second['content']
+ end
+
+ test 'extract attributes directly from array' do
+ comments = [ Comment.new('hello', 1), Comment.new('world', 2) ]
+
+ json = Jbuilder.encode do |json|
+ json.array! comments, :content, :id
+ end
+
+ parsed = MultiJson.load(json)
+ assert_equal 'hello', parsed.first['content']
+ assert_equal 1, parsed.first['id']
+ assert_equal 'world', parsed.second['content']
+ assert_equal 2, parsed.second['id']
+ end
+
+ test 'empty top-level array' do
+ comments = []
+
+ json = Jbuilder.encode do |json|
+ json.array!(comments) do |comment|
+ json.content comment.content
+ end
+ end
+
+ assert_equal [], MultiJson.load(json)
+ end
+
+ test 'dynamically set a key/value' do
+ json = Jbuilder.encode do |json|
+ json.set! :each, 'stuff'
+ end
+
+ assert_equal 'stuff', MultiJson.load(json)['each']
+ end
+
+ test 'dynamically set a key/nested child with block' do
+ json = Jbuilder.encode do |json|
+ json.set!(:author) do
+ json.name 'David'
+ json.age 32
+ end
+ end
+
+ parsed = MultiJson.load(json)
+ assert_equal 'David', parsed['author']['name']
+ assert_equal 32, parsed['author']['age']
+ end
+
+ test 'dynamically sets a collection' do
+ comments = [ Comment.new('hello', 1), Comment.new('world', 2) ]
+
+ json = Jbuilder.encode do |json|
+ json.set! :comments, comments, :content
+ end
+
+ parsed = MultiJson.load(json)
+ assert_equal ['content'], parsed['comments'].first.keys
+ assert_equal 'hello', parsed['comments'].first['content']
+ assert_equal 'world', parsed['comments'].second['content']
+ end
+
+ test 'query like object' do
+ class Person
+ attr_reader :name, :age
+
+ def initialize(name, age)
+ @name, @age = name, age
+ end
+ end
+ class RelationMock
+ include Enumerable
+
+ def each(&block)
+ [Person.new('Bob', 30), Person.new('Frank', 50)].each(&block)
+ end
+ def empty?
+ false
+ end
+ end
+
+ result = Jbuilder.encode do |json|
+ json.relations RelationMock.new, :name, :age
+ end
+
+ parsed = MultiJson.load(result)
+ assert_equal 2, parsed['relations'].length
+ assert_equal 'Bob', parsed['relations'][0]['name']
+ assert_equal 50, parsed['relations'][1]['age']
+ end
+
+ test 'initialize via options hash' do
+ jbuilder = Jbuilder.new(:key_formatter => 1, :ignore_nil => 2)
+ assert_equal 1, jbuilder.instance_eval{ @key_formatter }
+ assert_equal 2, jbuilder.instance_eval{ @ignore_nil }
+ end
+
+ test 'key_format! with parameter' do
+ json = Jbuilder.new
+ json.key_format! :camelize => [:lower]
+ json.camel_style 'for JS'
+
+ assert_equal ['camelStyle'], json.attributes!.keys
+ end
+
+ test 'key_format! with parameter not as an array' do
+ json = Jbuilder.new
+ json.key_format! :camelize => :lower
+ json.camel_style 'for JS'
+
+ assert_equal ['camelStyle'], json.attributes!.keys
+ end
+
+ test 'key_format! propagates to child elements' do
+ json = Jbuilder.new
+ json.key_format! :upcase
+ json.level1 'one'
+ json.level2 do
+ json.value 'two'
+ end
+
+ result = json.attributes!
+ assert_equal 'one', result['LEVEL1']
+ assert_equal 'two', result['LEVEL2']['VALUE']
+ end
+
+ test 'key_format! resets after child element' do
+ json = Jbuilder.new
+ json.level2 do
+ json.key_format! :upcase
+ json.value 'two'
+ end
+ json.level1 'one'
+
+ result = json.attributes!
+ assert_equal 'two', result['level2']['VALUE']
+ assert_equal 'one', result['level1']
+ end
+
+ test 'key_format! with no parameter' do
+ json = Jbuilder.new
+ json.key_format! :upcase
+ json.lower 'Value'
+
+ assert_equal ['LOWER'], json.attributes!.keys
+ end
+
+ test 'key_format! with multiple steps' do
+ json = Jbuilder.new
+ json.key_format! :upcase, :pluralize
+ json.pill ''
+
+ assert_equal ['PILLs'], json.attributes!.keys
+ end
+
+ test 'key_format! with lambda/proc' do
+ json = Jbuilder.new
+ json.key_format! lambda { |key| key + ' and friends' }
+ json.oats ''
+
+ assert_equal ['oats and friends'], json.attributes!.keys
+ end
+
+ test 'default key_format!' do
+ Jbuilder.key_format :camelize => :lower
+ json = Jbuilder.new
+ json.camel_style 'for JS'
+
+ assert_equal ['camelStyle'], json.attributes!.keys
+ Jbuilder.send(:class_variable_set, '@@key_formatter', Jbuilder::KeyFormatter.new)
+ end
+
+ test 'do not use default key formatter directly' do
+ json = Jbuilder.new
+ json.key 'value'
+
+ assert_equal [], Jbuilder.send(:class_variable_get, '@@key_formatter').instance_variable_get('@cache').keys
+ end
+
+ test 'ignore_nil! without a parameter' do
+ json = Jbuilder.new
+ json.ignore_nil!
+ json.test nil
+
+ assert_equal [], json.attributes!.keys
+ end
+
+ test 'ignore_nil! with parameter' do
+ json = Jbuilder.new
+ json.ignore_nil! true
+ json.name 'Bob'
+ json.dne nil
+
+ assert_equal ['name'], json.attributes!.keys
+
+ json = Jbuilder.new
+ json.ignore_nil! false
+ json.name 'Bob'
+ json.dne nil
+
+ assert_equal ['name', 'dne'], json.attributes!.keys
+ end
+
+ test 'default ignore_nil!' do
+ Jbuilder.ignore_nil
+ json = Jbuilder.new
+ json.name 'Bob'
+ json.dne nil
+
+ assert_equal ['name'], json.attributes!.keys
+ Jbuilder.send(:class_variable_set, '@@ignore_nil', false)
+ end
+
+ test 'nil!' do
+ json = Jbuilder.new
+ json.key 'value'
+ json.nil!
+ assert_nil json.attributes!
+ end
+
+ test 'null!' do
+ json = Jbuilder.new
+ json.key 'value'
+ json.null!
+ assert_nil json.attributes!
+ end
+
+ test 'throws meaningfull error when on trying to add properties to null' do
+ json = Jbuilder.new
+ json.null!
+ assert_raise(Jbuilder::NullError) { json.foo 'bar' }
+ end
+end
--- /dev/null
+require 'rails/generators/test_case'
+require 'generators/rails/scaffold_controller_generator'
+
+class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
+ tests Rails::Generators::ScaffoldControllerGenerator
+ arguments %w(Post title body:text)
+ destination File.expand_path('../tmp', __FILE__)
+ setup :prepare_destination
+
+ test 'controller content' do
+ run_generator
+
+ assert_file 'app/controllers/posts_controller.rb' do |content|
+ assert_instance_method :index, content do |m|
+ assert_match /@posts = Post\.all/, m
+ end
+
+ assert_instance_method :show, content do |m|
+ assert m.blank?
+ end
+
+ assert_instance_method :new, content do |m|
+ assert_match /@post = Post\.new/, m
+ end
+
+ assert_instance_method :edit, content do |m|
+ assert m.blank?
+ end
+
+ assert_instance_method :create, content do |m|
+ assert_match /@post = Post\.new\(post_params\)/, m
+ assert_match /@post\.save/, m
+ assert_match /format\.html \{ redirect_to @post, notice: 'Post was successfully created\.' \}/, m
+ assert_match /format\.json \{ render action: 'show', status: :created, location: @post \}/, m
+ assert_match /format\.html \{ render action: 'new' \}/, m
+ assert_match /format\.json \{ render json: @post\.errors, status: :unprocessable_entity \}/, m
+ end
+
+ assert_instance_method :update, content do |m|
+ assert_match /format\.html \{ redirect_to @post, notice: 'Post was successfully updated\.' \}/, m
+ assert_match /format\.json \{ render action: 'show', status: :ok, location: @post \}/, m
+ assert_match /format\.html \{ render action: 'edit' \}/, m
+ assert_match /format\.json \{ render json: @post.errors, status: :unprocessable_entity \}/, m
+ end
+
+ assert_instance_method :destroy, content do |m|
+ assert_match /@post\.destroy/, m
+ assert_match /format\.html { redirect_to posts_url \}/, m
+ assert_match /format\.json \{ head :no_content \}/, m
+ end
+
+ assert_match(/def post_params/, content)
+ assert_match(/params\.require\(:post\)\.permit\(:title, :body\)/, content)
+ end
+ end
+end
\ No newline at end of file