jbuilder-2.10.0/0000755000004100000410000000000013641340551013424 5ustar www-datawww-datajbuilder-2.10.0/.travis.yml0000644000004100000410000000163213641340551015537 0ustar www-datawww-datalanguage: ruby cache: bundler before_install: - "gem update --system 2.7.9" - "gem install bundler -v '<2'" rvm: - 2.2.10 - 2.3.8 - 2.4.6 - 2.5.5 - 2.6.2 - ruby-head - rbx-3.107 gemfile: - gemfiles/rails_5_0.gemfile - gemfiles/rails_5_1.gemfile - gemfiles/rails_5_2.gemfile - gemfiles/rails_6_0.gemfile - gemfiles/rails_head.gemfile matrix: exclude: - rvm: 2.2.10 gemfile: gemfiles/rails_6_0.gemfile - rvm: 2.3.8 gemfile: gemfiles/rails_6_0.gemfile - rvm: 2.4.6 gemfile: gemfiles/rails_6_0.gemfile - rvm: 2.2.10 gemfile: gemfiles/rails_head.gemfile - rvm: 2.3.8 gemfile: gemfiles/rails_head.gemfile - rvm: 2.4.6 gemfile: gemfiles/rails_head.gemfile allow_failures: - rvm: jruby-19mode - rvm: rbx-3.107 - rvm: ruby-head - gemfile: gemfiles/rails_head.gemfile fast_finish: true notifications: email: false jbuilder-2.10.0/test/0000755000004100000410000000000013641340551014403 5ustar www-datawww-datajbuilder-2.10.0/test/jbuilder_template_test.rb0000644000004100000410000002212113641340551021460 0ustar www-datawww-datarequire "test_helper" require "action_view/testing/resolvers" class JbuilderTemplateTest < ActiveSupport::TestCase POST_PARTIAL = <<-JBUILDER json.extract! post, :id, :body json.author do first_name, last_name = post.author_name.split(nil, 2) json.first_name first_name json.last_name last_name end JBUILDER COLLECTION_PARTIAL = <<-JBUILDER json.extract! collection, :id, :name JBUILDER RACER_PARTIAL = <<-JBUILDER json.extract! racer, :id, :name JBUILDER PARTIALS = { "_partial.json.jbuilder" => "json.content content", "_post.json.jbuilder" => POST_PARTIAL, "racers/_racer.json.jbuilder" => RACER_PARTIAL, "_collection.json.jbuilder" => COLLECTION_PARTIAL, # Ensure we find only Jbuilder partials from within Jbuilder templates. "_post.html.erb" => "Hello world!" } AUTHORS = [ "David Heinemeier Hansson", "Pavel Pravosud" ].cycle POSTS = (1..10).collect { |i| Post.new(i, "Post ##{i}", AUTHORS.next) } setup { Rails.cache.clear } test "basic template" do result = render('json.content "hello"') assert_equal "hello", result["content"] end test "partial by name with top-level locals" do result = render('json.partial! "partial", content: "hello"') assert_equal "hello", result["content"] end test "partial by name with nested locals" do result = render('json.partial! "partial", locals: { content: "hello" }') assert_equal "hello", result["content"] end test "partial by options containing nested locals" do result = render('json.partial! partial: "partial", locals: { content: "hello" }') assert_equal "hello", result["content"] end test "partial by options containing top-level locals" do result = render('json.partial! partial: "partial", content: "hello"') assert_equal "hello", result["content"] end test "partial for Active Model" do result = render('json.partial! @racer', racer: Racer.new(123, "Chris Harris")) assert_equal 123, result["id"] assert_equal "Chris Harris", result["name"] end test "partial collection by name with symbol local" do result = render('json.partial! "post", collection: @posts, as: :post', posts: POSTS) assert_equal 10, result.count assert_equal "Post #5", result[4]["body"] assert_equal "Heinemeier Hansson", result[2]["author"]["last_name"] assert_equal "Pavel", result[5]["author"]["first_name"] end test "partial collection by name with string local" do result = render('json.partial! "post", collection: @posts, as: "post"', posts: POSTS) assert_equal 10, result.count assert_equal "Post #5", result[4]["body"] assert_equal "Heinemeier Hansson", result[2]["author"]["last_name"] assert_equal "Pavel", result[5]["author"]["first_name"] end test "partial collection by options" do result = render('json.partial! partial: "post", collection: @posts, as: :post', posts: POSTS) assert_equal 10, result.count assert_equal "Post #5", result[4]["body"] assert_equal "Heinemeier Hansson", result[2]["author"]["last_name"] assert_equal "Pavel", result[5]["author"]["first_name"] end test "nil partial collection by name" do assert_equal [], render('json.partial! "post", collection: @posts, as: :post', posts: nil) end test "nil partial collection by options" do assert_equal [], render('json.partial! partial: "post", collection: @posts, as: :post', posts: nil) end test "array of partials" do result = render('json.array! @posts, partial: "post", as: :post', posts: POSTS) assert_equal 10, result.count assert_equal "Post #5", result[4]["body"] assert_equal "Heinemeier Hansson", result[2]["author"]["last_name"] assert_equal "Pavel", result[5]["author"]["first_name"] end test "empty array of partials from nil collection" do assert_equal [], render('json.array! @posts, partial: "post", as: :post', posts: nil) end test "array of partials under key" do result = render('json.posts @posts, partial: "post", as: :post', posts: POSTS) assert_equal 10, result["posts"].count assert_equal "Post #5", result["posts"][4]["body"] assert_equal "Heinemeier Hansson", result["posts"][2]["author"]["last_name"] assert_equal "Pavel", result["posts"][5]["author"]["first_name"] end test "empty array of partials under key from nil collection" do result = render('json.posts @posts, partial: "post", as: :post', posts: nil) assert_equal [], result["posts"] end test "object fragment caching" do render(<<-JBUILDER) json.cache! "cache-key" do json.name "Hit" end JBUILDER hit = render('json.cache! "cache-key" do; end') assert_equal "Hit", hit["name"] end test "conditional object fragment caching" do render(<<-JBUILDER) json.cache_if! true, "cache-key" do json.a "Hit" end json.cache_if! false, "cache-key" do json.b "Hit" end JBUILDER result = render(<<-JBUILDER) json.cache_if! true, "cache-key" do json.a "Miss" end json.cache_if! false, "cache-key" do json.b "Miss" end JBUILDER assert_equal "Hit", result["a"] assert_equal "Miss", result["b"] end test "object fragment caching with expiry" do travel_to "2018-05-12 11:29:00 -0400" render <<-JBUILDER json.cache! "cache-key", expires_in: 1.minute do json.name "Hit" end JBUILDER travel 30.seconds result = render(<<-JBUILDER) json.cache! "cache-key", expires_in: 1.minute do json.name "Miss" end JBUILDER assert_equal "Hit", result["name"] travel 31.seconds result = render(<<-JBUILDER) json.cache! "cache-key", expires_in: 1.minute do json.name "Miss" end JBUILDER assert_equal "Miss", result["name"] end test "object root caching" do render <<-JBUILDER json.cache_root! "cache-key" do json.name "Hit" end JBUILDER assert_equal JSON.dump(name: "Hit"), Rails.cache.read("jbuilder/root/cache-key") result = render(<<-JBUILDER) json.cache_root! "cache-key" do json.name "Miss" end JBUILDER assert_equal "Hit", result["name"] end test "array fragment caching" do render <<-JBUILDER json.cache! "cache-key" do json.array! %w[ a b c ] end JBUILDER assert_equal %w[ a b c ], render('json.cache! "cache-key" do; end') end test "array root caching" do render <<-JBUILDER json.cache_root! "cache-key" do json.array! %w[ a b c ] end JBUILDER assert_equal JSON.dump(%w[ a b c ]), Rails.cache.read("jbuilder/root/cache-key") assert_equal %w[ a b c ], render(<<-JBUILDER) json.cache_root! "cache-key" do json.array! %w[ d e f ] end JBUILDER end test "failing to cache root after JSON structures have been defined" do assert_raises ActionView::Template::Error, "cache_root! can't be used after JSON structures have been defined" do render <<-JBUILDER json.name "Kaboom" json.cache_root! "cache-key" do json.name "Miss" end JBUILDER end end test "empty fragment caching" do render 'json.cache! "nothing" do; end' result = nil assert_nothing_raised do result = render(<<-JBUILDER) json.foo "bar" json.cache! "nothing" do; end JBUILDER end assert_equal "bar", result["foo"] end test "cache instrumentation" do payloads = {} ActiveSupport::Notifications.subscribe("read_fragment.action_controller") { |*args| payloads[:read] = args.last } ActiveSupport::Notifications.subscribe("write_fragment.action_controller") { |*args| payloads[:write] = args.last } render <<-JBUILDER json.cache! "cache-key" do json.name "Cache" end JBUILDER assert_equal "jbuilder/cache-key", payloads[:read][:key] assert_equal "jbuilder/cache-key", payloads[:write][:key] end test "camelized keys" do result = render(<<-JBUILDER) json.key_format! camelize: [:lower] json.first_name "David" JBUILDER assert_equal "David", result["firstName"] end private def render(*args) JSON.load render_without_parsing(*args) end def render_without_parsing(source, assigns = {}) view = build_view(fixtures: PARTIALS.merge("source.json.jbuilder" => source), assigns: assigns) view.render(template: "source.json.jbuilder") end def build_view(options = {}) resolver = ActionView::FixtureResolver.new(options.fetch(:fixtures)) lookup_context = ActionView::LookupContext.new([ resolver ], {}, [""]) controller = ActionView::TestCase::TestController.new # TODO: Use with_empty_template_cache unconditionally after dropping support for Rails <6.0. view = if ActionView::Base.respond_to?(:with_empty_template_cache) ActionView::Base.with_empty_template_cache.new(lookup_context, options.fetch(:assigns, {}), controller) else ActionView::Base.new(lookup_context, options.fetch(:assigns, {}), controller) end def view.view_cache_dependencies; []; end view end end jbuilder-2.10.0/test/scaffold_api_controller_generator_test.rb0000644000004100000410000000404013641340551024710 0ustar www-datawww-datarequire 'test_helper' require 'rails/generators/test_case' require 'generators/rails/scaffold_controller_generator' if Rails::VERSION::MAJOR > 4 class ScaffoldApiControllerGeneratorTest < Rails::Generators::TestCase tests Rails::Generators::ScaffoldControllerGenerator arguments %w(Post title body:text images:attachments --api) 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 %r{@posts = Post\.all}, m end assert_instance_method :show, content do |m| assert m.blank? end assert_instance_method :create, content do |m| assert_match %r{@post = Post\.new\(post_params\)}, m assert_match %r{@post\.save}, m assert_match %r{render :show, status: :created, location: @post}, m assert_match %r{render json: @post\.errors, status: :unprocessable_entity}, m end assert_instance_method :update, content do |m| assert_match %r{render :show, status: :ok, location: @post}, m assert_match %r{render json: @post.errors, status: :unprocessable_entity}, m end assert_instance_method :destroy, content do |m| assert_match %r{@post\.destroy}, m end assert_match %r{def post_params}, content if Rails::VERSION::MAJOR >= 6 assert_match %r{params\.require\(:post\)\.permit\(:title, :body, images: \[\]\)}, content else assert_match %r{params\.require\(:post\)\.permit\(:title, :body, :images\)}, content end end end test 'dont use require and permit if there are no attributes' do run_generator %w(Post --api) assert_file 'app/controllers/posts_controller.rb' do |content| assert_match %r{def post_params}, content assert_match %r{params\.fetch\(:post, \{\}\)}, content end end end end jbuilder-2.10.0/test/jbuilder_generator_test.rb0000644000004100000410000000301213641340551021631 0ustar www-datawww-datarequire 'test_helper' 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 assert_file "app/views/posts/_post.json.jbuilder" end test 'index content' do run_generator assert_file 'app/views/posts/index.json.jbuilder' do |content| assert_match %r{json\.array! @posts, partial: "posts/post", as: :post}, content end assert_file 'app/views/posts/show.json.jbuilder' do |content| assert_match %r{json\.partial! "posts/post", post: @post}, content end assert_file 'app/views/posts/_post.json.jbuilder' do |content| assert_match %r{json\.extract! post, :id, :title, :body}, content assert_match %r{:created_at, :updated_at}, content assert_match %r{json\.url post_url\(post, format: :json\)}, content end end test 'timestamps are not generated in partial with --no-timestamps' do run_generator %w(Post title body:text --no-timestamps) assert_file 'app/views/posts/_post.json.jbuilder' do |content| assert_match %r{json\.extract! post, :id, :title, :body$}, content assert_no_match %r{:created_at, :updated_at}, content end end end jbuilder-2.10.0/test/jbuilder_dependency_tracker_test.rb0000644000004100000410000000405113641340551023500 0ustar www-datawww-datarequire 'test_helper' 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 collection 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 jbuilder-2.10.0/test/scaffold_controller_generator_test.rb0000644000004100000410000000524713641340551024071 0ustar www-datawww-datarequire 'test_helper' 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 images:attachments) 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 %r{@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 %r{@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 %r{@post = Post\.new\(post_params\)}, m assert_match %r{@post\.save}, m assert_match %r{format\.html \{ redirect_to @post, notice: 'Post was successfully created\.' \}}, m assert_match %r{format\.json \{ render :show, status: :created, location: @post \}}, m assert_match %r{format\.html \{ render :new \}}, m assert_match %r{format\.json \{ render json: @post\.errors, status: :unprocessable_entity \}}, m end assert_instance_method :update, content do |m| assert_match %r{format\.html \{ redirect_to @post, notice: 'Post was successfully updated\.' \}}, m assert_match %r{format\.json \{ render :show, status: :ok, location: @post \}}, m assert_match %r{format\.html \{ render :edit \}}, m assert_match %r{format\.json \{ render json: @post.errors, status: :unprocessable_entity \}}, m end assert_instance_method :destroy, content do |m| assert_match %r{@post\.destroy}, m assert_match %r{format\.html \{ redirect_to posts_url, notice: 'Post was successfully destroyed\.' \}}, m assert_match %r{format\.json \{ head :no_content \}}, m end assert_match %r{def post_params}, content if Rails::VERSION::MAJOR >= 6 assert_match %r{params\.require\(:post\)\.permit\(:title, :body, images: \[\]\)}, content else assert_match %r{params\.require\(:post\)\.permit\(:title, :body, :images\)}, content end end end test 'dont use require and permit if there are no attributes' do run_generator %w(Post) assert_file 'app/controllers/posts_controller.rb' do |content| assert_match %r{def post_params}, content assert_match %r{params\.fetch\(:post, \{\}\)}, content end end end jbuilder-2.10.0/test/test_helper.rb0000644000004100000410000000146013641340551017247 0ustar www-datawww-datarequire "bundler/setup" require "active_support" require "active_support/core_ext/array/access" require "active_support/cache/memory_store" require "active_support/json" require "active_model" require "action_view" require "rails/version" require "jbuilder" require "active_support/testing/autorun" require "mocha/setup" ActiveSupport.test_order = :random class << Rails def cache @cache ||= ActiveSupport::Cache::MemoryStore.new end end class Post < Struct.new(:id, :body, :author_name); end class Racer < Struct.new(:id, :name) extend ActiveModel::Naming include ActiveModel::Conversion end ActionView::Template.register_template_handler :jbuilder, JbuilderHandler ActionView::Base.remove_possible_method :fragment_name_with_digest ActionView::Base.remove_possible_method :cache_fragment_name jbuilder-2.10.0/test/jbuilder_test.rb0000644000004100000410000004257513641340551017604 0ustar www-datawww-datarequire 'test_helper' require 'active_support/inflector' require 'jbuilder' def jbuild(*args, &block) Jbuilder.new(*args, &block).attributes! end Comment = Struct.new(:content, :id) class NonEnumerable def initialize(collection) @collection = collection end delegate :map, :count, to: :@collection end class VeryBasicWrapper < BasicObject def initialize(thing) @thing = thing end def method_missing(name, *args, &block) @thing.send name, *args, &block end end # This is not Struct, because structs are Enumerable 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 class JbuilderTest < ActiveSupport::TestCase teardown do Jbuilder.send :class_variable_set, '@@key_formatter', nil end test 'single key' do result = jbuild do |json| json.content 'hello' end assert_equal 'hello', result['content'] end test 'single key with false value' do result = jbuild do |json| json.content false end assert_equal false, result['content'] end test 'single key with nil value' do result = jbuild do |json| json.content nil end assert result.has_key?('content') assert_nil result['content'] end test 'multiple keys' do result = jbuild do |json| json.title 'hello' json.content 'world' end assert_equal 'hello', result['title'] assert_equal 'world', result['content'] end test 'extracting from object' do person = Struct.new(:name, :age).new('David', 32) result = jbuild do |json| json.extract! person, :name, :age end assert_equal 'David', result['name'] assert_equal 32, result['age'] end test 'extracting from object using call style' do person = Struct.new(:name, :age).new('David', 32) result = jbuild do |json| json.(person, :name, :age) end assert_equal 'David', result['name'] assert_equal 32, result['age'] end test 'extracting from hash' do person = {:name => 'Jim', :age => 34} result = jbuild do |json| json.extract! person, :name, :age end assert_equal 'Jim', result['name'] assert_equal 34, result['age'] end test 'nesting single child with block' do result = jbuild do |json| json.author do json.name 'David' json.age 32 end end assert_equal 'David', result['author']['name'] assert_equal 32, result['author']['age'] end test 'empty block handling' do result = jbuild do |json| json.foo 'bar' json.author do end end assert_equal 'bar', result['foo'] assert !result.key?('author') end test 'blocks are additive' do result = jbuild do |json| json.author do json.name 'David' end json.author do json.age 32 end end assert_equal 'David', result['author']['name'] assert_equal 32, result['author']['age'] end test 'nested blocks are additive' do result = jbuild do |json| json.author do json.name do json.first 'David' end end json.author do json.name do json.last 'Heinemeier Hansson' end end end assert_equal 'David', result['author']['name']['first'] assert_equal 'Heinemeier Hansson', result['author']['name']['last'] end test 'support merge! method' do result = jbuild do |json| json.merge! 'foo' => 'bar' end assert_equal 'bar', result['foo'] end test 'support merge! method in a block' do result = jbuild do |json| json.author do json.merge! 'name' => 'Pavel' end end assert_equal 'Pavel', result['author']['name'] end test 'blocks are additive via extract syntax' do person = Person.new('Pavel', 27) result = jbuild do |json| json.author person, :age json.author person, :name end assert_equal 'Pavel', result['author']['name'] assert_equal 27, result['author']['age'] end test 'arrays are additive' do result = jbuild do |json| json.array! %w[foo] json.array! %w[bar] end assert_equal %w[foo bar], result end test 'nesting multiple children with block' do result = jbuild do |json| json.comments do json.child! { json.content 'hello' } json.child! { json.content 'world' } end end assert_equal 'hello', result['comments'].first['content'] assert_equal 'world', result['comments'].second['content'] end test 'nesting single child with inline extract' do person = Person.new('David', 32) result = jbuild do |json| json.author person, :name, :age end assert_equal 'David', result['author']['name'] assert_equal 32, result['author']['age'] end test 'nesting multiple children from array' do comments = [ Comment.new('hello', 1), Comment.new('world', 2) ] result = jbuild do |json| json.comments comments, :content end assert_equal ['content'], result['comments'].first.keys assert_equal 'hello', result['comments'].first['content'] assert_equal 'world', result['comments'].second['content'] end test 'nesting multiple children from array when child array is empty' do comments = [] result = jbuild do |json| json.name 'Parent' json.comments comments, :content end assert_equal 'Parent', result['name'] assert_equal [], result['comments'] end test 'nesting multiple children from array with inline loop' do comments = [ Comment.new('hello', 1), Comment.new('world', 2) ] result = jbuild do |json| json.comments comments do |comment| json.content comment.content end end assert_equal ['content'], result['comments'].first.keys assert_equal 'hello', result['comments'].first['content'] assert_equal 'world', result['comments'].second['content'] end test 'handles nil-collections as empty arrays' do result = jbuild do |json| json.comments nil do |comment| json.content comment.content end end assert_equal [], result['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) ]) result = jbuild do |json| json.comments comments, :content end assert_equal ['content'], result['comments'].first.keys assert_equal 'hello', result['comments'].first['content'] assert_equal 'world', result['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) ]) result = jbuild do |json| json.comments comments do |comment| json.content comment.content end end assert_equal ['content'], result['comments'].first.keys assert_equal 'hello', result['comments'].first['content'] assert_equal 'world', result['comments'].second['content'] end test 'array! casts array-like objects to array before merging' do wrapped_array = VeryBasicWrapper.new(%w[foo bar]) result = jbuild do |json| json.array! wrapped_array end assert_equal %w[foo bar], result end test 'nesting multiple children from array with inline loop on root' do comments = [ Comment.new('hello', 1), Comment.new('world', 2) ] result = jbuild do |json| json.call(comments) do |comment| json.content comment.content end end assert_equal 'hello', result.first['content'] assert_equal 'world', result.second['content'] end test 'array nested inside nested hash' do result = jbuild 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 assert_equal 'hello', result['author']['comments'].first['content'] assert_equal 'world', result['author']['comments'].second['content'] end test 'array nested inside array' do result = jbuild 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', result['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'] } ] result = jbuild do |json| json.array! data do |object| json.department object[:department] json.names do json.array! object[:names] end end end assert_equal 'David', result[0]['names'].last assert !result[0].key?('not_in_json') end test 'nested jbuilder objects' do to_nest = Jbuilder.new{ |json| json.nested_value 'Nested Test' } result = jbuild do |json| json.value 'Test' json.nested to_nest end expected = {'value' => 'Test', 'nested' => {'nested_value' => 'Nested Test'}} assert_equal expected, result end test 'nested jbuilder object via set!' do to_nest = Jbuilder.new{ |json| json.nested_value 'Nested Test' } result = jbuild do |json| json.value 'Test' json.set! :nested, to_nest end expected = {'value' => 'Test', 'nested' => {'nested_value' => 'Nested Test'}} assert_equal expected, result end test 'top-level array' do comments = [ Comment.new('hello', 1), Comment.new('world', 2) ] result = jbuild do |json| json.array! comments do |comment| json.content comment.content end end assert_equal 'hello', result.first['content'] assert_equal 'world', result.second['content'] end test 'it allows using next in array block to skip value' do comments = [ Comment.new('hello', 1), Comment.new('skip', 2), Comment.new('world', 3) ] result = jbuild do |json| json.array! comments do |comment| next if comment.id == 2 json.content comment.content end end assert_equal 2, result.length assert_equal 'hello', result.first['content'] assert_equal 'world', result.second['content'] end test 'extract attributes directly from array' do comments = [ Comment.new('hello', 1), Comment.new('world', 2) ] result = jbuild do |json| json.array! comments, :content, :id end assert_equal 'hello', result.first['content'] assert_equal 1, result.first['id'] assert_equal 'world', result.second['content'] assert_equal 2, result.second['id'] end test 'empty top-level array' do comments = [] result = jbuild do |json| json.array! comments do |comment| json.content comment.content end end assert_equal [], result end test 'dynamically set a key/value' do result = jbuild do |json| json.set! :each, 'stuff' end assert_equal 'stuff', result['each'] end test 'dynamically set a key/nested child with block' do result = jbuild do |json| json.set! :author do json.name 'David' json.age 32 end end assert_equal 'David', result['author']['name'] assert_equal 32, result['author']['age'] end test 'dynamically sets a collection' do comments = [ Comment.new('hello', 1), Comment.new('world', 2) ] result = jbuild do |json| json.set! :comments, comments, :content end assert_equal ['content'], result['comments'].first.keys assert_equal 'hello', result['comments'].first['content'] assert_equal 'world', result['comments'].second['content'] end test 'query like object' do result = jbuild do |json| json.relations RelationMock.new, :name, :age end assert_equal 2, result['relations'].length assert_equal 'Bob', result['relations'][0]['name'] assert_equal 50, result['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 result = jbuild do |json| json.key_format! camelize: [:lower] json.camel_style 'for JS' end assert_equal ['camelStyle'], result.keys end test 'key_format! with parameter not as an array' do result = jbuild do |json| json.key_format! :camelize => :lower json.camel_style 'for JS' end assert_equal ['camelStyle'], result.keys end test 'key_format! propagates to child elements' do result = jbuild do |json| json.key_format! :upcase json.level1 'one' json.level2 do json.value 'two' end end assert_equal 'one', result['LEVEL1'] assert_equal 'two', result['LEVEL2']['VALUE'] end test 'key_format! resets after child element' do result = jbuild do |json| json.level2 do json.key_format! :upcase json.value 'two' end json.level1 'one' end assert_equal 'two', result['level2']['VALUE'] assert_equal 'one', result['level1'] end test 'key_format! with no parameter' do result = jbuild do |json| json.key_format! :upcase json.lower 'Value' end assert_equal ['LOWER'], result.keys end test 'key_format! with multiple steps' do result = jbuild do |json| json.key_format! :upcase, :pluralize json.pill 'foo' end assert_equal ['PILLs'], result.keys end test 'key_format! with lambda/proc' do result = jbuild do |json| json.key_format! ->(key){ key + ' and friends' } json.oats 'foo' end assert_equal ['oats and friends'], result.keys end test 'default key_format!' do Jbuilder.key_format camelize: :lower result = jbuild{ |json| json.camel_style 'for JS' } assert_equal ['camelStyle'], result.keys end test 'do not use default key formatter directly' do Jbuilder.key_format jbuild{ |json| json.key 'value' } formatter = Jbuilder.send(:class_variable_get, '@@key_formatter') cache = formatter.instance_variable_get('@cache') assert_empty cache end test 'ignore_nil! without a parameter' do result = jbuild do |json| json.ignore_nil! json.test nil end assert_empty result.keys end test 'ignore_nil! with parameter' do result = jbuild do |json| json.ignore_nil! true json.name 'Bob' json.dne nil end assert_equal ['name'], result.keys result = jbuild do |json| json.ignore_nil! false json.name 'Bob' json.dne nil end assert_equal ['name', 'dne'], result.keys end test 'default ignore_nil!' do Jbuilder.ignore_nil result = jbuild do |json| json.name 'Bob' json.dne nil end assert_equal ['name'], result.keys Jbuilder.send(:class_variable_set, '@@ignore_nil', false) end test 'nil!' do result = jbuild do |json| json.key 'value' json.nil! end assert_nil result end test 'null!' do result = jbuild do |json| json.key 'value' json.null! end assert_nil result end test 'null! in a block' do result = jbuild do |json| json.author do json.name 'David' end json.author do json.null! end end assert result.key?('author') assert_nil result['author'] end test 'empty attributes respond to empty?' do attributes = Jbuilder.new.attributes! assert attributes.empty? assert attributes.blank? assert !attributes.present? end test 'throws ArrayError when trying to add a key to an array' do assert_raise Jbuilder::ArrayError do jbuild do |json| json.array! %w[foo bar] json.fizz "buzz" end end end test 'throws NullError when trying to add properties to null' do assert_raise Jbuilder::NullError do jbuild do |json| json.null! json.foo 'bar' end end end test 'throws NullError when trying to add properties to null using block syntax' do assert_raise Jbuilder::NullError do jbuild do |json| json.author do json.null! end json.author do json.name "Pavel" end end end end test "throws MergeError when trying to merge array with non-empty hash" do assert_raise Jbuilder::MergeError do jbuild do |json| json.name "Daniel" json.merge! [] end end end test "throws MergeError when trying to merge hash with array" do assert_raise Jbuilder::MergeError do jbuild do |json| json.array! json.merge!({}) end end end test "throws MergeError when trying to merge invalid objects" do assert_raise Jbuilder::MergeError do jbuild do |json| json.name "Daniel" json.merge! "Nope" end end end if RUBY_VERSION >= "2.2.10" test "respects JSON encoding customizations" do # Active Support overrides Time#as_json for custom formatting. # Ensure we call #to_json on the final attributes instead of JSON.dump. result = JSON.load(Jbuilder.encode { |json| json.time Time.parse("2018-05-13 11:51:00.485 -0400") }) assert_equal "2018-05-13T11:51:00.485-04:00", result["time"] end end end jbuilder-2.10.0/README.md0000644000004100000410000001630313641340551014706 0ustar www-datawww-data# Jbuilder Jbuilder gives you a simple DSL for declaring JSON structures that beats manipulating giant hash structures. This is particularly helpful when the generation process is fraught with conditionals and loops. Here's a simple example: ``` ruby # app/views/messages/show.json.jbuilder 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 ``` This will build the following structure: ``` javascript { "content": "

This is serious monkey business

", "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' ", "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" }} ``` To merge existing hash or array to current context: ``` ruby hash = { author: { name: "David" } } json.post do json.title "Merge HOWTO" json.merge! hash end # => "post": { "title": "Merge HOWTO", "author": { "name": "David" } } ``` Top level arrays can be handled directly. Useful for index and other collection actions. ``` ruby # @comments = @post.comments json.array! @comments do |comment| next if comment.marked_as_spam_by?(current_user) json.body comment.body json.author do json.first_name comment.author.first_name json.last_name comment.author.last_name end end # => [ { "body": "great post...", "author": { "first_name": "Joe", "last_name": "Bloe" }} ] ``` 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 a la 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: 'comments/comment', as: :comment ``` The `as: :some_symbol` is used with partials. It will take care of mapping the passed in object to a variable for the partial. If the value is a collection (either implicitly or explicitly by using the `collection:` option, then each value of the collection is passed to the partial as the variable `some_symbol`. If the value is a singular object, then the object is passed to the partial as the variable `some_symbol`. Be sure not to confuse the `as:` option to mean nesting of the partial. For example: ```ruby # Use the default `views/comments/_comment.json.jbuilder`, putting @comment as the comment local variable. # Note, `comment` attributes are "inlined". json.partial! @comment, as: :comment ``` is quite different than: ```ruby # comment attributes are nested under a "comment" property json.comment do json.partial! "/comments/comment.json.jbuilder", comment: @comment end ``` You can pass any objects into partial templates with or without `:locals` option. ```ruby json.partial! 'sub_template', locals: { user: user } # or json.partial! 'sub_template', user: user ``` 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 ``` To prevent Jbuilder from including null values in the output, you can use the `ignore_nil!` method: ```ruby json.ignore_nil! json.foo nil json.bar "bar" # => { "bar": "bar" } ``` 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 ``` If you are rendering fragments for a collection of objects, have a look at `jbuilder_cache_multi` gem. It uses fetch_multi (>= Rails 4.1) to fetch multiple keys at once. 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 ``` ## Contributing to Jbuilder Jbuilder is the work of many contributors. You're encouraged to submit pull requests, propose features and discuss issues. See [CONTRIBUTING](CONTRIBUTING.md). ## License Jbuilder is released under the [MIT License](http://www.opensource.org/licenses/MIT). jbuilder-2.10.0/gemfiles/0000755000004100000410000000000013641340551015217 5ustar www-datawww-datajbuilder-2.10.0/gemfiles/rails_5_2.gemfile0000644000004100000410000000025313641340551020330 0ustar www-datawww-data# This file was generated by Appraisal source "https://rubygems.org" gem "rake" gem "mocha", require: false gem "appraisal" gem "rails", "~> 5.2.0" gemspec path: "../" jbuilder-2.10.0/gemfiles/rails_5_0.gemfile0000644000004100000410000000025313641340551020326 0ustar www-datawww-data# This file was generated by Appraisal source "https://rubygems.org" gem "rake" gem "mocha", require: false gem "appraisal" gem "rails", "~> 5.0.0" gemspec path: "../" jbuilder-2.10.0/gemfiles/rails_5_1.gemfile0000644000004100000410000000025313641340551020327 0ustar www-datawww-data# This file was generated by Appraisal source "https://rubygems.org" gem "rake" gem "mocha", require: false gem "appraisal" gem "rails", "~> 5.1.0" gemspec path: "../" jbuilder-2.10.0/gemfiles/rails_6_0.gemfile0000644000004100000410000000025313641340551020327 0ustar www-datawww-data# This file was generated by Appraisal source "https://rubygems.org" gem "rake" gem "mocha", require: false gem "appraisal" gem "rails", "~> 6.0.0" gemspec path: "../" jbuilder-2.10.0/gemfiles/rails_head.gemfile0000644000004100000410000000026613641340551020650 0ustar www-datawww-data# This file was generated by Appraisal source "https://rubygems.org" gem "rake" gem "mocha", require: false gem "appraisal" gem "rails", github: "rails/rails" gemspec path: "../" jbuilder-2.10.0/CHANGELOG.md0000644000004100000410000002651113641340551015242 0ustar www-datawww-data# Changelog 2.10.0 ---------- * Requires Rails 5+ and Ruby 2.2+ * Nested hashes are deep-merged 2.9.1 ----- * [Respect JSON encoding customizations](https://github.com/rails/jbuilder/commit/e2e8623b08078ad6a2323ce8ecaf642b7afe1166) 2.9.0 ----- * [Fix passing object with partial without locals](https://github.com/rails/jbuilder/pull/435) * [Fix deprecation warning in Rails 6.0](https://github.com/rails/jbuilder/pull/453) * [Use quotes consistently in generated templates](https://github.com/rails/jbuilder/pull/455) * [Allow omitting timestamps from generated partials](https://github.com/rails/jbuilder/pull/448) * [Respect changing scaffold generator](https://github.com/rails/jbuilder/pull/458) * [Use a symbolic default format for Rails 6.0 forward compatibility](https://github.com/rails/jbuilder/commit/3895a7243f3db292b0bf15513fc05494e6e50576) * [Drop MultiJSON in favor of Ruby standard library JSON](https://github.com/rails/jbuilder/commit/b952ae096eb1828b0fcfde06e6ba62311494ec49) 2.8.0 ----- * [Replace deprecated fragment_cache_key for Rails 5.2 support](https://github.com/rails/jbuilder/pull/430) 2.7.0 ----- * [Requires Rails 4+](https://github.com/rails/jbuilder/commit/5207ff394533177fffdd768bfaa6413a0cd16dc8) * [Fix implicitly rendering a JSON partial with the same name as an HTML partial](https://github.com/rails/jbuilder/pull/400) 2.6.4 ----- * Drop the pessimistic upper-bound restriction on Active Support and MultiJSON to prevent future gemfile resolution deadlocks *DHH* 2.6.3 ----- * Support Rails 5.1 2.6.2 ----- * Fix thor warnings 2.6.1 ----- * [Optimize root caches with cache_root!](https://github.com/rails/jbuilder/pull/370) 2.6.0 ----- * [Rails 5 cache! with expire support](https://github.com/rails/jbuilder/commit/d61e3354563863731bc1f358f495b1dbb7ae9d32) * [Generated view DRYed by using model partial](https://github.com/rails/jbuilder/commit/83256f4d7e9211c9dc47972feaed7fd31e4f7cac) 2.5.0 ----- * [Rails 5 compatibility](https://github.com/rails/jbuilder/commit/64c510ec69d9e63b73ffd5942e802d21a7d14701) 2.4.1 ----- * [Fix controller generators to be Rails 5 compatible](https://github.com/rails/jbuilder/commit/2dc6203c5c4a98701d5b64c2a5200835a48bb533) 2.4.0 ----- * [Rails 5 compatibility](https://github.com/rails/jbuilder/commit/4aa2cfcc19a4634d65a28ffc75f0ac0cb8304115) 2.3.2 ----- * [Remove Mime Types deprecation message](https://github.com/rails/jbuilder/commit/5ba4e4ac654cc8388619538f576fe234659b84ec) 2.3.1 ----- * [Explicitly require ostruct to prevent NameError](https://github.com/rails/jbuilder/pull/281) 2.3.0 ----- * [Add new in-place partial invocation support](https://github.com/rails/jbuilder/commit/1feda7ee605c136e59fb4de970f4674de518e6de) * [Add implicit partial rendering for AM::Models](https://github.com/rails/jbuilder/commit/4d5bf7d0ea92765adb7be36834e84f9855a061df) * [Generate API controller if Rails API option is enabled](https://github.com/rails/jbuilder/commit/db68f6bd327cf42b47ef22d455fb5721a8c2cf5f) * [JBuilder's templates have less priority than app templates](https://github.com/rails/jbuilder/commit/7c1a5f25603ec1f4e51fba3dbba9db23726a5d69) * [Add AC::Helpers module to jbuilder for api only apps](https://github.com/rails/jbuilder/commit/7cf1d1eb7d125caf38309b5427952030011c1aa0) 2.2.16 ------ * [Fix NoMethodError around `api_only` in railtie](https://github.com/rails/jbuilder/commit/b08d1da10b14720b46d383b2917e336060fd9ffa) 2.2.14 ------ * [Make Jbuilder compatible with Rails API](https://github.com/rails/jbuilder/commit/29c0014a9c954c990075d42c45c66075260e924b) 2.2.13 ------ * Several performance optimizations: [#260](https://github.com/rails/jbuilder/pull/260) & [#261](https://github.com/rails/jbuilder/pull/261) 2.2.12 ------ * [Replace explicit block calls with yield for performance](https://github.com/rails/jbuilder/commit/3184f941276ad03a071cf977133d1a32302afa47) 2.2.11 ------ * Generate the templates for Rails 5+ [#258](https://github.com/rails/jbuilder/pull/258) [@amatsuda](https://github.com/amatsuda) 2.2.10 ------ * Add Jbuilder::Blank#empty? to tell if attributes are empty [#257](https://github.com/rails/jbuilder/pull/257) [@a2ikm](https://github.com/a2ikm) 2.2.9 ----- * Support `partial!` call with `locals` option in `JbuilderTemplate` [#251](https://github.com/rails/jbuilder/pull/251) 2.2.8 ----- * [Raise ArrayError when trying to add key to an array](https://github.com/rails/jbuilder/commit/869e4be1ad165ce986d8fca78311bdd3ed166087) 2.2.7 ----- * [Make Blank object serializable with Marshal](https://github.com/rails/jbuilder/commit/7083f28d8b665aa60d0d1b1927ae88bb5c6290ba) 2.2.6 ----- * [Make sure dependency tracker loads after template handler](https://github.com/rails/jbuilder/commit/3ba404b1207b557e14771c90b8832bc01ae67a42) 2.2.5 ----- * [Refactor merge block behavior to raise error for unexpected values](https://github.com/rails/jbuilder/commit/4503162fb26f53f613fc83ac081fd244748b6fe9) 2.2.4 ----- * [Typecast locals hash key during collection render](https://github.com/rails/jbuilder/commit/a6b0c8651a08e01cb53eee38e211c65423f275f7) 2.2.3 ----- * [Move template handler registration into railtie](https://github.com/rails/jbuilder/commit/c8acc5cea6da2a79b7b345adc301cb5ff2517647) * [Do not capture the block where it is possible](https://github.com/rails/jbuilder/commit/973b382c3924cb59fc0e4e25266b18e74d41d646) 2.2.2 ----- * [Fix `Jbuilder#merge!` inside block](https://github.com/rails/jbuilder/commit/a7b328552eb0d36315f75bff813bea7eecf8c1d7) 2.2.1 ----- * [Fix empty block handling](https://github.com/rails/jbuilder/commit/972a11141403269e9b17b45b0c95f8a9788245ee) 2.2.0 ----- * [Allow to skip `array!` iterations by calling `next`](https://github.com/rails/jbuilder/commit/81a63308fb9d5002519dd871f829ccc58067251a) 2.1.2 ----- * [Cast array-like objects to array before merging](https://github.com/rails/jbuilder/commit/7b8c8a1cb09b7f3dd26e5643ebbd6b2ec67185db) 2.1.1 ----- * [Remove unused file](https://github.com/rails/jbuilder/commit/e49e1047976fac93b8242ab212c7b1a463b70809) 2.1.0 ----- * [Blocks and their extract! shortcuts are additive by default](https://github.com/rails/jbuilder/commit/a49390736c5f6e2d7a31111df6531bc28dba9fb1) 2.0.8 ----- * [Eliminate circular dependencies](https://github.com/rails/jbuilder/commit/0879484dc74e7be93b695f66e3708ba48cdb1be3) * [Support cache key generation for complex objects](https://github.com/rails/jbuilder/commit/ca9622cca30c1112dd4408fcb2e658849abe1dd5) * [Remove JbuilderProxy class](https://github.com/rails/jbuilder/commit/5877482fc7da3224e42d4f72a1386f7a3a08173b) * [Move KeyFormatter into a separate file](https://github.com/rails/jbuilder/commit/13fee8464ff53ce853030114283c03c135c052b6) * [Move NullError into a separate file](https://github.com/rails/jbuilder/commit/13fee8464ff53ce853030114283c03c135c052b6) 2.0.7 ----- * [Add destroy notice to scaffold generator](https://github.com/rails/jbuilder/commit/8448e86f8cdfa0f517bd59576947875775a1d43c) 2.0.6 ----- * [Use render short form in controller generator](https://github.com/rails/jbuilder/commit/acf37320a7cea7fcc70c791bc94bd5f46b8349ff) 2.0.5 ----- * [Fix edgecase when json is defined as a method](https://github.com/rails/jbuilder/commit/ca711a0c0a5760e26258ce2d93c14bef8fff0ead) 2.0.4 ----- * [Add cache_if! to conditionally cache JSON fragments](https://github.com/rails/jbuilder/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) * [Meaningful 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 jbuilder-2.10.0/Appraisals0000644000004100000410000000051113641340551015443 0ustar www-datawww-dataappraise "rails-5-0" do gem "rails", "~> 5.0.0" end appraise "rails-5-1" do gem "rails", "~> 5.1.0" end appraise "rails-5-2" do gem "rails", "~> 5.2.0" end if RUBY_VERSION >= "2.5.0" appraise "rails-6-0" do gem "rails", "~> 6.0.0" end appraise "rails-head" do gem "rails", github: "rails/rails" end end jbuilder-2.10.0/.gitignore0000644000004100000410000000007113641340551015412 0ustar www-datawww-datatmp gemfiles/*.lock Gemfile.lock .ruby-version pkg *.gem jbuilder-2.10.0/Rakefile0000644000004100000410000000057013641340551015073 0ustar www-datawww-datarequire "bundler/setup" require "bundler/gem_tasks" require "rake/testtask" if !ENV["APPRAISAL_INITIALIZED"] && !ENV["TRAVIS"] require "appraisal/task" Appraisal::Task.new task default: :appraisal else Rake::TestTask.new do |test| require "rails/version" test.libs << "test" test.test_files = FileList["test/*_test.rb"] end task default: :test end jbuilder-2.10.0/lib/0000755000004100000410000000000013641340551014172 5ustar www-datawww-datajbuilder-2.10.0/lib/generators/0000755000004100000410000000000013641340551016343 5ustar www-datawww-datajbuilder-2.10.0/lib/generators/rails/0000755000004100000410000000000013641340551017455 5ustar www-datawww-datajbuilder-2.10.0/lib/generators/rails/jbuilder_generator.rb0000644000004100000410000000326213641340551023653 0ustar www-datawww-datarequire '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' class_option :timestamps, type: :boolean, default: true 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 template filename_with_extensions('partial'), File.join('app/views', controller_file_path, filename_with_extensions("_#{singular_table_name}")) end protected def attributes_names [:id] + super end def filename_with_extensions(name) [name, :json, :jbuilder] * '.' end def full_attributes_list if options[:timestamps] attributes_list(attributes_names + %w(created_at updated_at)) else attributes_list(attributes_names) end 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 jbuilder-2.10.0/lib/generators/rails/scaffold_controller_generator.rb0000644000004100000410000000075413641340551026102 0ustar www-datawww-datarequire 'rails/generators' require 'rails/generators/rails/scaffold_controller/scaffold_controller_generator' module Rails module Generators class ScaffoldControllerGenerator source_paths << File.expand_path('../templates', __FILE__) hook_for :jbuilder, type: :boolean, default: true private def permitted_params attributes_names.map { |name| ":#{name}" }.join(', ') end unless private_method_defined? :permitted_params end end end jbuilder-2.10.0/lib/generators/rails/templates/0000755000004100000410000000000013641340551021453 5ustar www-datawww-datajbuilder-2.10.0/lib/generators/rails/templates/index.json.jbuilder0000644000004100000410000000020713641340551025253 0ustar www-datawww-datajson.array! @<%= plural_table_name %>, partial: "<%= plural_table_name %>/<%= singular_table_name %>", as: :<%= singular_table_name %> jbuilder-2.10.0/lib/generators/rails/templates/api_controller.rb0000644000004100000410000000357313641340551025024 0ustar www-datawww-data<% 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, :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 # POST <%= route_url %> # POST <%= route_url %>.json def create @<%= singular_table_name %> = <%= orm_class.build(class_name, "#{singular_table_name}_params") %> if @<%= orm_instance.save %> render :show, status: :created, location: <%= "@#{singular_table_name}" %> else render json: <%= "@#{orm_instance.errors}" %>, status: :unprocessable_entity end end # PATCH/PUT <%= route_url %>/1 # PATCH/PUT <%= route_url %>/1.json def update if @<%= orm_instance.update("#{singular_table_name}_params") %> render :show, status: :ok, location: <%= "@#{singular_table_name}" %> else render json: <%= "@#{orm_instance.errors}" %>, status: :unprocessable_entity end end # DELETE <%= route_url %>/1 # DELETE <%= route_url %>/1.json def destroy @<%= orm_instance.destroy %> 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 # Only allow a list of trusted parameters through. def <%= "#{singular_table_name}_params" %> <%- if attributes_names.empty? -%> params.fetch(<%= ":#{singular_table_name}" %>, {}) <%- else -%> params.require(<%= ":#{singular_table_name}" %>).permit(<%= permitted_params %>) <%- end -%> end end <% end -%> jbuilder-2.10.0/lib/generators/rails/templates/partial.json.jbuilder0000644000004100000410000000023113641340551025575 0ustar www-datawww-datajson.extract! <%= singular_table_name %>, <%= full_attributes_list %> json.url <%= singular_table_name %>_url(<%= singular_table_name %>, format: :json) jbuilder-2.10.0/lib/generators/rails/templates/controller.rb0000644000004100000410000000530413641340551024165 0ustar www-datawww-data<% 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 :show, status: :created, location: <%= "@#{singular_table_name}" %> } else format.html { render :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 :show, status: :ok, location: <%= "@#{singular_table_name}" %> } else format.html { render :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, notice: <%= "'#{human_name} was successfully destroyed.'" %> } 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 # Only allow a list of trusted parameters through. def <%= "#{singular_table_name}_params" %> <%- if attributes_names.empty? -%> params.fetch(<%= ":#{singular_table_name}" %>, {}) <%- else -%> params.require(<%= ":#{singular_table_name}" %>).permit(<%= permitted_params %>) <%- end -%> end end <% end -%> jbuilder-2.10.0/lib/generators/rails/templates/show.json.jbuilder0000644000004100000410000000017513641340551025130 0ustar www-datawww-datajson.partial! "<%= plural_table_name %>/<%= singular_table_name %>", <%= singular_table_name %>: @<%= singular_table_name %> jbuilder-2.10.0/lib/jbuilder/0000755000004100000410000000000013641340551015772 5ustar www-datawww-datajbuilder-2.10.0/lib/jbuilder/railtie.rb0000644000004100000410000000174213641340551017754 0ustar www-datawww-datarequire 'rails/railtie' require 'jbuilder/jbuilder_template' class Jbuilder class Railtie < ::Rails::Railtie initializer :jbuilder do ActiveSupport.on_load :action_view do ActionView::Template.register_template_handler :jbuilder, JbuilderHandler require 'jbuilder/dependency_tracker' end if Rails::VERSION::MAJOR >= 5 module ::ActionController module ApiRendering include ActionView::Rendering end end ActiveSupport.on_load :action_controller do if self == ActionController::API include ActionController::Helpers include ActionController::ImplicitRender end end end end if Rails::VERSION::MAJOR >= 4 generators do |app| Rails::Generators.configure! app.config.generators Rails::Generators.hidden_namespaces.uniq! require 'generators/rails/scaffold_controller_generator' end end end end jbuilder-2.10.0/lib/jbuilder/errors.rb0000644000004100000410000000110213641340551017625 0ustar www-datawww-datarequire 'jbuilder/jbuilder' class Jbuilder class NullError < ::NoMethodError def self.build(key) message = "Failed to add #{key.to_s.inspect} property to null object" new(message) end end class ArrayError < ::StandardError def self.build(key) message = "Failed to add #{key.to_s.inspect} property to an array" new(message) end end class MergeError < ::StandardError def self.build(current_value, updates) message = "Can't merge #{updates.inspect} into #{current_value.inspect}" new(message) end end end jbuilder-2.10.0/lib/jbuilder/jbuilder_template.rb0000644000004100000410000001526513641340551022023 0ustar www-datawww-datarequire 'jbuilder/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) @context = context @cached_root = nil super(*args) end def partial!(*args) if args.one? && _is_active_model?(args.first) _render_active_model_partial args.first else _render_explicit_partial(*args) 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={}) if @context.controller.perform_caching value = _cache_fragment_for(key, options) do _scope { yield self } end merge! value else yield end end # Caches the json structure at the root using a string rather than the hash structure. This is considerably # faster, but the drawback is that it only works, as the name hints, at the root. So you cannot # use this approach to cache deeper inside the hierarchy, like in partials or such. Continue to use #cache! there. # # Example: # # json.cache_root! @person do # json.extract! @person, :name, :age # end # # # json.extra 'This will not work either, the root must be exclusive' def cache_root!(key=nil, options={}) if @context.controller.perform_caching raise "cache_root! can't be used after JSON structures have been defined" if @attributes.present? @cached_root = _cache_fragment_for([ :root, key ], options) { yield; target! } else yield end end # Conditionally caches 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 def target! @cached_root || super end def array!(collection = [], *args) options = args.first if args.one? && _partial_options?(options) partial! options.merge(collection: collection) else super end end def set!(name, object = BLANK, *args) options = args.first if args.one? && _partial_options?(options) _set_inline_partial name, object, options else super end end private def _render_partial_with_options(options) options.reverse_merge! locals: options.except(:partial, :as, :collection) options.reverse_merge! ::JbuilderTemplate.template_lookup_options as = options[:as] if as && options.key?(:collection) as = as.to_sym collection = options.delete(:collection) locals = options.delete(:locals) array! collection do |member| member_locals = locals.clone member_locals.merge! collection: collection member_locals.merge! as => member _render_partial options.merge(locals: member_locals) end else _render_partial options end end def _render_partial(options) options[:locals].merge! json: self @context.render options end def _cache_fragment_for(key, options, &block) key = _cache_key(key, options) _read_fragment_cache(key, options) || _write_fragment_cache(key, options, &block) end def _read_fragment_cache(key, options = nil) @context.controller.instrument_fragment_cache :read_fragment, key do ::Rails.cache.read(key, options) end end def _write_fragment_cache(key, options = nil) @context.controller.instrument_fragment_cache :write_fragment, key do yield.tap do |value| ::Rails.cache.write(key, value, options) end end end def _cache_key(key, options) name_options = options.slice(:skip_digest, :virtual_path) key = _fragment_name_with_digest(key, name_options) if @context.respond_to?(:combined_fragment_cache_key) key = @context.combined_fragment_cache_key(key) else key = url_for(key).split('://', 2).last if ::Hash === key end ::ActiveSupport::Cache.expand_cache_key(key, :jbuilder) end def _fragment_name_with_digest(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 key end end def _partial_options?(options) ::Hash === options && options.key?(:as) && options.key?(:partial) end def _is_active_model?(object) object.class.respond_to?(:model_name) && object.respond_to?(:to_partial_path) end def _set_inline_partial(name, object, options) value = if object.nil? [] elsif _is_collection?(object) _scope{ _render_partial_with_options options.merge(collection: object) } else locals = ::Hash[options[:as], object] _scope{ _render_partial_with_options options.merge(locals: locals) } end set! name, value end def _render_explicit_partial(name_or_options, locals = {}) case name_or_options when ::Hash # partial! partial: 'name', foo: 'bar' options = name_or_options else # partial! 'name', locals: {foo: 'bar'} if locals.one? && (locals.keys.first == :locals) options = locals.merge(partial: name_or_options) else options = { partial: name_or_options, locals: locals } end # partial! 'name', foo: 'bar' as = locals.delete(:as) options[:as] = as if as.present? options[:collection] = locals[:collection] if locals.key?(:collection) end _render_partial_with_options options end def _render_active_model_partial(object) @context.render object, json: self end end class JbuilderHandler cattr_accessor :default_format self.default_format = :json def self.call(template, source = nil) source ||= template.source # this juggling is required to keep line numbers right in the error %{__already_defined = defined?(json); json||=JbuilderTemplate.new(self); #{source} json.target! unless (__already_defined && __already_defined != "method")} end end jbuilder-2.10.0/lib/jbuilder/blank.rb0000644000004100000410000000020313641340551017401 0ustar www-datawww-dataclass Jbuilder class Blank def ==(other) super || Blank === other end def empty? true end end end jbuilder-2.10.0/lib/jbuilder/key_formatter.rb0000644000004100000410000000125513641340551021175 0ustar www-datawww-datarequire 'jbuilder/jbuilder' require 'active_support/core_ext/array' class Jbuilder class KeyFormatter def initialize(*args) @format = {} @cache = {} options = args.extract_options! args.each do |name| @format[name] = [] end options.each do |name, parameters| @format[name] = parameters 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 end jbuilder-2.10.0/lib/jbuilder/jbuilder.rb0000644000004100000410000000027313641340551020121 0ustar www-datawww-dataJbuilder = Class.new(begin require 'active_support/proxy_object' ActiveSupport::ProxyObject rescue LoadError require 'active_support/basic_object' ActiveSupport::BasicObject end) jbuilder-2.10.0/lib/jbuilder/dependency_tracker.rb0000644000004100000410000000326513641340551022156 0ustar www-datawww-datarequire 'jbuilder/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 jbuilder-2.10.0/lib/jbuilder.rb0000644000004100000410000002073613641340551016327 0ustar www-datawww-datarequire 'jbuilder/jbuilder' require 'jbuilder/blank' require 'jbuilder/key_formatter' require 'jbuilder/errors' require 'json' require 'ostruct' require 'active_support/core_ext/hash/deep_merge' class Jbuilder @@key_formatter = nil @@ignore_nil = false def initialize(options = {}) @attributes = {} @key_formatter = options.fetch(:key_formatter){ @@key_formatter ? @@key_formatter.clone : nil} @ignore_nil = options.fetch(:ignore_nil, @@ignore_nil) yield self if ::Kernel.block_given? end # Yields a builder and automatically turns the result into a JSON string def self.encode(*args, &block) new(*args, &block).target! end BLANK = Blank.new NON_ENUMERABLES = [ ::Struct, ::OpenStruct ].to_set def set!(key, value = BLANK, *args, &block) result = if ::Kernel.block_given? if !_blank?(value) # json.comments @post.comments { |comment| ... } # { "comments": [ { ... }, { ... } ] } _scope{ array! value, &block } else # json.comments { ... } # { "comments": ... } _merge_block(key){ 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 _is_collection?(value) # 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" } } _merge_block(key){ extract! value, *args } end _set_value key, result end def method_missing(*args, &block) if ::Kernel.block_given? set!(*args, &block) else set!(*args) end end # 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 } ] # # 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) array = if collection.nil? [] elsif ::Kernel.block_given? _map_collection(collection, &block) elsif attributes.any? _map_collection(collection) { |element| extract! element, *attributes } else collection.to_a end merge! array 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 ::Kernel.block_given? 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) @attributes = _merge_values(@attributes, hash_or_array) end # Encodes the current builder as JSON. def target! @attributes.to_json 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 _merge_block(key) current_value = _blank? ? BLANK : @attributes.fetch(_key(key), BLANK) raise NullError.build(key) if current_value.nil? new_value = _scope{ yield self } _merge_values(current_value, new_value) end def _merge_values(current_value, updates) if _blank?(updates) current_value elsif _blank?(current_value) || updates.nil? || current_value.empty? && ::Array === updates updates elsif ::Array === current_value && ::Array === updates current_value + updates elsif ::Hash === current_value && ::Hash === updates current_value.deep_merge(updates) else raise MergeError.build(current_value, updates) end end def _key(key) @key_formatter ? @key_formatter.format(key) : key.to_s end def _set_value(key, value) raise NullError.build(key) if @attributes.nil? raise ArrayError.build(key) if ::Array === @attributes return if @ignore_nil && value.nil? or _blank?(value) @attributes = {} if _blank? @attributes[_key(key)] = value end def _map_collection(collection) collection.map do |element| _scope{ yield element } end - [BLANK] end def _scope parent_attributes, parent_formatter = @attributes, @key_formatter @attributes = BLANK yield @attributes ensure @attributes, @key_formatter = parent_attributes, parent_formatter end def _is_collection?(object) _object_respond_to?(object, :map, :count) && NON_ENUMERABLES.none?{ |klass| klass === object } end def _blank?(value=@attributes) BLANK == value end def _object_respond_to?(object, *methods) methods.all?{ |m| object.respond_to?(m) } end end require 'jbuilder/railtie' if defined?(Rails) jbuilder-2.10.0/CONTRIBUTING.md0000644000004100000410000000614613641340551015664 0ustar www-datawww-dataContributing to Jbuilder ===================== [![Build Status](https://api.travis-ci.org/rails/jbuilder.svg?branch=master)][travis] [![Gem Version](https://badge.fury.io/rb/jbuilder.svg)][gem] [![Code Climate](https://codeclimate.com/github/rails/jbuilder/badges/gpa.svg)][codeclimate] [![Dependencies Status](https://gemnasium.com/rails/jbuilder.svg)][gemnasium] [travis]: https://travis-ci.org/rails/jbuilder [gem]: https://rubygems.org/gems/jbuilder [codeclimate]: https://codeclimate.com/github/rails/jbuilder [gemnasium]: https://gemnasium.com/rails/jbuilder Jbuilder is work of [many contributors](https://github.com/rails/jbuilder/graphs/contributors). You're encouraged to submit [pull requests](https://github.com/rails/jbuilder/pulls), [propose features and discuss issues](https://github.com/rails/jbuilder/issues). #### Fork the Project Fork the [project on Github](https://github.com/rails/jbuilder) and check out your copy. ``` git clone https://github.com/contributor/jbuilder.git cd jbuilder git remote add upstream https://github.com/rails/jbuilder.git ``` #### Create a Topic Branch Make sure your fork is up-to-date and create a topic branch for your feature or bug fix. ``` git checkout master git pull upstream master git checkout -b my-feature-branch ``` #### Bundle Install and Test Ensure that you can build the project and run tests. ``` bundle install appraisal install appraisal rake test ``` #### Write Tests Try to write a test that reproduces the problem you're trying to fix or describes a feature that you want to build. Add to [test](test). We definitely appreciate pull requests that highlight or reproduce a problem, even without a fix. #### Write Code Implement your feature or bug fix. Make sure that `appraisal rake test` completes without errors. #### Write Documentation Document any external behavior in the [README](README.md). #### Commit Changes Make sure git knows your name and email address: ``` git config --global user.name "Your Name" git config --global user.email "contributor@example.com" ``` Writing good commit logs is important. A commit log should describe what changed and why. ``` git add ... git commit ``` #### Push ``` git push origin my-feature-branch ``` #### Make a Pull Request Visit your forked repo and click the 'New pull request' button. Select your feature branch, fill out the form, and click the 'Create pull request' button. Pull requests are usually reviewed within a few days. #### Rebase If you've been working on a change for a while, rebase with upstream/master. ``` git fetch upstream git rebase upstream/master git push origin my-feature-branch -f ``` #### Check on Your Pull Request Go back to your pull request after a few minutes and see whether it passed muster with Travis-CI. Everything should look green, otherwise fix issues and amend your commit as described above. #### Be Patient It's likely that your change will not be merged and that the nitpicky maintainers will ask you to do more, or fix seemingly benign problems. Hang in there! #### Thank You Please do know that we really appreciate and value your time and work. We love you, really. jbuilder-2.10.0/jbuilder.gemspec0000644000004100000410000000075313641340551016576 0ustar www-datawww-dataGem::Specification.new do |s| s.name = 'jbuilder' s.version = '2.10.0' s.authors = 'David Heinemeier Hansson' s.email = 'david@basecamp.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 = '>= 2.2.2' s.add_dependency 'activesupport', '>= 5.0.0' s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- test/*`.split("\n") end jbuilder-2.10.0/Gemfile0000644000004100000410000000013713641340551014720 0ustar www-datawww-datasource "https://rubygems.org" gemspec gem "rake" gem "mocha", require: false gem "appraisal" jbuilder-2.10.0/MIT-LICENSE0000644000004100000410000000207413641340551015063 0ustar www-datawww-dataCopyright (c) 2011-2018 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.