From ee1dfbac17e9e93dcc5d669e25f215ad7160a051 Mon Sep 17 00:00:00 2001 From: Yoshitomo Yasuno Date: Mon, 24 Jul 2023 11:01:39 +0900 Subject: [PATCH 01/24] Revert "Scan code with mend" --- .github/workflows/mend.yml | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 .github/workflows/mend.yml diff --git a/.github/workflows/mend.yml b/.github/workflows/mend.yml deleted file mode 100644 index b05ec28..0000000 --- a/.github/workflows/mend.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: mend -permissions: - contents: read - id-token: write -on: - schedule: - - cron: '0 8 * * 1' - -jobs: - scan-code: - runs-on: ubuntu-20.04 - steps: - - name: Checkout mend action - uses: actions/checkout@v3 - with: - repository: aktsk/mend-action - path: ./.github/actions/mend-action - ssh-key: ${{ secrets.MEND_CHECKOUT_KEY }} - - name: Generate credential of GCP - uses: 'google-github-actions/auth@v1' - with: - workload_identity_provider: ${{ secrets.MEND_GCP_PROVIDER_ID }} - service_account: ${{ secrets.MEND_SERVICE_ACCOUNT }} - - name: Scan repo - uses: ./.github/actions/mend-action - with: - mend_api_key: ${{ secrets.MEND_API_KEY }} - mend_user_key: ${{ secrets.MEND_USER_KEY }} - slack_token: ${{ secrets.MEND_SLACK_TOKEN }} From f40d7ba42c313cc459658a6f9eed368b22bf2925 Mon Sep 17 00:00:00 2001 From: fumihumi Date: Sun, 4 May 2025 21:49:23 +0900 Subject: [PATCH 02/24] format readme --- README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/README.md b/README.md index 673fb55..a6c6c7f 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,17 @@ # SimpleJson + A simple & fast solution for Rails JSON rendering. ## Get started + In Gemfile + ```ruby gem 'simple_json' ``` In controller + ```ruby class ApplicationController < ActionController::Base include SimpleJson::SimpleJsonRenderable @@ -44,10 +48,13 @@ That's all! Have fun! ## Special thanks + This project is built on work of [jb](https://github.com/amatsuda/jb). ## Template Syntax + SimpleJson templates are simply lambda objects that return data(Hashes or Arrays) for json. + ```ruby -> { { @@ -55,7 +62,9 @@ SimpleJson templates are simply lambda objects that return data(Hashes or Arrays } } ``` + When no parameters specified, `-> {` and `}` can be omitted. + ```ruby { key: @value, @@ -83,6 +92,7 @@ Use `partial!` method to call another template in template. Note that path is al ``` Cache helpers of simple_json is similar to jbuilder. + ```ruby cache! key, options do data_to_cache @@ -90,6 +100,7 @@ end ``` Cache helpers uses `Rails.cache` to cache, so array keys, expirations are available. Make sure `perform_caching` is enabled. + ```ruby cache! [key1, key2], expires_in: 10.minutes do data_to_cache @@ -97,6 +108,7 @@ end ``` `cache_if!` is also available + ```ruby cache_if! boolean, key1, options do data_to_cache @@ -104,41 +116,51 @@ end ``` You can set key_prefix for caching like this + ```ruby SimpleJson.cache_key_prefix = "MY_PREFIX" ``` ## Configurations + Load all templates on boot. (For production) Templates loaded will not load again, so it is not recommended in development environment. + ```ruby # config/environments/production.rb SimpleJson.enable_template_cache ``` The default path for templates is `app/views`, you can change it by + ```ruby SimpleJson.template_paths.append("app/simple_jsons") # or SimpleJson.template_paths=["app/views", "app/simple_jsons"] ``` + Note that these paths should not be eager loaded cause using .rb as suffix. SimpleJson uses Oj as json serializer by default. Modules with `#encode` and `#decode` method can be used here. + ```ruby SimpleJson.json_module = ActiveSupport::JSON ``` ## The Generator + SimpleJson extends the default Rails scaffold generator and adds some simple_json templates. If you don't need them, please configure like so. + ```rb Rails.application.config.generators.simple_json false ``` ## Benchmarks + Here're the results of a benchmark (which you can find [here](https://github.com/aktsk/simple_json/blob/master/test/dummy_app/app/controllers/benchmarks_controller.rb) in this repo) rendering a collection to JSON. ### RAILS_ENV=development + ``` % ./bin/benchmark.sh @@ -189,7 +211,9 @@ Comparison: jb: 106.1 i/s - 2.56x (± 0.00) slower jbuilder: 13.0 i/s - 20.88x (± 0.00) slower ``` + ### RAILS_ENV=production + ``` % RAILS_ENV=production ./bin/benchmark.sh @@ -242,13 +266,16 @@ Comparison: ``` ## Migrating from Jbuilder + When migrating from Jbuilder, you can include `Migratable` in controller for migrating mode. + ``` include SimpleJson::SimpleJsonRenderable include SimpleJson::Migratable ``` In migrating mode + - Comparision will be performed for simple_json and ActionView render(Jbuilder) result. - simple_json partials not found will use Jbuilder partial as an alternative. From e29f1a90f19ed4b9a8d10b12cebf007adbb066b7 Mon Sep 17 00:00:00 2001 From: fumihumi Date: Sun, 4 May 2025 21:49:57 +0900 Subject: [PATCH 03/24] bump development dependency gems * update rails to 8.0 * Modify the settings in config/environments/test (the setting for action_dispatch.show_exceptions has changed in Rails 7.2). * Change debugger gem 'byebug' to 'debug' --- simple_json.gemspec | 5 +++-- test/dummy_app/config/environments/test.rb | 2 +- test/test_helper.rb | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/simple_json.gemspec b/simple_json.gemspec index 10fa57c..2cae41a 100644 --- a/simple_json.gemspec +++ b/simple_json.gemspec @@ -24,11 +24,12 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'action_args' spec.add_development_dependency 'bundler' - spec.add_development_dependency 'byebug' - spec.add_development_dependency 'rails' + spec.add_development_dependency 'debug' + spec.add_development_dependency 'rails', '~> 8.0' spec.add_development_dependency 'rake' spec.add_development_dependency 'selenium-webdriver' spec.add_development_dependency 'test-unit-rails' + spec.add_development_dependency 'mutex_m' spec.required_ruby_version = '>= 2.5.0' end diff --git a/test/dummy_app/config/environments/test.rb b/test/dummy_app/config/environments/test.rb index b1d2da7..d8da507 100644 --- a/test/dummy_app/config/environments/test.rb +++ b/test/dummy_app/config/environments/test.rb @@ -19,7 +19,7 @@ config.action_controller.perform_caching = false # Raise exceptions instead of rendering exception templates. - config.action_dispatch.show_exceptions = false + config.action_dispatch.show_exceptions = :none # Disable request forgery protection in test environment. config.action_controller.allow_forgery_protection = false diff --git a/test/test_helper.rb b/test/test_helper.rb index 3fea8a6..3fdfcbd 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -8,5 +8,5 @@ Bundler.require require 'action_args' -require 'byebug' +require 'debug' require 'test/unit/rails/test_help' From 8f25c1b265f236636a3836eeedce40dea1260345 Mon Sep 17 00:00:00 2001 From: fumihumi Date: Sun, 4 May 2025 22:17:11 +0900 Subject: [PATCH 04/24] Fix the broken bin/benchmark.sh --- .../app/controllers/benchmarks_controller.rb | 18 ++++++++++++++++-- ...son.simple_json.rb => index.simple_json.rb} | 0 test/dummy_app/bin/bench.rb | 7 +++++++ 3 files changed, 23 insertions(+), 2 deletions(-) rename test/dummy_app/app/views/benchmarks/{index_simple_json.simple_json.rb => index.simple_json.rb} (100%) diff --git a/test/dummy_app/app/controllers/benchmarks_controller.rb b/test/dummy_app/app/controllers/benchmarks_controller.rb index f7b0761..d5a2cca 100644 --- a/test/dummy_app/app/controllers/benchmarks_controller.rb +++ b/test/dummy_app/app/controllers/benchmarks_controller.rb @@ -8,14 +8,28 @@ def index(n = '100') jb = render_to_string 'index_jb' jbuilder = render_to_string 'index_jbuilder' - simple_json = render_to_string 'index_simple_json' + + SimpleJson.json_module = SimpleJson::Json::Oj + simple_json = render_to_string 'index' + + SimpleJson.json_module = ActiveSupport::JSON + simple_json_active_support_json = render_to_string 'index' + raise 'jb != jbuilder' unless jb == jbuilder raise 'simple_json != jbuilder' unless simple_json == jbuilder + raise 'simple_json_active_support_json != jbuilder' unless simple_json_active_support_json == jbuilder result = Benchmark.ips do |x| x.report('jb') { render_to_string 'index_jb' } x.report('jbuilder') { render_to_string 'index_jbuilder' } - x.report('simple_json') { render_to_string 'index_simple_json' } + x.report('simple_json(oj)') { + SimpleJson.json_module = SimpleJson::Json::Oj + render_to_string 'index' + } + x.report('simple_json(AS::json)') { + SimpleJson.json_module = ActiveSupport::JSON + render_to_string 'index' + } x.compare! end render plain: result.data.to_s diff --git a/test/dummy_app/app/views/benchmarks/index_simple_json.simple_json.rb b/test/dummy_app/app/views/benchmarks/index.simple_json.rb similarity index 100% rename from test/dummy_app/app/views/benchmarks/index_simple_json.simple_json.rb rename to test/dummy_app/app/views/benchmarks/index.simple_json.rb diff --git a/test/dummy_app/bin/bench.rb b/test/dummy_app/bin/bench.rb index f99419b..d795a88 100644 --- a/test/dummy_app/bin/bench.rb +++ b/test/dummy_app/bin/bench.rb @@ -2,6 +2,13 @@ require 'action_dispatch/testing/integration' +puts 'SimpleJson Benchmark' +puts "ruby: #{RUBY_VERSION}" +puts "rails: #{Rails.version}" +puts "json: #{JSON::VERSION}" +puts "oj: #{Oj::VERSION}" +puts "----------------------" + puts '* Rendering 10 partials via render_partial' ActionDispatch::Integration::Session.new(Rails.application).get '/benchmarks.json?n=10' From 213a821acde1c4fc2d4dacf1e9c68fa996ef3ab8 Mon Sep 17 00:00:00 2001 From: fumihumi Date: Sun, 4 May 2025 22:46:37 +0900 Subject: [PATCH 05/24] CI: fix matrix specs --- .github/workflows/main.yml | 44 +++---------------- gemfiles/rails_42.gemfile | 9 ---- gemfiles/rails_50.gemfile | 8 ---- gemfiles/rails_52.gemfile | 8 ---- .../{rails_60.gemfile => rails_7.1.gemfile} | 2 +- .../{rails_61.gemfile => rails_7.2.gemfile} | 2 +- .../{rails_51.gemfile => rails_8.0.gemfile} | 3 +- simple_json.gemspec | 2 +- 8 files changed, 10 insertions(+), 68 deletions(-) delete mode 100644 gemfiles/rails_42.gemfile delete mode 100644 gemfiles/rails_50.gemfile delete mode 100644 gemfiles/rails_52.gemfile rename gemfiles/{rails_60.gemfile => rails_7.1.gemfile} (83%) rename gemfiles/{rails_61.gemfile => rails_7.2.gemfile} (83%) rename gemfiles/{rails_51.gemfile => rails_8.0.gemfile} (66%) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4d6ef44..72500a0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,52 +6,18 @@ jobs: build: strategy: matrix: - ruby_version: ['3.0', '2.7', '2.6'] - - gemfile: - - gemfiles/rails_61.gemfile - - gemfiles/rails_60.gemfile - - include: - - ruby_version: ruby-head - gemfile: gemfiles/rails_edge.gemfile - allow_failures: 'true' - - ruby_version: '3.0' - gemfile: gemfiles/rails_edge.gemfile - allow_failures: 'true' - - - ruby_version: '2.7' - gemfile: gemfiles/rails_edge.gemfile - allow_failures: 'true' - - - ruby_version: '2.6' - gemfile: gemfiles/rails_52.gemfile - - ruby_version: '2.6' - gemfile: gemfiles/rails_51.gemfile - - ruby_version: '2.6' - gemfile: gemfiles/rails_50.gemfile - - ruby_version: '2.6' - gemfile: gemfiles/rails_42.gemfile - bundler_version: '1' - - - ruby_version: '2.5' - gemfile: gemfiles/rails_52.gemfile - - ruby_version: '2.5' - gemfile: gemfiles/rails_42.gemfile - bundler_version: '1' + ruby: ['3.2', '3.3', '3.4'] + rails: ["7.1", "7.2", "8.0"] runs-on: ubuntu-latest env: - BUNDLE_GEMFILE: ${{ matrix.gemfile }} + BUNDLE_GEMFILE: gemfiles/rails_${{ matrix.rails }}.gemfile steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: - ruby-version: ${{ matrix.ruby_version }} - bundler: ${{ matrix.bundler_version }} + ruby-version: ${{ matrix.ruby }} bundler-cache: true - continue-on-error: ${{ matrix.allow_failures == 'true' }} - run: bundle exec rake - continue-on-error: ${{ matrix.allow_failures == 'true' }} diff --git a/gemfiles/rails_42.gemfile b/gemfiles/rails_42.gemfile deleted file mode 100644 index 58018ef..0000000 --- a/gemfiles/rails_42.gemfile +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -source 'https://rubygems.org' - -gemspec path: '../' - -gem 'jbuilder' -gem 'nokogiri', RUBY_VERSION < '2.1' ? '~> 1.6.0' : '>= 1.7' -gem 'rails', '~> 4.2.0' diff --git a/gemfiles/rails_50.gemfile b/gemfiles/rails_50.gemfile deleted file mode 100644 index 5b3e5a7..0000000 --- a/gemfiles/rails_50.gemfile +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -source 'https://rubygems.org' - -gemspec path: '../' - -gem 'jbuilder' -gem 'rails', '~> 5.0.0' diff --git a/gemfiles/rails_52.gemfile b/gemfiles/rails_52.gemfile deleted file mode 100644 index 1b91b90..0000000 --- a/gemfiles/rails_52.gemfile +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -source 'https://rubygems.org' - -gemspec path: '../' - -gem 'jbuilder' -gem 'rails', '~> 5.2.0' diff --git a/gemfiles/rails_60.gemfile b/gemfiles/rails_7.1.gemfile similarity index 83% rename from gemfiles/rails_60.gemfile rename to gemfiles/rails_7.1.gemfile index 1975ecc..021c42f 100644 --- a/gemfiles/rails_60.gemfile +++ b/gemfiles/rails_7.1.gemfile @@ -5,5 +5,5 @@ source 'https://rubygems.org' gemspec path: '../' gem 'jbuilder' -gem 'rails', '~> 6.0.0' +gem 'rails', '~> 7.1.0' gem 'selenium-webdriver' diff --git a/gemfiles/rails_61.gemfile b/gemfiles/rails_7.2.gemfile similarity index 83% rename from gemfiles/rails_61.gemfile rename to gemfiles/rails_7.2.gemfile index 51bf378..d7339d9 100644 --- a/gemfiles/rails_61.gemfile +++ b/gemfiles/rails_7.2.gemfile @@ -5,5 +5,5 @@ source 'https://rubygems.org' gemspec path: '../' gem 'jbuilder' -gem 'rails', '~> 6.1.0' +gem 'rails', '~> 7.2.0' gem 'selenium-webdriver' diff --git a/gemfiles/rails_51.gemfile b/gemfiles/rails_8.0.gemfile similarity index 66% rename from gemfiles/rails_51.gemfile rename to gemfiles/rails_8.0.gemfile index b4ed4c5..718c4af 100644 --- a/gemfiles/rails_51.gemfile +++ b/gemfiles/rails_8.0.gemfile @@ -5,4 +5,5 @@ source 'https://rubygems.org' gemspec path: '../' gem 'jbuilder' -gem 'rails', '~> 5.1.0' +gem 'rails', '~> 8.0.0' +gem 'selenium-webdriver' diff --git a/simple_json.gemspec b/simple_json.gemspec index 2cae41a..c496ef4 100644 --- a/simple_json.gemspec +++ b/simple_json.gemspec @@ -31,5 +31,5 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'test-unit-rails' spec.add_development_dependency 'mutex_m' - spec.required_ruby_version = '>= 2.5.0' + spec.required_ruby_version = '>= 3.2.0' end From 714e5d48dccfb5195019ee4f0ceb1059527e3504 Mon Sep 17 00:00:00 2001 From: fumihumi Date: Wed, 7 May 2025 10:47:23 +0900 Subject: [PATCH 06/24] Change default_json_module: 'oj' to 'json' --- README.md | 23 ++++++++++++++++--- lib/simple_json.rb | 3 +-- simple_json.gemspec | 2 +- .../config/initializers}/simple_json/json.rb | 0 4 files changed, 22 insertions(+), 6 deletions(-) rename {lib => test/dummy_app/config/initializers}/simple_json/json.rb (100%) diff --git a/README.md b/README.md index a6c6c7f..c3c0a53 100644 --- a/README.md +++ b/README.md @@ -141,10 +141,27 @@ SimpleJson.template_paths=["app/views", "app/simple_jsons"] Note that these paths should not be eager loaded cause using .rb as suffix. -SimpleJson uses Oj as json serializer by default. Modules with `#encode` and `#decode` method can be used here. +SimpleJson uses ActiveSupport::JSON as the default JSON serializer. If you want to change to a module that has `#encode` and `#decode` methods, such as the Oj gem, you can do so as follows. ```ruby -SimpleJson.json_module = ActiveSupport::JSON + +# define Custom Json class +# ex. config/initializers/simple_json/json.rb +module SimpleJson + module Json + class Oj + def self.encode(json) + ::Oj.dump(json, mode: :rails) + end + + def self.decode(json_string) + ::Oj.load(json_string, mode: :rails) + end + end + end +end + +SimpleJson.json_module = SimpleJson::Json::Oj ``` ## The Generator @@ -283,7 +300,7 @@ Note that render will be performed twice, so using it in production mode is not ## Contributing -Pull requests are welcome on GitHub at https://github.com/aktsk/simple_json. +Pull requests are welcome on GitHub at . ## License diff --git a/lib/simple_json.rb b/lib/simple_json.rb index f95ddbd..5f5894e 100644 --- a/lib/simple_json.rb +++ b/lib/simple_json.rb @@ -8,14 +8,13 @@ require 'simple_json/migratable' require 'simple_json/simple_json_renderer_for_migration' -require 'simple_json/json' module SimpleJson @config = { cache_enabled: false, template_paths: ['app/views'], cache_key_prefix: 'simple_json/views', - default_json_module: Json::Oj + default_json_module: ActiveSupport::JSON } class << self diff --git a/simple_json.gemspec b/simple_json.gemspec index c496ef4..3ca5f23 100644 --- a/simple_json.gemspec +++ b/simple_json.gemspec @@ -20,7 +20,7 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ['lib'] - spec.add_dependency 'oj', '~> 3.13' + spec.add_development_dependency 'oj' spec.add_development_dependency 'action_args' spec.add_development_dependency 'bundler' diff --git a/lib/simple_json/json.rb b/test/dummy_app/config/initializers/simple_json/json.rb similarity index 100% rename from lib/simple_json/json.rb rename to test/dummy_app/config/initializers/simple_json/json.rb From c4bab02f9055d5e1d92a0e4f58eece6a6dfe9404 Mon Sep 17 00:00:00 2001 From: kiyoto-akazawa Date: Wed, 10 Sep 2025 15:06:11 +0900 Subject: [PATCH 07/24] test: Add test cases for different action and template names --- test/dummy_app/app/controllers/api/posts_controller.rb | 6 ++++++ test/dummy_app/config/routes.rb | 1 + test/features/action_controller_api_test.rb | 8 ++++++++ 3 files changed, 15 insertions(+) diff --git a/test/dummy_app/app/controllers/api/posts_controller.rb b/test/dummy_app/app/controllers/api/posts_controller.rb index 3722553..a0f0a9b 100644 --- a/test/dummy_app/app/controllers/api/posts_controller.rb +++ b/test/dummy_app/app/controllers/api/posts_controller.rb @@ -8,6 +8,12 @@ class PostsController < ActionController::API def show(id) @post = Post.find id end + + def renamed_show(id) + @post = Post.find id + + render :show + end end end end diff --git a/test/dummy_app/config/routes.rb b/test/dummy_app/config/routes.rb index f95f929..87c4299 100644 --- a/test/dummy_app/config/routes.rb +++ b/test/dummy_app/config/routes.rb @@ -20,6 +20,7 @@ if Rails::VERSION::MAJOR >= 5 namespace :api do resources :posts, only: :show + get '/renamed_posts/:id', to: 'posts#renamed_show' end end # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html diff --git a/test/features/action_controller_api_test.rb b/test/features/action_controller_api_test.rb index 76871f0..cb72c13 100644 --- a/test/features/action_controller_api_test.rb +++ b/test/features/action_controller_api_test.rb @@ -12,6 +12,14 @@ class ActionControllerAPITest < ActionDispatch::IntegrationTest assert_equal json, { 'title' => 'post 1' } end + + test 'The renamed template correctly renders a JSON' do + get '/api/renamed_posts/1.json' + + json = response.parsed_body + + assert_equal json, { 'title' => 'post 1' } + end end end From bcdbbc7909f71db7c10bf013fd80a770d49a5a6c Mon Sep 17 00:00:00 2001 From: kiyoto-akazawa Date: Wed, 10 Sep 2025 16:07:51 +0900 Subject: [PATCH 08/24] fix: Fix MissingTemplate error after Rails 8 upgrade --- lib/simple_json/simple_json_renderable.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/simple_json/simple_json_renderable.rb b/lib/simple_json/simple_json_renderable.rb index eaff277..c583e63 100644 --- a/lib/simple_json/simple_json_renderable.rb +++ b/lib/simple_json/simple_json_renderable.rb @@ -29,7 +29,7 @@ def render_to_body(options) # use super when any of [:body, :plain, :html] exist in options return super if self.class::RENDER_FORMATS_IN_PRIORITY.any? { |key| options.key? key } - template_name = template_name(options[:template] || action_name) + template_name = template_name(options[:template] || options[:action] || action_name) if options.key?(:json) process_options(options) @rendered_format = 'application/json; charset=utf-8' From 2ecc631db2636ae6bbdf0da7041b40291e21d143 Mon Sep 17 00:00:00 2001 From: kiyoto-akazawa Date: Wed, 10 Sep 2025 17:47:45 +0900 Subject: [PATCH 09/24] Bump simple_json to 0.1.0 --- lib/simple_json/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/simple_json/version.rb b/lib/simple_json/version.rb index d1d9ec1..285ef66 100644 --- a/lib/simple_json/version.rb +++ b/lib/simple_json/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module SimpleJson - VERSION = '0.0.0' + VERSION = '0.1.0' end From 690700d6b32f3ca4986b9b6931583c7411251aea Mon Sep 17 00:00:00 2001 From: "jingyuan.zhao" Date: Mon, 26 Jan 2026 19:28:18 +0900 Subject: [PATCH 10/24] Define template lambdas into methods in Renderer. --- lib/simple_json/simple_json_renderer.rb | 30 ++++++++++++++++++------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/lib/simple_json/simple_json_renderer.rb b/lib/simple_json/simple_json_renderer.rb index ba4e1a3..b8d76f4 100644 --- a/lib/simple_json/simple_json_renderer.rb +++ b/lib/simple_json/simple_json_renderer.rb @@ -20,7 +20,7 @@ def load_all_templates! template_files = Rails.root.glob("#{path}/**/*.simple_json.rb") template_files.each do |file_path| template_path = file_path.relative_path_from(Rails.root.join(path)).to_path.delete_suffix('.simple_json.rb') - @renderers[template_path] = SimpleJsonTemplate.new(file_path.to_path).renderer + define_template_method(template_path, file_path.to_path) end end @templates_loaded = true @@ -29,7 +29,7 @@ def load_all_templates! def load_template(template_path) if SimpleJson.template_cache_enabled? load_all_templates! unless templates_loaded? - renderers[template_path] + render_methods[template_path] else load_template_from_file(template_path) end @@ -38,18 +38,31 @@ def load_template(template_path) def load_template_from_file(template_path) SimpleJson.template_paths.each do |path| file_path = Rails.root.join("#{path}/#{template_path}.simple_json.rb").to_path - return SimpleJsonTemplate.new(file_path).renderer if File.exist?(file_path) + + if File.exist?(file_path) + return define_template_method(template_path, file_path) + end end nil end - def renderers - @renderers ||= {} + def define_template_method(template_path, file_path) + @template_num ||= 0 + @template_num += 1 + method_name = :"template_#{@template_num}" + render_methods[template_path] = method_name + define_method(method_name, &SimpleJsonTemplate.new(file_path).renderer) + + return method_name + end + + def render_methods + @render_methods ||= {} end def clear_renderers - @renderers = {} + @render_methods = {} @templates_loaded = false end end @@ -70,10 +83,11 @@ def renderers end def render(template_name, **params) + method_name = renderer(template_name) if !params.empty? - instance_exec(**params, &renderer(template_name)) + send(method_name, **params) else - instance_exec(&renderer(template_name)) + send(method_name) end end From cea163c966aaabf785d7053beccf3fe1eb223a3c Mon Sep 17 00:00:00 2001 From: "jingyuan.zhao" Date: Mon, 26 Jan 2026 19:34:59 +0900 Subject: [PATCH 11/24] Rename Template.renderer to lambda --- lib/simple_json/simple_json_renderer.rb | 2 +- lib/simple_json/simple_json_template.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/simple_json/simple_json_renderer.rb b/lib/simple_json/simple_json_renderer.rb index b8d76f4..1b098d1 100644 --- a/lib/simple_json/simple_json_renderer.rb +++ b/lib/simple_json/simple_json_renderer.rb @@ -52,7 +52,7 @@ def define_template_method(template_path, file_path) @template_num += 1 method_name = :"template_#{@template_num}" render_methods[template_path] = method_name - define_method(method_name, &SimpleJsonTemplate.new(file_path).renderer) + define_method(method_name, &SimpleJsonTemplate.new(file_path).lambda) return method_name end diff --git a/lib/simple_json/simple_json_template.rb b/lib/simple_json/simple_json_template.rb index deab7b3..e64a9e5 100644 --- a/lib/simple_json/simple_json_template.rb +++ b/lib/simple_json/simple_json_template.rb @@ -7,8 +7,8 @@ def initialize(path) @source = File.read(path) end - def renderer - @renderer ||= eval(code, TOPLEVEL_BINDING, @path) # rubocop:disable Security/Eval + def lambda + @lambda ||= eval(code, TOPLEVEL_BINDING, @path) # rubocop:disable Security/Eval end def code From c20eb4bf74a279367917ead47f9c43f85d1ccb2d Mon Sep 17 00:00:00 2001 From: "jingyuan.zhao" Date: Tue, 27 Jan 2026 21:04:44 +0900 Subject: [PATCH 12/24] Fix typo: cache_enabled -> template_cache_enabled --- lib/simple_json.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/simple_json.rb b/lib/simple_json.rb index 5f5894e..17f54e5 100644 --- a/lib/simple_json.rb +++ b/lib/simple_json.rb @@ -11,7 +11,7 @@ module SimpleJson @config = { - cache_enabled: false, + template_cache_enabled: false, template_paths: ['app/views'], cache_key_prefix: 'simple_json/views', default_json_module: ActiveSupport::JSON From fb2b9940ca5dbf53184c3d9092fb960a8d560b42 Mon Sep 17 00:00:00 2001 From: "jingyuan.zhao" Date: Tue, 27 Jan 2026 21:13:07 +0900 Subject: [PATCH 13/24] Cache render context to avoid ruby cache invalidation --- lib/simple_json/simple_json_renderable.rb | 12 +++++++++--- lib/simple_json/simple_json_renderer.rb | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/simple_json/simple_json_renderable.rb b/lib/simple_json/simple_json_renderable.rb index c583e63..91469b6 100644 --- a/lib/simple_json/simple_json_renderable.rb +++ b/lib/simple_json/simple_json_renderable.rb @@ -54,9 +54,7 @@ def rendered_format end def simple_renderer - @simple_renderer ||= SimpleJsonRenderer.new(self).tap do |r| - r.extend(_helpers) if respond_to?(:_helpers) - end + @simple_renderer ||= self.class.simple_json_renderer_class.new(self) end def render_json_template(template_name, **_options) @@ -68,6 +66,14 @@ def render_json_template(template_name, **_options) if SimpleJson.template_cache_enabled? && !SimpleJsonRenderer.templates_loaded? SimpleJsonRenderer.load_all_templates! end + + def self.simple_json_renderer_class + @simple_json_renderer_class ||= begin + klass = Class.new(SimpleJsonRenderer) + klass.include(_helpers) if method_defined?(:_helpers) + klass + end + end end def template_path diff --git a/lib/simple_json/simple_json_renderer.rb b/lib/simple_json/simple_json_renderer.rb index ba4e1a3..0cff63f 100644 --- a/lib/simple_json/simple_json_renderer.rb +++ b/lib/simple_json/simple_json_renderer.rb @@ -60,7 +60,7 @@ def initialize(controller) end def renderer(template_path) - renderers[template_path] || self.class.load_template(template_path).tap do |renderer| + renderers[template_path] || SimpleJson::SimpleJsonRenderer.load_template(template_path).tap do |renderer| renderers[template_path] = renderer end end From 334260ef62f0b52db717f724e723b2df4b5317b1 Mon Sep 17 00:00:00 2001 From: "jingyuan.zhao" Date: Tue, 27 Jan 2026 11:47:00 +0900 Subject: [PATCH 14/24] Define method using class_eval instead of define_method. --- lib/simple_json/simple_json_renderer.rb | 8 ++---- lib/simple_json/simple_json_template.rb | 33 ++++++++++++++++++++++--- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/lib/simple_json/simple_json_renderer.rb b/lib/simple_json/simple_json_renderer.rb index 1b098d1..9f1c35c 100644 --- a/lib/simple_json/simple_json_renderer.rb +++ b/lib/simple_json/simple_json_renderer.rb @@ -39,9 +39,7 @@ def load_template_from_file(template_path) SimpleJson.template_paths.each do |path| file_path = Rails.root.join("#{path}/#{template_path}.simple_json.rb").to_path - if File.exist?(file_path) - return define_template_method(template_path, file_path) - end + return define_template_method(template_path, file_path) if File.exist?(file_path) end nil @@ -52,9 +50,7 @@ def define_template_method(template_path, file_path) @template_num += 1 method_name = :"template_#{@template_num}" render_methods[template_path] = method_name - define_method(method_name, &SimpleJsonTemplate.new(file_path).lambda) - - return method_name + SimpleJsonTemplate.new(file_path).define_to_class(self, method_name) end def render_methods diff --git a/lib/simple_json/simple_json_template.rb b/lib/simple_json/simple_json_template.rb index e64a9e5..37d3e2d 100644 --- a/lib/simple_json/simple_json_template.rb +++ b/lib/simple_json/simple_json_template.rb @@ -7,20 +7,45 @@ def initialize(path) @source = File.read(path) end - def lambda - @lambda ||= eval(code, TOPLEVEL_BINDING, @path) # rubocop:disable Security/Eval + def define_to_class(klass, method_name) + method_string = to_method_string(method_name) + klass.class_eval(method_string, @path) + + method_name end + private + def code @code ||= lambda_stringify(@source) end - private - def lambda_stringify(source) return source if source.match?(/^(?:\s*(?:#.*?)?\n)*\s*->/) "-> { #{source} }" end + + def method_string_from_lambda(source, method_name) + pattern = %r{ + \A(?(?:\s*\#.*\n|\s+)*) + ->\s* + (?:\((?.*?)\)|(?[^\{ ]*?)) + \s*(?:\{(?.*)\}|do(?.*)end) + (?(?:\s*\#.*|\s+)*)\z + }mx + + match = source.match(pattern) + raise :parse_error unless match + + params = (match[:params_p] || match[:params_n] || "").strip + arg_part = params.empty? ? "" : "(#{params})" + + "#{match[:prefix]}def #{method_name}#{arg_part};#{match[:body]};end#{match[:suffix]}" + end + + def to_method_string(method_name) + method_string_from_lambda(code, method_name) + end end end From 1c3bb9fdda8c342a4bf97e16fcf168f7b31e2b21 Mon Sep 17 00:00:00 2001 From: "jingyuan.zhao" Date: Tue, 27 Jan 2026 21:00:59 +0900 Subject: [PATCH 15/24] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c3c0a53..ec5a6dc 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ This project is built on work of [jb](https://github.com/amatsuda/jb). ## Template Syntax -SimpleJson templates are simply lambda objects that return data(Hashes or Arrays) for json. +SimpleJson templates should be written in Ruby lambda format. The template code is converted into a method and then invoked to produce data (Hashes or Arrays) for JSON. ```ruby -> { From 917e03d71ceaa498d850d0d4d7362d126547f067 Mon Sep 17 00:00:00 2001 From: "jingyuan.zhao" Date: Wed, 28 Jan 2026 12:44:57 +0900 Subject: [PATCH 16/24] Reuse methods when template cache disabled --- lib/simple_json/simple_json_renderer.rb | 19 ++++++++++++++----- lib/simple_json/simple_json_template.rb | 4 ++-- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/lib/simple_json/simple_json_renderer.rb b/lib/simple_json/simple_json_renderer.rb index 9f1c35c..5961765 100644 --- a/lib/simple_json/simple_json_renderer.rb +++ b/lib/simple_json/simple_json_renderer.rb @@ -46,17 +46,26 @@ def load_template_from_file(template_path) end def define_template_method(template_path, file_path) - @template_num ||= 0 - @template_num += 1 - method_name = :"template_#{@template_num}" - render_methods[template_path] = method_name - SimpleJsonTemplate.new(file_path).define_to_class(self, method_name) + template = SimpleJsonTemplate.new(file_path) + code_hash = template.code.hash + + render_methods[template_path] = render_methods_cache.fetch(code_hash) do + @template_num ||= 0 + @template_num += 1 + method_name = :"template_#{@template_num}" + template.define_to_class(self, method_name) + render_methods_cache[code_hash] = method_name + end end def render_methods @render_methods ||= {} end + def render_methods_cache + @render_methods_cache ||= {} + end + def clear_renderers @render_methods = {} @templates_loaded = false diff --git a/lib/simple_json/simple_json_template.rb b/lib/simple_json/simple_json_template.rb index 37d3e2d..a375e32 100644 --- a/lib/simple_json/simple_json_template.rb +++ b/lib/simple_json/simple_json_template.rb @@ -14,12 +14,12 @@ def define_to_class(klass, method_name) method_name end - private - def code @code ||= lambda_stringify(@source) end + private + def lambda_stringify(source) return source if source.match?(/^(?:\s*(?:#.*?)?\n)*\s*->/) From 9f6b48c63e7c2d85e7eb05af1e3c8772a2f424b7 Mon Sep 17 00:00:00 2001 From: "jingyuan.zhao" Date: Wed, 28 Jan 2026 19:39:48 +0900 Subject: [PATCH 17/24] Add test for lambda-to-method conversion. --- test/unit/simple_json_template_test.rb | 251 +++++++++++++++++++++++++ 1 file changed, 251 insertions(+) create mode 100644 test/unit/simple_json_template_test.rb diff --git a/test/unit/simple_json_template_test.rb b/test/unit/simple_json_template_test.rb new file mode 100644 index 0000000..6d6b837 --- /dev/null +++ b/test/unit/simple_json_template_test.rb @@ -0,0 +1,251 @@ +# frozen_string_literal: true + +require 'test_helper' + +class SimpleJsonTemplateTest < Test::Unit::TestCase + # -> { body } form + test 'converts simple lambda with braces' do + source = '-> { "hello" }' + result = call_method(source, :foo) + + assert_equal 'def foo; "hello" ;end', result + end + + # ->(params) { body } form (parenthesized parameters) + test 'converts lambda with parenthesized parameters' do + source = '->(a, b) { a + b }' + result = call_method(source, :add) + + assert_equal 'def add(a, b); a + b ;end', result + end + + # ->params { body } form (no parentheses, no space) + test 'converts lambda with parameter without parentheses' do + source = '->a { a * 2 }' + result = call_method(source, :double) + + assert_equal 'def double(a); a * 2 ;end', result + end + + # -> do body end form + test 'converts lambda with do-end' do + source = '-> do "hello" end' + result = call_method(source, :foo) + + assert_equal 'def foo; "hello" ;end', result + end + + # ->(params) do body end form + test 'converts lambda with parameters and do-end' do + source = '->(x) do x * 2 end' + result = call_method(source, :double) + + assert_equal 'def double(x); x * 2 ;end', result + end + + # Multiline body + test 'converts multiline lambda body' do + source = <<~RUBY + -> { + x = 1 + y = 2 + x + y + } + RUBY + result = call_method(source, :calc) + + assert_match(/def calc;/, result) + assert_match(/x = 1/, result) + assert_match(/y = 2/, result) + assert_match(/x \+ y/, result) + assert_match(/;end/, result) + end + + # Preserve prefix comments + test 'preserves prefix comments' do + source = <<~RUBY + # This is a comment + -> { "hello" } + RUBY + result = call_method(source, :foo) + + assert_match(/\A# This is a comment\n/, result) + assert_match(/def foo;/, result) + end + + # Preserve suffix comments + test 'preserves suffix comments' do + source = <<~RUBY + -> { "hello" } + # trailing comment + RUBY + result = call_method(source, :foo) + + assert_match(/;end\n# trailing comment/, result) + end + + # Empty parameter list + test 'converts lambda with empty parentheses' do + source = '->() { "hello" }' + result = call_method(source, :foo) + + assert_equal 'def foo; "hello" ;end', result + end + + # Space before brace with no parameters + test 'converts lambda with space before brace' do + source = '-> { "hello" }' + result = call_method(source, :foo) + + assert_equal 'def foo; "hello" ;end', result + end + + # Multiline do-end form + test 'converts multiline do-end lambda' do + source = <<~RUBY + -> do + result = [] + result << 1 + result + end + RUBY + result = call_method(source, :build) + + assert_match(/def build;/, result) + assert_match(/result = \[\]/, result) + assert_match(/;end/, result) + end + + # --- Parameter types --- + + # Default argument + test 'converts lambda with default parameter' do + source = '->(a, b = 1) { a + b }' + result = call_method(source, :add) + + assert_equal 'def add(a, b = 1); a + b ;end', result + end + + # Keyword arguments + test 'converts lambda with keyword parameters' do + source = '->(a:, b: 1) { a + b }' + result = call_method(source, :add) + + assert_equal 'def add(a:, b: 1); a + b ;end', result + end + + # Variadic arguments + test 'converts lambda with splat parameter' do + source = '->(*args) { args }' + result = call_method(source, :collect) + + assert_equal 'def collect(*args); args ;end', result + end + + # Block parameter + test 'converts lambda with block parameter' do + source = '->(&block) { block.call }' + result = call_method(source, :execute) + + assert_equal 'def execute(&block); block.call ;end', result + end + + # Double splat + test 'converts lambda with double splat parameter' do + source = '->(**opts) { opts }' + result = call_method(source, :options) + + assert_equal 'def options(**opts); opts ;end', result + end + + # --- Body edge cases --- + + # Nested braces + test 'converts lambda with nested braces in body' do + source = '-> { { key: value } }' + result = call_method(source, :hash) + + assert_equal 'def hash; { key: value } ;end', result + end + + # Empty body (braces) + test 'converts lambda with empty body' do + source = '-> { }' + result = call_method(source, :noop) + + assert_equal 'def noop; ;end', result + end + + # Empty body (do-end) + test 'converts do-end lambda with empty body' do + source = '-> do end' + result = call_method(source, :noop) + + assert_equal 'def noop; ;end', result + end + + # do-end body contains end keyword + test 'converts do-end lambda with nested end keyword' do + source = '-> do if x then y end end' + result = call_method(source, :conditional) + + assert_equal 'def conditional; if x then y end ;end', result + end + + # Body string contains end + test 'converts lambda with end string in body' do + source = '-> { "end of string" }' + result = call_method(source, :message) + + assert_equal 'def message; "end of string" ;end', result + end + + # --- Prefix edge cases --- + + # Multiple prefix comments + test 'preserves multiple prefix comments' do + source = <<~RUBY + # comment 1 + # comment 2 + -> { 1 } + RUBY + result = call_method(source, :foo) + + assert_match(/\A# comment 1\n# comment 2\n/, result) + assert_match(/def foo;/, result) + end + + # Blank line prefix + test 'preserves blank line prefix' do + source = "\n\n-> { 1 }" + result = call_method(source, :foo) + + assert_match(/\A\n\ndef foo;/, result) + end + + # --- Error cases --- + + # Non-lambda input + test 'raises error for non-lambda input' do + source = 'def foo; end' + + assert_raise(TypeError) do + call_method(source, :foo) + end + end + + # Proc.new input + test 'raises error for Proc.new input' do + source = 'Proc.new { 1 }' + + assert_raise(TypeError) do + call_method(source, :foo) + end + end + + private + + def call_method(source, method_name) + SimpleJson::SimpleJsonTemplate.allocate.send(:method_string_from_lambda, source, method_name) + end +end From 0625690f4a5faa80287bc0bb1000114b5b560b21 Mon Sep 17 00:00:00 2001 From: "jingyuan.zhao" Date: Wed, 28 Jan 2026 13:25:13 +0900 Subject: [PATCH 18/24] Add /vendor/bundle to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index dd48f47..3bdbd4e 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ gemfiles/*.lock /spec/reports/ /tmp/ /test/dummy_app/log/* +/vendor/bundle From 943e3e28c680011934e6e8c4f9afa461a6458855 Mon Sep 17 00:00:00 2001 From: "jingyuan.zhao" Date: Wed, 28 Jan 2026 19:06:13 +0900 Subject: [PATCH 19/24] Created CHANGELOG.md --- CHANGELOG.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e6c9aa3 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,35 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on Keep a Changelog, and this project adheres to Semantic Versioning. + +## [Unreleased] + +## [1.0.0] - 2026-01-28 +### Breaking +- Templates are no longer executed by invoking lambdas directly; they are compiled into methods. + +### Changed +- Cache template methods by code hash to reduce Ruby method cache invalidation. +- Use a renderer subclass that includes helpers instead of extending instances. + +### Fixed +- Rename the config key `cache_enabled` to `template_cache_enabled`. +- Add `/vendor/bundle` to `.gitignore`. + +## [0.1.0] - 2025-09-10 +### Changed +- Default JSON serializer switched from Oj to ActiveSupport::JSON to better align with Rails defaults. +- Development dependencies refreshed and benchmark script fixed for the latest Ruby/Rails stacks. +- README formatting and examples improved for clarity. + +### Fixed +- Resolved `MissingTemplate` errors introduced by the Rails 8 upgrade. +- Added coverage for rendering when template/action names differ to avoid regressions. + +## [0.0.0] - 2021-11-02 +### Added +- Initial SimpleJson renderer, templates (`.simple_json.rb` lambdas), and `SimpleJson::SimpleJsonRenderable` integration. +- Template caching toggle and configurable template paths. +- Rails generator hooks and dummy app scaffolding for getting started. +- Migration helpers for comparing SimpleJson output against existing Jbuilder views. From a2f0e8cbe461ddf81c4e36d58e3f9fc7df9178e9 Mon Sep 17 00:00:00 2001 From: "jingyuan.zhao" Date: Wed, 29 Oct 2025 19:38:35 +0900 Subject: [PATCH 20/24] Updated benchmark program --- .../app/controllers/benchmarks_controller.rb | 47 +++++------ .../app/views/benchmarks/_comment_jb.json.jb | 54 ++++++++++++- .../_comment_jbuilder.json.jbuilder | 58 ++++++++++++++ .../_comment_simple_json.simple_json.rb | 53 ++++++++++++- .../app/views/benchmarks/index.simple_json.rb | 5 -- .../app/views/benchmarks/index_jb.json.jb | 3 - .../benchmarks/index_jbuilder.json.jbuilder | 5 -- .../dummy_app/app/views/benchmarks/jb.json.jb | 18 +++++ .../views/benchmarks/jbuilder.json.jbuilder | 19 +++++ .../simple_json_as_json.simple_json.rb | 20 +++++ .../benchmarks/simple_json_oj.simple_json.rb | 20 +++++ test/dummy_app/bin/bench.rb | 79 ++++++++++++++++--- test/dummy_app/config/routes.rb | 9 ++- 13 files changed, 336 insertions(+), 54 deletions(-) delete mode 100644 test/dummy_app/app/views/benchmarks/index.simple_json.rb delete mode 100644 test/dummy_app/app/views/benchmarks/index_jb.json.jb delete mode 100644 test/dummy_app/app/views/benchmarks/index_jbuilder.json.jbuilder create mode 100644 test/dummy_app/app/views/benchmarks/jb.json.jb create mode 100644 test/dummy_app/app/views/benchmarks/jbuilder.json.jbuilder create mode 100644 test/dummy_app/app/views/benchmarks/simple_json_as_json.simple_json.rb create mode 100644 test/dummy_app/app/views/benchmarks/simple_json_oj.simple_json.rb diff --git a/test/dummy_app/app/controllers/benchmarks_controller.rb b/test/dummy_app/app/controllers/benchmarks_controller.rb index d5a2cca..5646a10 100644 --- a/test/dummy_app/app/controllers/benchmarks_controller.rb +++ b/test/dummy_app/app/controllers/benchmarks_controller.rb @@ -1,37 +1,30 @@ # frozen_string_literal: true -require 'benchmark/ips' - class BenchmarksController < ApplicationController - def index(n = '100') - @comments = 1.upto(n.to_i).map { |i| Comment.new(i, nil, "comment #{i}") } + before_action :prepare_comments + + def self.comments + @comments ||= 1.upto(1000).map { |i| Comment.new(i, nil, "comment #{i}") } + end + + def jb + end - jb = render_to_string 'index_jb' - jbuilder = render_to_string 'index_jbuilder' + def jbuilder + end + def simple_json_oj SimpleJson.json_module = SimpleJson::Json::Oj - simple_json = render_to_string 'index' + end + def simple_json_as_json SimpleJson.json_module = ActiveSupport::JSON - simple_json_active_support_json = render_to_string 'index' - - raise 'jb != jbuilder' unless jb == jbuilder - raise 'simple_json != jbuilder' unless simple_json == jbuilder - raise 'simple_json_active_support_json != jbuilder' unless simple_json_active_support_json == jbuilder - - result = Benchmark.ips do |x| - x.report('jb') { render_to_string 'index_jb' } - x.report('jbuilder') { render_to_string 'index_jbuilder' } - x.report('simple_json(oj)') { - SimpleJson.json_module = SimpleJson::Json::Oj - render_to_string 'index' - } - x.report('simple_json(AS::json)') { - SimpleJson.json_module = ActiveSupport::JSON - render_to_string 'index' - } - x.compare! - end - render plain: result.data.to_s + end + + private + + def prepare_comments + n = params.fetch(:n, 100).to_i + @comments = self.class.comments.take(n) end end diff --git a/test/dummy_app/app/views/benchmarks/_comment_jb.json.jb b/test/dummy_app/app/views/benchmarks/_comment_jb.json.jb index 1a0202d..db578aa 100644 --- a/test/dummy_app/app/views/benchmarks/_comment_jb.json.jb +++ b/test/dummy_app/app/views/benchmarks/_comment_jb.json.jb @@ -1,3 +1,55 @@ # frozen_string_literal: true -{ body: comment.body } +length = comment.body.length +likes = comment.id * 3 +dislikes = comment.id % 4 +rating = (likes.to_f / (dislikes.zero? ? 1 : dislikes)).round(2) +position = comment_counter + 1 + +{ + id: comment.id, + body: comment.body, + metrics: { + length: length, + readability: (length / 5.0).round(2), + engagement: { + likes: likes, + dislikes: dislikes, + rating: rating + } + }, + author: { + name: "user-#{comment.id}", + active: comment.id.odd?, + badges: (comment.id % 3).zero? ? %w[insightful] : [], + contact: { + email: "user#{comment.id}@example.com", + url: "https://example.com/users/#{comment.id}" + }, + settings: { + theme: comment.id.even? ? 'dark' : 'light', + timezone: "UTC+#{(comment.id % 5) - 2}" + } + }, + tags: ['bench', "comment-#{comment.id}", (comment.id.even? ? 'even' : nil)].compact, + flags: { + pinned: position == 1, + hidden: (comment.id % 5).zero? + }, + metadata: { + position: position, + attachments: (comment.id % 4).zero? ? [{ filename: "attachment-#{comment.id}.txt", size: comment.id * 128 }] : [], + history: [ + { version: 1, updated_by: "user-#{comment.id}", updated_at: '2024-01-01T00:00:00Z' }, + { version: 2, updated_by: "user-#{comment.id}", updated_at: '2024-01-02T00:00:00Z' } + ] + }, + links: { + self: "/comments/#{comment.id}", + post: "/posts/#{comment.post&.id || 1}" + }, + extras: { + mood: %w[happy neutral excited][comment.id % 3], + score: comment.id * position + } +} diff --git a/test/dummy_app/app/views/benchmarks/_comment_jbuilder.json.jbuilder b/test/dummy_app/app/views/benchmarks/_comment_jbuilder.json.jbuilder index 46c16ec..0066f21 100644 --- a/test/dummy_app/app/views/benchmarks/_comment_jbuilder.json.jbuilder +++ b/test/dummy_app/app/views/benchmarks/_comment_jbuilder.json.jbuilder @@ -1,3 +1,61 @@ # frozen_string_literal: true +length = comment.body.length +likes = comment.id * 3 +dislikes = comment.id % 4 +rating = (likes.to_f / (dislikes.zero? ? 1 : dislikes)).round(2) +position = comment_counter + 1 + +json.id comment.id json.body comment.body + +json.metrics do + json.length length + json.readability (length / 5.0).round(2) + json.engagement do + json.likes likes + json.dislikes dislikes + json.rating rating + end +end + +json.author do + json.name "user-#{comment.id}" + json.active comment.id.odd? + json.badges ((comment.id % 3).zero? ? %w[insightful] : []) + json.contact do + json.email "user#{comment.id}@example.com" + json.url "https://example.com/users/#{comment.id}" + end + json.settings do + json.theme(comment.id.even? ? 'dark' : 'light') + json.timezone "UTC+#{(comment.id % 5) - 2}" + end +end + +json.tags ['bench', "comment-#{comment.id}", (comment.id.even? ? 'even' : nil)].compact + +json.flags do + json.pinned position == 1 + json.hidden (comment.id % 5).zero? +end + +json.metadata do + json.position position + attachments = (comment.id % 4).zero? ? [{ filename: "attachment-#{comment.id}.txt", size: comment.id * 128 }] : [] + json.attachments attachments + json.history [ + { version: 1, updated_by: "user-#{comment.id}", updated_at: '2024-01-01T00:00:00Z' }, + { version: 2, updated_by: "user-#{comment.id}", updated_at: '2024-01-02T00:00:00Z' } + ] +end + +json.links do + json.self "/comments/#{comment.id}" + json.post "/posts/#{comment.post&.id || 1}" +end + +json.extras do + json.mood %w[happy neutral excited][comment.id % 3] + json.score comment.id * position +end diff --git a/test/dummy_app/app/views/benchmarks/_comment_simple_json.simple_json.rb b/test/dummy_app/app/views/benchmarks/_comment_simple_json.simple_json.rb index 09565b8..0d249a7 100644 --- a/test/dummy_app/app/views/benchmarks/_comment_simple_json.simple_json.rb +++ b/test/dummy_app/app/views/benchmarks/_comment_simple_json.simple_json.rb @@ -1,7 +1,56 @@ # frozen_string_literal: true -->(comment:) { +->(comment:, position:) { + length = comment.body.length + likes = comment.id * 3 + dislikes = comment.id % 4 + rating = (likes.to_f / (dislikes.zero? ? 1 : dislikes)).round(2) + { - body: comment.body + id: comment.id, + body: comment.body, + metrics: { + length: length, + readability: (length / 5.0).round(2), + engagement: { + likes: likes, + dislikes: dislikes, + rating: rating + } + }, + author: { + name: "user-#{comment.id}", + active: comment.id.odd?, + badges: (comment.id % 3).zero? ? %w[insightful] : [], + contact: { + email: "user#{comment.id}@example.com", + url: "https://example.com/users/#{comment.id}" + }, + settings: { + theme: comment.id.even? ? 'dark' : 'light', + timezone: "UTC+#{(comment.id % 5) - 2}" + } + }, + tags: ['bench', "comment-#{comment.id}", (comment.id.even? ? 'even' : nil)].compact, + flags: { + pinned: position == 1, + hidden: (comment.id % 5).zero? + }, + metadata: { + position: position, + attachments: (comment.id % 4).zero? ? [{ filename: "attachment-#{comment.id}.txt", size: comment.id * 128 }] : [], + history: [ + { version: 1, updated_by: "user-#{comment.id}", updated_at: '2024-01-01T00:00:00Z' }, + { version: 2, updated_by: "user-#{comment.id}", updated_at: '2024-01-02T00:00:00Z' } + ] + }, + links: { + self: "/comments/#{comment.id}", + post: "/posts/#{comment.post&.id || 1}" + }, + extras: { + mood: %w[happy neutral excited][comment.id % 3], + score: comment.id * position + } } } diff --git a/test/dummy_app/app/views/benchmarks/index.simple_json.rb b/test/dummy_app/app/views/benchmarks/index.simple_json.rb deleted file mode 100644 index e37cf1e..0000000 --- a/test/dummy_app/app/views/benchmarks/index.simple_json.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -{ - comments: @comments.map { |comment| partial!('benchmarks/_comment_simple_json', comment: comment) } -} diff --git a/test/dummy_app/app/views/benchmarks/index_jb.json.jb b/test/dummy_app/app/views/benchmarks/index_jb.json.jb deleted file mode 100644 index 592d814..0000000 --- a/test/dummy_app/app/views/benchmarks/index_jb.json.jb +++ /dev/null @@ -1,3 +0,0 @@ -# frozen_string_literal: true - -{ comments: render(partial: 'comment_jb', collection: @comments, as: 'comment') } diff --git a/test/dummy_app/app/views/benchmarks/index_jbuilder.json.jbuilder b/test/dummy_app/app/views/benchmarks/index_jbuilder.json.jbuilder deleted file mode 100644 index 1dca7dc..0000000 --- a/test/dummy_app/app/views/benchmarks/index_jbuilder.json.jbuilder +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -json.comments do - json.partial! 'comment_jbuilder', collection: @comments, as: 'comment' -end diff --git a/test/dummy_app/app/views/benchmarks/jb.json.jb b/test/dummy_app/app/views/benchmarks/jb.json.jb new file mode 100644 index 0000000..eb71ef6 --- /dev/null +++ b/test/dummy_app/app/views/benchmarks/jb.json.jb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +lengths = @comments.map { |comment| comment.body.length } +avg_length = lengths.empty? ? 0.0 : (lengths.sum.to_f / lengths.size).round(2) + +{ + meta: { + total_comments: @comments.size, + longest_comment_length: lengths.max || 0, + average_comment_length: avg_length, + tags: %w[bench simple json], + flags: { + has_many: @comments.size > 500, + empty: @comments.empty? + } + }, + comments: render(partial: 'comment_jb', collection: @comments, as: 'comment') +} diff --git a/test/dummy_app/app/views/benchmarks/jbuilder.json.jbuilder b/test/dummy_app/app/views/benchmarks/jbuilder.json.jbuilder new file mode 100644 index 0000000..8a28de6 --- /dev/null +++ b/test/dummy_app/app/views/benchmarks/jbuilder.json.jbuilder @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +lengths = @comments.map { |comment| comment.body.length } +avg_length = lengths.empty? ? 0.0 : (lengths.sum.to_f / lengths.size).round(2) + +json.meta do + json.total_comments @comments.size + json.longest_comment_length lengths.max || 0 + json.average_comment_length avg_length + json.tags %w[bench simple json] + json.flags do + json.has_many @comments.size > 500 + json.empty @comments.empty? + end +end + +json.comments do + json.partial! 'comment_jbuilder', collection: @comments, as: 'comment' +end diff --git a/test/dummy_app/app/views/benchmarks/simple_json_as_json.simple_json.rb b/test/dummy_app/app/views/benchmarks/simple_json_as_json.simple_json.rb new file mode 100644 index 0000000..b5dc72e --- /dev/null +++ b/test/dummy_app/app/views/benchmarks/simple_json_as_json.simple_json.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +lengths = @comments.map { |comment| comment.body.length } +avg_length = lengths.empty? ? 0.0 : (lengths.sum.to_f / lengths.size).round(2) + +{ + meta: { + total_comments: @comments.size, + longest_comment_length: lengths.max || 0, + average_comment_length: avg_length, + tags: %w[bench simple json], + flags: { + has_many: @comments.size > 500, + empty: @comments.empty? + } + }, + comments: @comments.each_with_index.map do |comment, index| + partial!('benchmarks/_comment_simple_json', comment: comment, position: index + 1) + end +} diff --git a/test/dummy_app/app/views/benchmarks/simple_json_oj.simple_json.rb b/test/dummy_app/app/views/benchmarks/simple_json_oj.simple_json.rb new file mode 100644 index 0000000..b5dc72e --- /dev/null +++ b/test/dummy_app/app/views/benchmarks/simple_json_oj.simple_json.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +lengths = @comments.map { |comment| comment.body.length } +avg_length = lengths.empty? ? 0.0 : (lengths.sum.to_f / lengths.size).round(2) + +{ + meta: { + total_comments: @comments.size, + longest_comment_length: lengths.max || 0, + average_comment_length: avg_length, + tags: %w[bench simple json], + flags: { + has_many: @comments.size > 500, + empty: @comments.empty? + } + }, + comments: @comments.each_with_index.map do |comment, index| + partial!('benchmarks/_comment_simple_json', comment: comment, position: index + 1) + end +} diff --git a/test/dummy_app/bin/bench.rb b/test/dummy_app/bin/bench.rb index d795a88..a5cd789 100644 --- a/test/dummy_app/bin/bench.rb +++ b/test/dummy_app/bin/bench.rb @@ -1,6 +1,72 @@ # frozen_string_literal: true require 'action_dispatch/testing/integration' +require 'benchmark/ips' +require 'json' + +SCENARIOS = [ + { + label: 'jb', + path: '/benchmarks/jb.json', + decoder: ->(body) { JSON.parse(body) } + }, + { + label: 'jbuilder', + path: '/benchmarks/jbuilder.json', + decoder: ->(body) { JSON.parse(body) } + }, + { + label: 'simple_json(oj)', + path: '/benchmarks/simple_json_oj.json', + decoder: ->(body) { JSON.parse(body) } + }, + { + label: 'simple_json(AS::json)', + path: '/benchmarks/simple_json_as_json.json', + decoder: ->(body) { JSON.parse(body) } + } +].freeze + +SimpleJson.enable_template_cache + +def perform_request(path, n:) + session = ActionDispatch::Integration::Session.new(Rails.application) + session.get(path, params: { n: n }) + raise "Unexpected status #{session.response.status} for #{path}" unless session.response.successful? + session.response +end + +def verify_payloads(n) + baseline = SCENARIOS.first + expected_response = perform_request(baseline[:path], n: n) + expected_payload = baseline[:decoder].call(expected_response.body) + + SCENARIOS[1..].each do |scenario| + response = perform_request(scenario[:path], n: n) + if scenario[:media_type] && response.media_type != scenario[:media_type] + raise "#{scenario[:label]} content type mismatch: #{response.media_type}" + end + + actual_payload = scenario[:decoder].call(response.body) + raise "#{scenario[:label]} payload mismatch" unless actual_payload == expected_payload + end +end + +def benchmark_requests(n) + puts + puts "* Rendering #{n} partials via render_to_string" + + verify_payloads(n) + + Benchmark.ips do |x| + SCENARIOS.each do |scenario| + x.report(scenario[:label]) do + perform_request(scenario[:path], n: n) + end + end + x.compare! + end +end puts 'SimpleJson Benchmark' puts "ruby: #{RUBY_VERSION}" @@ -9,13 +75,6 @@ puts "oj: #{Oj::VERSION}" puts "----------------------" -puts '* Rendering 10 partials via render_partial' -ActionDispatch::Integration::Session.new(Rails.application).get '/benchmarks.json?n=10' - -puts -puts '* Rendering 100 partials via render_partial' -ActionDispatch::Integration::Session.new(Rails.application).get '/benchmarks.json?n=100' - -puts -puts '* Rendering 1000 partials via render_partial' -ActionDispatch::Integration::Session.new(Rails.application).get '/benchmarks.json?n=1000' +[10, 100, 1000].each do |n| + benchmark_requests(n) +end diff --git a/test/dummy_app/config/routes.rb b/test/dummy_app/config/routes.rb index 87c4299..f32cd37 100644 --- a/test/dummy_app/config/routes.rb +++ b/test/dummy_app/config/routes.rb @@ -15,7 +15,14 @@ resources :posts_with_cache, only: :show resources :posts_with_multiple_view_paths, only: :show - resources :benchmarks, only: :index + resources :benchmarks, only: [] do + collection do + get :jb, defaults: { format: :json } + get :jbuilder, defaults: { format: :json } + get :simple_json_oj, defaults: { format: :json } + get :simple_json_as_json, defaults: { format: :json } + end + end if Rails::VERSION::MAJOR >= 5 namespace :api do From 7334c19e2fc717056f2521540d07c8fc312bc3e0 Mon Sep 17 00:00:00 2001 From: "jingyuan.zhao" Date: Wed, 28 Jan 2026 20:46:38 +0900 Subject: [PATCH 21/24] Updated benchmark result in README.md --- README.md | 138 +++++++++++++++++++++--------------------------------- 1 file changed, 54 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index ec5a6dc..0c2960a 100644 --- a/README.md +++ b/README.md @@ -176,110 +176,80 @@ Rails.application.config.generators.simple_json false Here're the results of a benchmark (which you can find [here](https://github.com/aktsk/simple_json/blob/master/test/dummy_app/app/controllers/benchmarks_controller.rb) in this repo) rendering a collection to JSON. -### RAILS_ENV=development - ``` % ./bin/benchmark.sh -* Rendering 10 partials via render_partial -Warming up -------------------------------------- - jb 257.000 i/100ms - jbuilder 108.000 i/100ms - simple_json 2.039k i/100ms -Calculating ------------------------------------- - jb 2.611k (± 7.1%) i/s - 13.107k in 5.046110s - jbuilder 1.084k (± 3.5%) i/s - 5.508k in 5.088845s - simple_json 20.725k (± 4.4%) i/s - 103.989k in 5.026914s - -Comparison: - simple_json: 20725.5 i/s - jb: 2610.5 i/s - 7.94x (± 0.00) slower - jbuilder: 1083.8 i/s - 19.12x (± 0.00) slower - - -* Rendering 100 partials via render_partial -Warming up -------------------------------------- - jb 88.000 i/100ms - jbuilder 14.000 i/100ms - simple_json 290.000 i/100ms -Calculating ------------------------------------- - jb 928.202 (± 5.0%) i/s - 4.664k in 5.037314s - jbuilder 137.980 (± 6.5%) i/s - 700.000 in 5.094658s - simple_json 2.931k (± 5.2%) i/s - 14.790k in 5.060707s - -Comparison: - simple_json: 2931.1 i/s - jb: 928.2 i/s - 3.16x (± 0.00) slower - jbuilder: 138.0 i/s - 21.24x (± 0.00) slower - - -* Rendering 1000 partials via render_partial -Warming up -------------------------------------- - jb 11.000 i/100ms - jbuilder 1.000 i/100ms - simple_json 29.000 i/100ms -Calculating ------------------------------------- - jb 106.150 (± 5.7%) i/s - 539.000 in 5.094255s - jbuilder 13.012 (± 7.7%) i/s - 65.000 in 5.054016s - simple_json 271.683 (± 5.2%) i/s - 1.363k in 5.030646s - -Comparison: - simple_json: 271.7 i/s - jb: 106.1 i/s - 2.56x (± 0.00) slower - jbuilder: 13.0 i/s - 20.88x (± 0.00) slower -``` - -### RAILS_ENV=production - -``` -% RAILS_ENV=production ./bin/benchmark.sh +SimpleJson Benchmark +ruby: 4.0.1 +rails: 8.1.1 +json: 2.15.2 +oj: 3.16.11 +---------------------- -* Rendering 10 partials via render_partial +* Rendering 10 partials via render_to_string +ruby 4.0.1 (2026-01-13 revision e04267a14b) +PRISM [arm64-darwin24] Warming up -------------------------------------- - jb 246.000 i/100ms - jbuilder 97.000 i/100ms - simple_json 1.957k i/100ms + jb 23.000 i/100ms + jbuilder 30.000 i/100ms + simple_json(oj) 40.000 i/100ms +simple_json(AS::json) + 46.000 i/100ms Calculating ------------------------------------- - jb 2.611k (± 4.1%) i/s - 13.038k in 5.002304s - jbuilder 972.031 (± 4.7%) i/s - 4.850k in 5.001200s - simple_json 20.383k (± 3.8%) i/s - 101.764k in 4.999989s + jb 298.518 (±23.4%) i/s (3.35 ms/i) - 1.426k in 5.019370s + jbuilder 255.925 (± 4.3%) i/s (3.91 ms/i) - 1.290k in 5.052973s + simple_json(oj) 270.192 (± 3.7%) i/s (3.70 ms/i) - 1.360k in 5.039635s +simple_json(AS::json) + 297.476 (±10.1%) i/s (3.36 ms/i) - 1.518k in 5.145803s Comparison: - simple_json: 20382.8 i/s - jb: 2611.3 i/s - 7.81x (± 0.00) slower - jbuilder: 972.0 i/s - 20.97x (± 0.00) slower + jb: 298.5 i/s +simple_json(AS::json): 297.5 i/s - same-ish: difference falls within error + simple_json(oj): 270.2 i/s - same-ish: difference falls within error + jbuilder: 255.9 i/s - same-ish: difference falls within error -* Rendering 100 partials via render_partial +* Rendering 100 partials via render_to_string +ruby 4.0.1 (2026-01-13 revision e04267a14b) +PRISM [arm64-darwin24] Warming up -------------------------------------- - jb 90.000 i/100ms - jbuilder 11.000 i/100ms - simple_json 280.000 i/100ms + jb 19.000 i/100ms + jbuilder 14.000 i/100ms + simple_json(oj) 15.000 i/100ms +simple_json(AS::json) + 26.000 i/100ms Calculating ------------------------------------- - jb 883.446 (± 4.8%) i/s - 4.410k in 5.003438s - jbuilder 119.932 (± 8.3%) i/s - 605.000 in 5.085382s - simple_json 2.886k (± 4.2%) i/s - 14.560k in 5.054327s + jb 186.051 (±12.9%) i/s (5.37 ms/i) - 912.000 in 5.075117s + jbuilder 144.279 (± 2.1%) i/s (6.93 ms/i) - 728.000 in 5.048538s + simple_json(oj) 159.254 (± 1.9%) i/s (6.28 ms/i) - 810.000 in 5.088178s +simple_json(AS::json) + 249.690 (± 6.0%) i/s (4.00 ms/i) - 1.248k in 5.017042s Comparison: - simple_json: 2885.7 i/s - jb: 883.4 i/s - 3.27x (± 0.00) slower - jbuilder: 119.9 i/s - 24.06x (± 0.00) slower +simple_json(AS::json): 249.7 i/s + jb: 186.1 i/s - 1.34x slower + simple_json(oj): 159.3 i/s - 1.57x slower + jbuilder: 144.3 i/s - 1.73x slower -* Rendering 1000 partials via render_partial +* Rendering 1000 partials via render_to_string +ruby 4.0.1 (2026-01-13 revision e04267a14b) +PRISM [arm64-darwin24] Warming up -------------------------------------- - jb 12.000 i/100ms - jbuilder 1.000 i/100ms - simple_json 32.000 i/100ms + jb 4.000 i/100ms + jbuilder 2.000 i/100ms + simple_json(oj) 2.000 i/100ms +simple_json(AS::json) + 10.000 i/100ms Calculating ------------------------------------- - jb 124.627 (± 4.8%) i/s - 624.000 in 5.018515s - jbuilder 12.710 (± 7.9%) i/s - 64.000 in 5.073018s - simple_json 314.896 (± 3.2%) i/s - 1.600k in 5.086509s + jb 48.691 (± 2.1%) i/s (20.54 ms/i) - 244.000 in 5.013815s + jbuilder 27.930 (± 3.6%) i/s (35.80 ms/i) - 140.000 in 5.016897s + simple_json(oj) 29.083 (± 6.9%) i/s (34.38 ms/i) - 146.000 in 5.039076s +simple_json(AS::json) + 99.792 (± 7.0%) i/s (10.02 ms/i) - 500.000 in 5.037716s Comparison: - simple_json: 314.9 i/s - jb: 124.6 i/s - 2.53x (± 0.00) slower - jbuilder: 12.7 i/s - 24.78x (± 0.00) slower +simple_json(AS::json): 99.8 i/s + jb: 48.7 i/s - 2.05x slower + simple_json(oj): 29.1 i/s - 3.43x slower + jbuilder: 27.9 i/s - 3.57x slower ``` ## Migrating from Jbuilder From 487d4a84eab3184d69a023a428cfb29be3938c27 Mon Sep 17 00:00:00 2001 From: "jingyuan.zhao" Date: Wed, 28 Jan 2026 20:49:11 +0900 Subject: [PATCH 22/24] Add /test/dummy_app/tmp/* to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 3bdbd4e..ca4f590 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ gemfiles/*.lock /spec/reports/ /tmp/ /test/dummy_app/log/* +/test/dummy_app/tmp/* /vendor/bundle From b508162fb55eb7753ef8cc374a018bb53ce26794 Mon Sep 17 00:00:00 2001 From: "jingyuan.zhao" Date: Wed, 28 Jan 2026 20:50:17 +0900 Subject: [PATCH 23/24] Release v1.0.0 --- lib/simple_json/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/simple_json/version.rb b/lib/simple_json/version.rb index 285ef66..2809975 100644 --- a/lib/simple_json/version.rb +++ b/lib/simple_json/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module SimpleJson - VERSION = '0.1.0' + VERSION = '1.0.0' end From 85cdd2454b08cbbb14ca4635fcaaa760ce0d8d9c Mon Sep 17 00:00:00 2001 From: "jingyuan.zhao" Date: Wed, 28 Jan 2026 20:55:29 +0900 Subject: [PATCH 24/24] Added ruby 4.0 and rails 8.1 support. --- .github/workflows/main.yml | 4 ++-- gemfiles/rails_8.1.gemfile | 9 +++++++++ simple_json.gemspec | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 gemfiles/rails_8.1.gemfile diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 72500a0..19aa5c3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,8 +6,8 @@ jobs: build: strategy: matrix: - ruby: ['3.2', '3.3', '3.4'] - rails: ["7.1", "7.2", "8.0"] + ruby: ['3.2', '3.3', '3.4', "4.0"] + rails: ["7.1", "7.2", "8.0", "8.1"] runs-on: ubuntu-latest diff --git a/gemfiles/rails_8.1.gemfile b/gemfiles/rails_8.1.gemfile new file mode 100644 index 0000000..6f2a3da --- /dev/null +++ b/gemfiles/rails_8.1.gemfile @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +gemspec path: '../' + +gem 'jbuilder' +gem 'rails', '~> 8.1.0' +gem 'selenium-webdriver' diff --git a/simple_json.gemspec b/simple_json.gemspec index 3ca5f23..c339c35 100644 --- a/simple_json.gemspec +++ b/simple_json.gemspec @@ -25,7 +25,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'action_args' spec.add_development_dependency 'bundler' spec.add_development_dependency 'debug' - spec.add_development_dependency 'rails', '~> 8.0' + spec.add_development_dependency 'rails', '>= 7.1' spec.add_development_dependency 'rake' spec.add_development_dependency 'selenium-webdriver' spec.add_development_dependency 'test-unit-rails'