jbuilder-2.12.0/ 0000755 0000041 0000041 00000000000 14621622754 013435 5 ustar www-data www-data jbuilder-2.12.0/.gitignore 0000644 0000041 0000041 00000000117 14621622754 015424 0 ustar www-data www-data tmp /log gemfiles/.bundle gemfiles/*.lock Gemfile.lock .ruby-version pkg *.gem jbuilder-2.12.0/CONTRIBUTING.md 0000644 0000041 0000041 00000006011 14621622754 015664 0 ustar www-data www-data Contributing to Jbuilder ===================== [][test] [][gem] [][codeclimate] [test]: https://github.com/rails/jbuilder/actions?query=branch%3Amaster [gem]: https://rubygems.org/gems/jbuilder [codeclimate]: https://codeclimate.com/github/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 GitHub Actions. 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.12.0/.github/ 0000755 0000041 0000041 00000000000 14621622754 014775 5 ustar www-data www-data jbuilder-2.12.0/.github/workflows/ 0000755 0000041 0000041 00000000000 14621622754 017032 5 ustar www-data www-data jbuilder-2.12.0/.github/workflows/ruby.yml 0000644 0000041 0000041 00000004150 14621622754 020536 0 ustar www-data www-data name: Ruby test on: [push, pull_request] jobs: test: name: Ruby ${{ matrix.ruby }} (${{ matrix.gemfile }}) runs-on: ubuntu-20.04 continue-on-error: ${{ matrix.gemfile == 'rails_head' }} env: BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile BUNDLE_JOBS: 4 BUNDLE_RETRY: 3 strategy: fail-fast: false matrix: ruby: - "2.7" - "3.0" - "3.1" - "3.2" - "3.3" gemfile: - "rails_6_1" - "rails_7_0" - "rails_7_1" - "rails_head" exclude: - ruby: 2.7 gemfile: rails_head - ruby: '3.0' gemfile: rails_head include: - ruby: 2.2 gemfile: rails_5_0 - ruby: 2.2 gemfile: rails_5_1 - ruby: 2.3 gemfile: rails_5_0 - ruby: 2.3 gemfile: rails_5_1 - ruby: 2.3 gemfile: rails_5_2 - ruby: 2.4 gemfile: rails_5_0 - ruby: 2.4 gemfile: rails_5_1 - ruby: 2.4 gemfile: rails_5_2 - ruby: 2.5 gemfile: rails_5_0 - ruby: 2.5 gemfile: rails_5_1 - ruby: 2.5 gemfile: rails_5_2 - ruby: 2.5 gemfile: rails_6_0 - ruby: 2.5 gemfile: rails_6_1 - ruby: 2.6 gemfile: rails_5_0 - ruby: 2.6 gemfile: rails_5_1 - ruby: 2.6 gemfile: rails_5_2 - ruby: 2.6 gemfile: rails_6_0 - ruby: 2.6 gemfile: rails_6_1 - ruby: 2.7 gemfile: rails_6_0 - ruby: '3.1' gemfile: rails_head - ruby: '3.2' gemfile: rails_head - ruby: head gemfile: rails_head steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true - name: Ruby test run: bundle exec rake jbuilder-2.12.0/lib/ 0000755 0000041 0000041 00000000000 14621622754 014203 5 ustar www-data www-data jbuilder-2.12.0/lib/generators/ 0000755 0000041 0000041 00000000000 14621622754 016354 5 ustar www-data www-data jbuilder-2.12.0/lib/generators/rails/ 0000755 0000041 0000041 00000000000 14621622754 017466 5 ustar www-data www-data jbuilder-2.12.0/lib/generators/rails/templates/ 0000755 0000041 0000041 00000000000 14621622754 021464 5 ustar www-data www-data jbuilder-2.12.0/lib/generators/rails/templates/api_controller.rb 0000644 0000041 0000041 00000003565 14621622754 025036 0 ustar www-data www-data <% if namespaced? -%> require_dependency "<%= namespaced_path %>/application_controller" <% end -%> <% module_namespacing do -%> class <%= controller_class_name %>Controller < ApplicationController before_action :set_<%= singular_table_name %>, only: %i[ 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.12.0/lib/generators/rails/templates/index.json.jbuilder 0000644 0000041 0000041 00000000154 14621622754 025265 0 ustar www-data www-data json.array! @<%= plural_table_name %>, partial: "<%= partial_path_name %>", as: :<%= singular_table_name %> jbuilder-2.12.0/lib/generators/rails/templates/partial.json.jbuilder 0000644 0000041 0000041 00000001366 14621622754 025620 0 ustar www-data www-data json.extract! <%= singular_table_name %>, <%= full_attributes_list %> json.url <%= singular_table_name %>_url(<%= singular_table_name %>, format: :json) <%- virtual_attributes.each do |attribute| -%> <%- if attribute.type == :rich_text -%> json.<%= attribute.name %> <%= singular_table_name %>.<%= attribute.name %>.to_s <%- elsif attribute.type == :attachment -%> json.<%= attribute.name %> url_for(<%= singular_table_name %>.<%= attribute.name %>) <%- elsif attribute.type == :attachments -%> json.<%= attribute.name %> do json.array!(<%= singular_table_name %>.<%= attribute.name %>) do |<%= attribute.singular_name %>| json.id <%= attribute.singular_name %>.id json.url url_for(<%= attribute.singular_name %>) end end <%- end -%> <%- end -%> jbuilder-2.12.0/lib/generators/rails/templates/show.json.jbuilder 0000644 0000041 0000041 00000000142 14621622754 025133 0 ustar www-data www-data json.partial! "<%= partial_path_name %>", <%= singular_table_name %>: @<%= singular_table_name %> jbuilder-2.12.0/lib/generators/rails/templates/controller.rb 0000644 0000041 0000041 00000005311 14621622754 024174 0 ustar www-data www-data <% if namespaced? -%> require_dependency "<%= namespaced_path %>/application_controller" <% end -%> <% module_namespacing do -%> class <%= controller_class_name %>Controller < ApplicationController before_action :set_<%= singular_table_name %>, only: %i[ show edit update destroy ] # GET <%= route_url %> or <%= route_url %>.json def index @<%= plural_table_name %> = <%= orm_class.all(class_name) %> end # GET <%= route_url %>/1 or <%= 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 %> or <%= 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 <%= show_helper %>, notice: <%= %("#{human_name} was successfully created.") %> } format.json { render :show, status: :created, location: <%= "@#{singular_table_name}" %> } else format.html { render :new, status: :unprocessable_entity } format.json { render json: <%= "@#{orm_instance.errors}" %>, status: :unprocessable_entity } end end end # PATCH/PUT <%= route_url %>/1 or <%= route_url %>/1.json def update respond_to do |format| if @<%= orm_instance.update("#{singular_table_name}_params") %> format.html { redirect_to <%= show_helper %>, notice: <%= %("#{human_name} was successfully updated.") %> } format.json { render :show, status: :ok, location: <%= "@#{singular_table_name}" %> } else format.html { render :edit, status: :unprocessable_entity } format.json { render json: <%= "@#{orm_instance.errors}" %>, status: :unprocessable_entity } end end end # DELETE <%= route_url %>/1 or <%= 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.12.0/lib/generators/rails/jbuilder_generator.rb 0000644 0000041 0000041 00000003634 14621622754 023667 0 ustar www-data www-data 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' 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 def virtual_attributes attributes.select {|name| name.respond_to?(:virtual?) && name.virtual? } end def partial_path_name [controller_file_path, singular_table_name].join("/") end end end end jbuilder-2.12.0/lib/generators/rails/scaffold_controller_generator.rb 0000644 0000041 0000041 00000000754 14621622754 026113 0 ustar www-data www-data require '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.12.0/lib/jbuilder.rb 0000644 0000041 0000041 00000023702 14621622754 016334 0 ustar www-data www-data require 'active_support' require 'jbuilder/jbuilder' require 'jbuilder/blank' require 'jbuilder/key_formatter' require 'jbuilder/errors' require 'json' require 'active_support/core_ext/hash/deep_merge' begin require 'ostruct' rescue LoadError end class Jbuilder @@key_formatter = nil @@ignore_nil = false @@deep_format_keys = 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) @deep_format_keys = options.fetch(:deep_format_keys, @@deep_format_keys) 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 = defined?(::OpenStruct) ? [::Struct, ::OpenStruct].to_set : [::Struct].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": { ... } _format_keys(value.attributes!) else # json.age 32 # { "age": 32 } _format_keys(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 # Deeply apply key format to nested hashes and arrays passed to # methods like set!, merge! or array!. # # Example: # # json.key_format! camelize: :lower # json.settings({some_value: "abc"}) # # { "settings": { "some_value": "abc" }} # # json.key_format! camelize: :lower # json.deep_format_keys! # json.settings({some_value: "abc"}) # # { "settings": { "someValue": "abc" }} # def deep_format_keys!(value = true) @deep_format_keys = value end # Same as instance method deep_format_keys! except sets the default. def self.deep_format_keys(value = true) @@deep_format_keys = 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 _format_keys(collection.to_a) end @attributes = _merge_values(@attributes, 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, array, or Jbuilder instance into current builder. def merge!(object) hash_or_array = ::Jbuilder === object ? object.attributes! : object @attributes = _merge_values(@attributes, _format_keys(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, _format_keys(object.fetch(key)) } end def _extract_method_values(object, attributes) attributes.each{ |key| _set_value key, _format_keys(object.public_send(key)) } end def _merge_block(key) current_value = _blank? ? BLANK : @attributes.fetch(_key(key), BLANK) ::Kernel.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 ::Kernel.raise MergeError.build(current_value, updates) end end def _key(key) @key_formatter ? @key_formatter.format(key) : key.to_s end def _format_keys(hash_or_array) return hash_or_array unless @deep_format_keys if ::Array === hash_or_array hash_or_array.map { |value| _format_keys(value) } elsif ::Hash === hash_or_array ::Hash[hash_or_array.collect { |k, v| [_key(k), _format_keys(v)] }] else hash_or_array end end def _set_value(key, value) ::Kernel.raise NullError.build(key) if @attributes.nil? ::Kernel.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, parent_deep_format_keys = @attributes, @key_formatter, @deep_format_keys @attributes = BLANK yield @attributes ensure @attributes, @key_formatter, @deep_format_keys = parent_attributes, parent_formatter, parent_deep_format_keys 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.12.0/lib/jbuilder/ 0000755 0000041 0000041 00000000000 14621622754 016003 5 ustar www-data www-data jbuilder-2.12.0/lib/jbuilder/key_formatter.rb 0000644 0000041 0000041 00000001255 14621622754 021206 0 ustar www-data www-data require '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.12.0/lib/jbuilder/jbuilder_dependency_tracker.rb 0000644 0000041 0000041 00000003667 14621622754 024055 0 ustar www-data www-data class Jbuilder::DependencyTracker EXPLICIT_DEPENDENCY = /# Template Dependency: (\S+)/ # 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 self.call(name, template, view_paths = nil) new(name, template, view_paths).dependencies end def initialize(name, template, view_paths = nil) @name, @template, @view_paths = name, template, view_paths end def dependencies direct_dependencies + indirect_dependencies + explicit_dependencies end private attr_reader :name, :template def direct_dependencies source.scan(DIRECT_RENDERS).map(&:second) end def indirect_dependencies source.scan(INDIRECT_RENDERS).map(&:second) end def explicit_dependencies dependencies = source.scan(EXPLICIT_DEPENDENCY).flatten.uniq wildcards, explicits = dependencies.partition { |dependency| dependency.end_with?("/*") } (explicits + resolve_directories(wildcards)).uniq end def resolve_directories(wildcard_dependencies) return [] unless @view_paths return [] if wildcard_dependencies.empty? # Remove trailing "/*" prefixes = wildcard_dependencies.map { |query| query[0..-3] } @view_paths.flat_map(&:all_template_paths).uniq.filter_map { |path| path.to_s if prefixes.include?(path.prefix) }.sort end def source template.source end end jbuilder-2.12.0/lib/jbuilder/jbuilder.rb 0000644 0000041 0000041 00000000042 14621622754 020124 0 ustar www-data www-data Jbuilder = Class.new(BasicObject) jbuilder-2.12.0/lib/jbuilder/collection_renderer.rb 0000644 0000041 0000041 00000005525 14621622754 022360 0 ustar www-data www-data require 'delegate' require 'active_support/concern' require 'action_view' begin require 'action_view/renderer/collection_renderer' rescue LoadError require 'action_view/renderer/partial_renderer' end class Jbuilder module CollectionRenderable # :nodoc: extend ActiveSupport::Concern class_methods do def supported? superclass.private_method_defined?(:build_rendered_template) && self.superclass.private_method_defined?(:build_rendered_collection) end end private def build_rendered_template(content, template, layout = nil) super(content || json.attributes!, template) end def build_rendered_collection(templates, _spacer) json.merge!(templates.map(&:body)) end def json @options[:locals].fetch(:json) end class ScopedIterator < ::SimpleDelegator # :nodoc: include Enumerable def initialize(obj, scope) super(obj) @scope = scope end # Rails 6.0 support: def each return enum_for(:each) unless block_given? __getobj__.each do |object| @scope.call { yield(object) } end end # Rails 6.1 support: def each_with_info return enum_for(:each_with_info) unless block_given? __getobj__.each_with_info do |object, info| @scope.call { yield(object, info) } end end end private_constant :ScopedIterator end if defined?(::ActionView::CollectionRenderer) # Rails 6.1 support: class CollectionRenderer < ::ActionView::CollectionRenderer # :nodoc: include CollectionRenderable def initialize(lookup_context, options, &scope) super(lookup_context, options) @scope = scope end private def collection_with_template(view, template, layout, collection) super(view, template, layout, ScopedIterator.new(collection, @scope)) end end else # Rails 6.0 support: class CollectionRenderer < ::ActionView::PartialRenderer # :nodoc: include CollectionRenderable def initialize(lookup_context, options, &scope) super(lookup_context) @options = options @scope = scope end def render_collection_with_partial(collection, partial, context, block) render(context, @options.merge(collection: collection, partial: partial), block) end private def collection_without_template(view) @collection = ScopedIterator.new(@collection, @scope) super(view) end def collection_with_template(view, template) @collection = ScopedIterator.new(@collection, @scope) super(view, template) end end end class EnumerableCompat < ::SimpleDelegator # Rails 6.1 requires this. def size(*args, &block) __getobj__.count(*args, &block) end end end jbuilder-2.12.0/lib/jbuilder/blank.rb 0000644 0000041 0000041 00000000203 14621622754 017412 0 ustar www-data www-data class Jbuilder class Blank def ==(other) super || Blank === other end def empty? true end end end jbuilder-2.12.0/lib/jbuilder/errors.rb 0000644 0000041 0000041 00000001102 14621622754 017636 0 ustar www-data www-data require '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.12.0/lib/jbuilder/railtie.rb 0000644 0000041 0000041 00000001745 14621622754 017770 0 ustar www-data www-data require 'rails' 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/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 name == '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.12.0/lib/jbuilder/jbuilder_template.rb 0000644 0000041 0000041 00000021001 14621622754 022015 0 ustar www-data www-data require 'jbuilder/jbuilder' require 'jbuilder/collection_renderer' 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 # Generates JSON using the template specified with the `:partial` option. For example, the code below will render # the file `views/comments/_comments.json.jbuilder`, and set a local variable comments with all this message's # comments, which can be used inside the partial. # # Example: # # json.partial! 'comments/comments', comments: @message.comments # # There are multiple ways to generate a collection of elements as JSON, as ilustrated below: # # Example: # # 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 # # Aside from that, the `:cached` options is available on Rails >= 6.0. This will cache the rendered results # effectively using the multi fetch feature. # # Example: # # json.array! @posts, partial: "posts/post", as: :post, cached: true # # json.comments @post.comments, partial: "comments/comment", as: :comment, cached: true # 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 ::Kernel.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, :cached) options.reverse_merge! ::JbuilderTemplate.template_lookup_options as = options[:as] if as && options.key?(:collection) && CollectionRenderer.supported? collection = options.delete(:collection) || [] partial = options.delete(:partial) options[:locals].merge!(json: self) collection = EnumerableCompat.new(collection) if collection.respond_to?(:count) && !collection.respond_to?(:size) if options.has_key?(:layout) ::Kernel.raise ::NotImplementedError, "The `:layout' option is not supported in collection rendering." end if options.has_key?(:spacer_template) ::Kernel.raise ::NotImplementedError, "The `:spacer_template' option is not supported in collection rendering." end results = CollectionRenderer .new(@context.lookup_context, options) { |&block| _scope(&block) } .render_collection_with_partial(collection, partial, @context, nil) array! if results.respond_to?(:body) && results.body.nil? elsif as && options.key?(:collection) && !CollectionRenderer.supported? # For Rails <= 5.2: 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) @context.cache_fragment_name(key, **options) 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.12.0/Appraisals 0000644 0000041 0000041 00000001266 14621622754 015464 0 ustar www-data www-data if RUBY_VERSION < "2.7.0" appraise "rails-5-0" do gem "rails", "~> 5.0.0" gem "loofah", "< 2.21.0" end appraise "rails-5-1" do gem "rails", "~> 5.1.0" gem "loofah", "< 2.21.0" end appraise "rails-5-2" do gem "rails", "~> 5.2.0" gem "loofah", "< 2.21.0" end end if RUBY_VERSION >= "2.5.0" appraise "rails-6-0" do gem "rails", "~> 6.0.0" end appraise "rails-6-1" do gem "rails", "~> 6.1.0" end end if RUBY_VERSION >= "2.7.0" appraise "rails-7-0" do gem "rails", "~> 7.0.0" end appraise "rails-7-1" do gem "rails", "~> 7.1.0" end appraise "rails-head" do gem "rails", github: "rails/rails", branch: "main" end end jbuilder-2.12.0/test/ 0000755 0000041 0000041 00000000000 14621622754 014414 5 ustar www-data www-data jbuilder-2.12.0/test/test_helper.rb 0000644 0000041 0000041 00000002031 14621622754 017253 0 ustar www-data www-data require "bundler/setup" require "rails" require "jbuilder" require "active_support/core_ext/array/access" require "active_support/cache/memory_store" require "active_support/json" require "active_model" require 'action_controller/railtie' require 'action_view/railtie' require "active_support/testing/autorun" require "mocha/minitest" ActiveSupport.test_order = :random ENV["RAILS_ENV"] ||= "test" class << Rails def cache @cache ||= ActiveSupport::Cache::MemoryStore.new end end Jbuilder::CollectionRenderer.collection_cache = Rails.cache class Post < Struct.new(:id, :body, :author_name) def cache_key "post-#{id}" end end class Racer < Struct.new(:id, :name) extend ActiveModel::Naming include ActiveModel::Conversion end # Instantiate an Application in order to trigger the initializers Class.new(Rails::Application) do config.secret_key_base = 'secret' config.eager_load = false end.initialize! # Touch AV::Base in order to trigger :action_view on_load hook before running the tests ActionView::Base.inspect jbuilder-2.12.0/test/jbuilder_generator_test.rb 0000644 0000041 0000041 00000005013 14621622754 021645 0 ustar www-data www-data require '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 test 'namespaced views are generated correctly for index' do run_generator %w(Admin::Post --model-name=Post) assert_file 'app/views/admin/posts/index.json.jbuilder' do |content| assert_match %r{json\.array! @posts, partial: "admin/posts/post", as: :post}, content end assert_file 'app/views/admin/posts/show.json.jbuilder' do |content| assert_match %r{json\.partial! "admin/posts/post", post: @post}, content end end if Rails::VERSION::MAJOR >= 6 test 'handles virtual attributes' do run_generator %w(Message content:rich_text video:attachment photos:attachments) assert_file 'app/views/messages/_message.json.jbuilder' do |content| assert_match %r{json\.content message\.content\.to_s}, content assert_match %r{json\.video url_for\(message\.video\)}, content assert_match %r{json\.photos do\n json\.array!\(message\.photos\) do \|photo\|\n json\.id photo\.id\n json\.url url_for\(photo\)\n end\nend}, content end end end end jbuilder-2.12.0/test/jbuilder_template_test.rb 0000644 0000041 0000041 00000031760 14621622754 021502 0 ustar www-data www-data require "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 caching" do result = render('json.partial! "post", collection: @posts, cached: true, 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 Time.iso8601("2018-05-12T11:29:00-04:00") 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 if JbuilderTemplate::CollectionRenderer.supported? test "returns an empty array for an empty collection" do result = render('json.array! @posts, partial: "post", as: :post, cached: true', posts: []) # Do not use #assert_empty as it is important to ensure that the type of the JSON result is an array. assert_equal [], result end test "works with an enumerable object" do enumerable_class = Class.new do include Enumerable def each(&block) [].each(&block) end end result = render('json.array! @posts, partial: "post", as: :post, cached: true', posts: enumerable_class.new) # Do not use #assert_empty as it is important to ensure that the type of the JSON result is an array. assert_equal [], result end test "supports the cached: true option" do result = render('json.array! @posts, partial: "post", as: :post, cached: true', 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"] expected = { "id" => 1, "body" => "Post #1", "author" => { "first_name" => "David", "last_name" => "Heinemeier Hansson" } } assert_equal expected, Rails.cache.read("post-1") result = render('json.array! @posts, partial: "post", as: :post, cached: true', 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 "supports the cached: ->() {} option" do result = render('json.array! @posts, partial: "post", as: :post, cached: ->(post) { [post, "foo"] }', 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"] expected = { "id" => 1, "body" => "Post #1", "author" => { "first_name" => "David", "last_name" => "Heinemeier Hansson" } } assert_equal expected, Rails.cache.read("post-1/foo") result = render('json.array! @posts, partial: "post", as: :post, cached: ->(post) { [post, "foo"] }', 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 "raises an error on a render call with the :layout option" do error = assert_raises NotImplementedError do render('json.array! @posts, partial: "post", as: :post, layout: "layout"', posts: POSTS) end assert_equal "The `:layout' option is not supported in collection rendering.", error.message end test "raises an error on a render call with the :spacer_template option" do error = assert_raises NotImplementedError do render('json.array! @posts, partial: "post", as: :post, spacer_template: "template"', posts: POSTS) end assert_equal "The `:spacer_template' option is not supported in collection rendering.", error.message end 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") 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 def view.combined_fragment_cache_key(key) [ key ] end def view.cache_fragment_name(key, *) key end def view.fragment_name_with_digest(key) key end view end end jbuilder-2.12.0/test/jbuilder_dependency_tracker_test.rb 0000644 0000041 0000041 00000004062 14621622754 023513 0 ustar www-data www-data require 'test_helper' require 'jbuilder/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 dependency' 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.12.0/test/scaffold_controller_generator_test.rb 0000644 0000041 0000041 00000007617 14621622754 024105 0 ustar www-data www-data require '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_url\(@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, status: :unprocessable_entity \}}, 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_url\(@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, status: :unprocessable_entity \}}, 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 if Rails::VERSION::MAJOR >= 6 test 'controller with namespace' do run_generator %w(Admin::Post --model-name=Post) assert_file 'app/controllers/admin/posts_controller.rb' do |content| assert_instance_method :create, content do |m| assert_match %r{format\.html \{ redirect_to admin_post_url\(@post\), notice: "Post was successfully created\." \}}, m end assert_instance_method :update, content do |m| assert_match %r{format\.html \{ redirect_to admin_post_url\(@post\), notice: "Post was successfully updated\." \}}, m end assert_instance_method :destroy, content do |m| assert_match %r{format\.html \{ redirect_to admin_posts_url, notice: "Post was successfully destroyed\." \}}, m end end end end test "don't 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 if Rails::VERSION::MAJOR >= 6 test 'handles virtual attributes' do run_generator %w(Message content:rich_text video:attachment photos:attachments) assert_file 'app/controllers/messages_controller.rb' do |content| assert_match %r{params\.require\(:message\)\.permit\(:content, :video, photos: \[\]\)}, content end end end end jbuilder-2.12.0/test/scaffold_api_controller_generator_test.rb 0000644 0000041 0000041 00000004644 14621622754 024733 0 ustar www-data www-data require '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 "don't 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 if Rails::VERSION::MAJOR >= 6 test 'handles virtual attributes' do run_generator ["Message", "content:rich_text", "video:attachment", "photos:attachments"] assert_file 'app/controllers/messages_controller.rb' do |content| assert_match %r{params\.require\(:message\)\.permit\(:content, :video, photos: \[\]\)}, content end end end end end jbuilder-2.12.0/test/jbuilder_test.rb 0000644 0000041 0000041 00000055705 14621622754 017614 0 ustar www-data www-data require '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 'support merge! method with Jbuilder instance' do obj = jbuild do |json| json.foo 'bar' end result = jbuild do |json| json.merge! obj end assert_equal 'bar', result['foo'] 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 children 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! can be changed in child elements' do result = jbuild do |json| json.key_format! camelize: :lower json.level_one do json.key_format! :upcase json.value 'two' end end assert_equal ['levelOne'], result.keys assert_equal ['VALUE'], result['levelOne'].keys end test 'key_format! can be changed in array!' do result = jbuild do |json| json.key_format! camelize: :lower json.level_one do json.array! [{value: 'two'}] do |object| json.key_format! :upcase json.value object[:value] end end end assert_equal ['levelOne'], result.keys assert_equal ['VALUE'], result['levelOne'][0].keys 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 'key_format! is not applied deeply by default' do names = { first_name: 'camel', last_name: 'case' } result = jbuild do |json| json.key_format! camelize: :lower json.set! :all_names, names end assert_equal %i[first_name last_name], result['allNames'].keys end test 'applying key_format! deeply can be enabled per scope' do names = { first_name: 'camel', last_name: 'case' } result = jbuild do |json| json.key_format! camelize: :lower json.scope do json.deep_format_keys! json.set! :all_names, names end json.set! :all_names, names end assert_equal %w[firstName lastName], result['scope']['allNames'].keys assert_equal %i[first_name last_name], result['allNames'].keys end test 'applying key_format! deeply can be disabled per scope' do names = { first_name: 'camel', last_name: 'case' } result = jbuild do |json| json.key_format! camelize: :lower json.deep_format_keys! json.set! :all_names, names json.scope do json.deep_format_keys! false json.set! :all_names, names end end assert_equal %w[firstName lastName], result['allNames'].keys assert_equal %i[first_name last_name], result['scope']['allNames'].keys end test 'applying key_format! deeply can be enabled globally' do names = { first_name: 'camel', last_name: 'case' } Jbuilder.deep_format_keys true result = jbuild do |json| json.key_format! camelize: :lower json.set! :all_names, names end assert_equal %w[firstName lastName], result['allNames'].keys Jbuilder.send(:class_variable_set, '@@deep_format_keys', false) end test 'deep key_format! with merge!' do hash = { camel_style: 'for JS' } result = jbuild do |json| json.key_format! camelize: :lower json.deep_format_keys! json.merge! hash end assert_equal ['camelStyle'], result.keys end test 'deep key_format! with merge! deep' do hash = { camel_style: { sub_attr: 'for JS' } } result = jbuild do |json| json.key_format! camelize: :lower json.deep_format_keys! json.merge! hash end assert_equal ['subAttr'], result['camelStyle'].keys end test 'deep key_format! with set! array of hashes' do names = [{ first_name: 'camel', last_name: 'case' }] result = jbuild do |json| json.key_format! camelize: :lower json.deep_format_keys! json.set! :names, names end assert_equal %w[firstName lastName], result['names'][0].keys end test 'deep key_format! with set! extracting hash from object' do comment = Struct.new(:author).new({ first_name: 'camel', last_name: 'case' }) result = jbuild do |json| json.key_format! camelize: :lower json.deep_format_keys! json.set! :comment, comment, :author end assert_equal %w[firstName lastName], result['comment']['author'].keys end test 'deep key_format! with array! of hashes' do names = [{ first_name: 'camel', last_name: 'case' }] result = jbuild do |json| json.key_format! camelize: :lower json.deep_format_keys! json.array! names end assert_equal %w[firstName lastName], result[0].keys end test 'deep key_format! with merge! array of hashes' do names = [{ first_name: 'camel', last_name: 'case' }] new_names = [{ first_name: 'snake', last_name: 'case' }] result = jbuild do |json| json.key_format! camelize: :lower json.deep_format_keys! json.array! names json.merge! new_names end assert_equal %w[firstName lastName], result[1].keys end test 'deep key_format! is applied to hash extracted from object' do comment = Struct.new(:author).new({ first_name: 'camel', last_name: 'case' }) result = jbuild do |json| json.key_format! camelize: :lower json.deep_format_keys! json.extract! comment, :author end assert_equal %w[firstName lastName], result['author'].keys end test 'deep key_format! is applied to hash extracted from hash' do comment = {author: { first_name: 'camel', last_name: 'case' }} result = jbuild do |json| json.key_format! camelize: :lower json.deep_format_keys! json.extract! comment, :author end assert_equal %w[firstName lastName], result['author'].keys end test 'deep key_format! is applied to hash extracted directly from array' do comments = [Struct.new(:author).new({ first_name: 'camel', last_name: 'case' })] result = jbuild do |json| json.key_format! camelize: :lower json.deep_format_keys! json.array! comments, :author end assert_equal %w[firstName lastName], result[0]['author'].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.12.0/gemfiles/ 0000755 0000041 0000041 00000000000 14621622754 015230 5 ustar www-data www-data jbuilder-2.12.0/gemfiles/rails_5_1.gemfile 0000644 0000041 0000041 00000000304 14621622754 020335 0 ustar www-data www-data # This file was generated by Appraisal source "https://rubygems.org" gem "rake" gem "mocha", require: false gem "appraisal" gem "rails", "~> 5.1.0" gem "loofah", "< 2.21.0" gemspec path: "../" jbuilder-2.12.0/gemfiles/rails_5_0.gemfile 0000644 0000041 0000041 00000000304 14621622754 020334 0 ustar www-data www-data # This file was generated by Appraisal source "https://rubygems.org" gem "rake" gem "mocha", require: false gem "appraisal" gem "rails", "~> 5.0.0" gem "loofah", "< 2.21.0" gemspec path: "../" jbuilder-2.12.0/gemfiles/rails_6_0.gemfile 0000644 0000041 0000041 00000000253 14621622754 020340 0 ustar www-data www-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.12.0/gemfiles/rails_7_1.gemfile 0000644 0000041 0000041 00000000253 14621622754 020342 0 ustar www-data www-data # This file was generated by Appraisal source "https://rubygems.org" gem "rake" gem "mocha", require: false gem "appraisal" gem "rails", "~> 7.1.0" gemspec path: "../" jbuilder-2.12.0/gemfiles/rails_7_0.gemfile 0000644 0000041 0000041 00000000253 14621622754 020341 0 ustar www-data www-data # This file was generated by Appraisal source "https://rubygems.org" gem "rake" gem "mocha", require: false gem "appraisal" gem "rails", "~> 7.0.0" gemspec path: "../" jbuilder-2.12.0/gemfiles/rails_6_1.gemfile 0000644 0000041 0000041 00000000253 14621622754 020341 0 ustar www-data www-data # This file was generated by Appraisal source "https://rubygems.org" gem "rake" gem "mocha", require: false gem "appraisal" gem "rails", "~> 6.1.0" gemspec path: "../" jbuilder-2.12.0/gemfiles/rails_5_2.gemfile 0000644 0000041 0000041 00000000304 14621622754 020336 0 ustar www-data www-data # This file was generated by Appraisal source "https://rubygems.org" gem "rake" gem "mocha", require: false gem "appraisal" gem "rails", "~> 5.2.0" gem "loofah", "< 2.21.0" gemspec path: "../" jbuilder-2.12.0/gemfiles/rails_head.gemfile 0000644 0000041 0000041 00000000306 14621622754 020654 0 ustar www-data www-data # This file was generated by Appraisal source "https://rubygems.org" gem "rake" gem "mocha", require: false gem "appraisal" gem "rails", github: "rails/rails", branch: "main" gemspec path: "../" jbuilder-2.12.0/Rakefile 0000644 0000041 0000041 00000000564 14621622754 015107 0 ustar www-data www-data require "bundler/setup" require "bundler/gem_tasks" require "rake/testtask" if !ENV["APPRAISAL_INITIALIZED"] && !ENV["CI"] 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.12.0/MIT-LICENSE 0000644 0000041 0000041 00000002074 14621622754 015074 0 ustar www-data www-data Copyright (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. jbuilder-2.12.0/Gemfile 0000644 0000041 0000041 00000000137 14621622754 014731 0 ustar www-data www-data source "https://rubygems.org" gemspec gem "rake" gem "mocha", require: false gem "appraisal" jbuilder-2.12.0/jbuilder.gemspec 0000644 0000041 0000041 00000002110 14621622754 016574 0 ustar www-data www-data Gem::Specification.new do |s| s.name = 'jbuilder' s.version = '2.12.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.add_dependency 'actionview', '>= 5.0.0' if RUBY_ENGINE == 'rbx' s.add_development_dependency('racc') s.add_development_dependency('json') s.add_development_dependency('rubysl') end s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- test/*`.split("\n") s.metadata = { "bug_tracker_uri" => "https://github.com/rails/jbuilder/issues", "changelog_uri" => "https://github.com/rails/jbuilder/releases/tag/v#{s.version}", "mailing_list_uri" => "https://discuss.rubyonrails.org/c/rubyonrails-talk", "source_code_uri" => "https://github.com/rails/jbuilder/tree/v#{s.version}", "rubygems_mfa_required" => "true", } end jbuilder-2.12.0/README.md 0000644 0000041 0000041 00000021765 14621622754 014727 0 ustar www-data www-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'