diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..96f29c19 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,25 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/ruby +{ + "name": "jbuilder", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "ghcr.io/rails/devcontainer/images/ruby:3.4.5", + "features": { + "ghcr.io/devcontainers/features/github-cli:1": {} + } + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "ruby --version", + + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index 0d5374dd..3bc499ad 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -24,14 +24,19 @@ jobs: gemfile: - "rails_7_0" - "rails_7_1" + - "rails_7_2" - "rails_8_0" - "rails_head" exclude: + - ruby: '3.0' + gemfile: rails_7_2 - ruby: '3.0' gemfile: rails_8_0 - ruby: '3.0' gemfile: rails_head + - ruby: '3.1' + gemfile: rails_7_2 - ruby: '3.1' gemfile: rails_8_0 - ruby: '3.1' diff --git a/Appraisals b/Appraisals index 3d1832d2..3caeb7ef 100644 --- a/Appraisals +++ b/Appraisals @@ -9,6 +9,10 @@ appraise "rails-7-1" do gem "rails", "~> 7.1.0" end +appraise "rails-7-2" do + gem "rails", "~> 7.2.0" +end + appraise "rails-8-0" do gem "rails", "~> 8.0.0" end diff --git a/bin/test b/bin/test index 45d4fe95..1bc5120f 100755 --- a/bin/test +++ b/bin/test @@ -1,4 +1,4 @@ -#!/bin/env bash +#!/usr/bin/env bash set -e bundle install diff --git a/gemfiles/rails_7_2.gemfile b/gemfiles/rails_7_2.gemfile new file mode 100644 index 00000000..8f5a412b --- /dev/null +++ b/gemfiles/rails_7_2.gemfile @@ -0,0 +1,10 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "rake" +gem "mocha", require: false +gem "appraisal" +gem "rails", "~> 7.2.0" + +gemspec path: "../" diff --git a/lib/jbuilder.rb b/lib/jbuilder.rb index 024e418c..1290839f 100644 --- a/lib/jbuilder.rb +++ b/lib/jbuilder.rb @@ -5,7 +5,6 @@ require 'jbuilder/blank' require 'jbuilder/key_formatter' require 'jbuilder/errors' -require 'jbuilder/version' require 'json' require 'active_support/core_ext/hash/deep_merge' @@ -29,8 +28,8 @@ def initialize( end # Yields a builder and automatically turns the result into a JSON string - def self.encode(*args, &block) - new(*args, &block).target! + def self.encode(...) + new(...).target! end BLANK = Blank.new diff --git a/lib/jbuilder/errors.rb b/lib/jbuilder/errors.rb index e2de671c..aba1f89b 100644 --- a/lib/jbuilder/errors.rb +++ b/lib/jbuilder/errors.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'jbuilder/jbuilder' +require 'jbuilder/version' class Jbuilder class NullError < ::NoMethodError diff --git a/lib/jbuilder/jbuilder.rb b/lib/jbuilder/jbuilder.rb index 1fc30257..ddb52733 100644 --- a/lib/jbuilder/jbuilder.rb +++ b/lib/jbuilder/jbuilder.rb @@ -1,3 +1,3 @@ # frozen_string_literal: true -Jbuilder = Class.new(BasicObject) +require 'jbuilder/version' diff --git a/lib/jbuilder/jbuilder_template.rb b/lib/jbuilder/jbuilder_template.rb index 96ee75ec..855dd678 100644 --- a/lib/jbuilder/jbuilder_template.rb +++ b/lib/jbuilder/jbuilder_template.rb @@ -55,7 +55,9 @@ def partial!(*args) if args.one? && _is_active_model?(args.first) _render_active_model_partial args.first else - _render_explicit_partial(*args) + options = args.extract_options!.dup + options[:partial] = args.first if args.present? + _render_partial_with_options options end end @@ -121,7 +123,9 @@ def array!(collection = [], *args) options = args.first if args.one? && _partial_options?(options) - partial! options.merge(collection: collection) + options = options.dup + options[:collection] = collection + _render_partial_with_options options else super end @@ -131,7 +135,7 @@ def set!(name, object = BLANK, *args) options = args.first if args.one? && _partial_options?(options) - _set_inline_partial name, object, options + _set_inline_partial name, object, options.dup else super end @@ -142,14 +146,14 @@ def set!(name, object = BLANK, *args) alias_method :method_missing, :set! def _render_partial_with_options(options) - options.reverse_merge! locals: options.except(:partial, :as, :collection, :cached) - options.reverse_merge! ::JbuilderTemplate.template_lookup_options + options[:locals] ||= options.except(:partial, :as, :collection, :cached) + options[:handlers] ||= ::JbuilderTemplate.template_lookup_options[:handlers] as = options[:as] if as && options.key?(:collection) collection = options.delete(:collection) || [] partial = options.delete(:partial) - options[:locals].merge!(json: self) + options[:locals][:json] = self collection = EnumerableCompat.new(collection) if collection.respond_to?(:count) && !collection.respond_to?(:size) if options.has_key?(:layout) @@ -175,7 +179,7 @@ def _render_partial_with_options(options) end def _render_partial(options) - options[:locals].merge! json: self + options[:locals][:json] = self @context.render options end @@ -231,34 +235,18 @@ 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 + _scope do + options[:collection] = object + _render_partial_with_options options + end 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 } + _scope do + options[:locals] = { options[:as] => object } + _render_partial_with_options options 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 + _set_value name, value end def _render_active_model_partial(object) diff --git a/lib/jbuilder/version.rb b/lib/jbuilder/version.rb index 3fe2e274..78151fd0 100644 --- a/lib/jbuilder/version.rb +++ b/lib/jbuilder/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -class Jbuilder - VERSION = "2.13.0" +class Jbuilder < BasicObject + VERSION = "2.14.1" end diff --git a/test/jbuilder_test.rb b/test/jbuilder_test.rb index eec22f17..c01fb1e8 100644 --- a/test/jbuilder_test.rb +++ b/test/jbuilder_test.rb @@ -936,4 +936,11 @@ class JbuilderTest < ActiveSupport::TestCase 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 + + test "encode forwards options to new" do + Jbuilder.encode(key_formatter: 1, ignore_nil: 2) do |json| + assert_equal 1, json.instance_eval{ @key_formatter } + assert_equal 2, json.instance_eval{ @ignore_nil } + end + end end diff --git a/test/scaffold_api_controller_generator_test.rb b/test/scaffold_api_controller_generator_test.rb index 8ab47082..546749b1 100644 --- a/test/scaffold_api_controller_generator_test.rb +++ b/test/scaffold_api_controller_generator_test.rb @@ -4,7 +4,7 @@ class ScaffoldApiControllerGeneratorTest < Rails::Generators::TestCase tests Rails::Generators::ScaffoldControllerGenerator - arguments %w(Post title body:text images:attachments --api) + arguments %w(Post title body:text images:attachments --api --skip-routes) destination File.expand_path('../tmp', __FILE__) setup :prepare_destination @@ -53,7 +53,7 @@ class ScaffoldApiControllerGeneratorTest < Rails::Generators::TestCase end test "don't use require and permit if there are no attributes" do - run_generator %w(Post --api) + run_generator %w(Post --api --skip-routes) assert_file 'app/controllers/posts_controller.rb' do |content| assert_match %r{def post_params}, content @@ -62,7 +62,7 @@ class ScaffoldApiControllerGeneratorTest < Rails::Generators::TestCase end test 'handles virtual attributes' do - run_generator ["Message", "content:rich_text", "video:attachment", "photos:attachments"] + run_generator %w(Message content:rich_text video:attachment photos:attachments --skip-routes) assert_file 'app/controllers/messages_controller.rb' do |content| if Rails::VERSION::MAJOR >= 8 diff --git a/test/scaffold_controller_generator_test.rb b/test/scaffold_controller_generator_test.rb index 795dec8e..048df2a3 100644 --- a/test/scaffold_controller_generator_test.rb +++ b/test/scaffold_controller_generator_test.rb @@ -4,7 +4,7 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase tests Rails::Generators::ScaffoldControllerGenerator - arguments %w(Post title body:text images:attachments) + arguments %w(Post title body:text images:attachments --skip-routes) destination File.expand_path('../tmp', __FILE__) setup :prepare_destination @@ -67,7 +67,7 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase end test 'controller with namespace' do - run_generator %w(Admin::Post --model-name=Post) + run_generator %w(Admin::Post --model-name=Post --skip-routes) 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\], notice: "Post was successfully created\." \}}, m @@ -84,7 +84,7 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase end test "don't use require and permit if there are no attributes" do - run_generator %w(Post) + run_generator %w(Post --skip-routes) assert_file 'app/controllers/posts_controller.rb' do |content| assert_match %r{def post_params}, content @@ -93,7 +93,7 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase end test 'handles virtual attributes' do - run_generator %w(Message content:rich_text video:attachment photos:attachments) + run_generator %w(Message content:rich_text video:attachment photos:attachments --skip-routes) assert_file 'app/controllers/messages_controller.rb' do |content| if Rails::VERSION::MAJOR >= 8 diff --git a/test/test_helper.rb b/test/test_helper.rb index d985da9c..0b4dfa64 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -19,7 +19,7 @@ ENV["RAILS_ENV"] ||= "test" class << Rails - def cache + redefine_method :cache do @cache ||= ActiveSupport::Cache::MemoryStore.new end end