From a2db653702742efabcb9a665d2bfbb81d5ef9004 Mon Sep 17 00:00:00 2001 From: Jesse van der Pluijm Date: Mon, 26 Jan 2026 15:02:55 +0100 Subject: [PATCH 1/3] Add 'ostruct' dependency The `ostruct` gem is no longer a default gem in Ruby 4. See: [release notes](https://www.ruby-lang.org/en/news/2025/12/25/ruby-4-0-0-released/#stdlib-updates) Calling `require 'ostruct'` results in this message: > warning: ostruct used to be loaded from the standard library, but is not part > of the default gems since Ruby 4.0.0. You can add ostruct to your Gemfile or > gemspec to fix this error. Followed by an error: ``` /path/to/ruby/4.0.0/bundled_gems.rb:60:in 'Kernel.require': cannot load such file -- ostruct (LoadError) ``` --- json-schema_builder.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/json-schema_builder.gemspec b/json-schema_builder.gemspec index 040ee6f..7df32fc 100644 --- a/json-schema_builder.gemspec +++ b/json-schema_builder.gemspec @@ -26,4 +26,5 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'appraisal' spec.add_dependency 'activesupport', '>= 4.0' spec.add_dependency 'json-schema', '>= 2.1' + spec.add_dependency 'ostruct' end From e0e2e7e7a1f04d75a35a318f1d455f7bf97282d9 Mon Sep 17 00:00:00 2001 From: Jesse van der Pluijm Date: Mon, 26 Jan 2026 16:05:14 +0100 Subject: [PATCH 2/3] Fix DeepOpenStruct for Ruby 3.0+ OpenStruct changes Ruby 3.0 (via `ostruct` gem v0.3.0) removed the internal `new_ostruct_member` method from `OpenStruct`. This was a protected/internal method used to dynamically define accessor methods. Replace manual `@table` manipulation with a simpler approach that transforms the hash and delegates to `super()`. References: - ostruct gem versions: https://rubygems.org/gems/ostruct/versions - Changes in ostruct 0.3.0: https://my.diffend.io/gems/ostruct/0.1.0/0.3.0 - Ruby/ostruct version mapping: https://stdgems.org/ostruct/ --- lib/json/schema_builder/rspec_helper/deep_open_struct.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/json/schema_builder/rspec_helper/deep_open_struct.rb b/lib/json/schema_builder/rspec_helper/deep_open_struct.rb index b6ceb31..166e4c4 100644 --- a/lib/json/schema_builder/rspec_helper/deep_open_struct.rb +++ b/lib/json/schema_builder/rspec_helper/deep_open_struct.rb @@ -3,12 +3,8 @@ module SchemaBuilder module RSpecHelper class DeepOpenStruct < OpenStruct def initialize(hash = { }) - @table = { } - hash.each_pair do |key, value| - key = key.to_sym - @table[key] = _transform value - new_ostruct_member key - end + transformed = hash.transform_keys(&:to_sym).transform_values { |v| _transform(v) } + super(transformed) end def ==(other) From cb7b54ff2178120590717f7c17ce47645ad484db Mon Sep 17 00:00:00 2001 From: Jesse van der Pluijm Date: Mon, 26 Jan 2026 16:05:19 +0100 Subject: [PATCH 3/3] Fix spec expectations for Ruby 3.0 keyword argument separation Ruby 3.0 enforces strict separation between positional hash arguments and keyword arguments. RSpec matchers now distinguish between: - `{ foo: :bar }` - positional hash argument - `foo: :bar` - keyword arguments Update test expectations to use explicit hash literals since the production code passes options as positional hash arguments. Reference: https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/ --- spec/unit/dsl_spec.rb | 6 +++--- spec/unit/entity_spec.rb | 2 +- spec/unit/schema_spec.rb | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/unit/dsl_spec.rb b/spec/unit/dsl_spec.rb index 2105f13..6226ef0 100644 --- a/spec/unit/dsl_spec.rb +++ b/spec/unit/dsl_spec.rb @@ -27,12 +27,12 @@ def initialize(*args); end end it 'should dispatch to entity' do - expect(instance).to receive(:entity).with(:something, 1, foo: :bar).and_call_original + expect(instance).to receive(:entity).with(:something, 1, { foo: :bar }).and_call_original instance.something 1, foo: :bar end it 'should allow unnamed entities' do - expect(instance).to receive(:entity).with(:something, nil, foo: :bar).and_call_original + expect(instance).to receive(:entity).with(:something, nil, { foo: :bar }).and_call_original instance.something foo: :bar end end @@ -60,7 +60,7 @@ def initialize(*args); end it 'should set the parent' do expect(instance.class).to receive(:new) - .with('name', parent: kind_of(klass)).and_call_original + .with('name', { parent: kind_of(klass) }).and_call_original entity = instance.entity :something, 'name' expect(entity).to be_a klass diff --git a/spec/unit/entity_spec.rb b/spec/unit/entity_spec.rb index 8b5e45e..913a3a2 100644 --- a/spec/unit/entity_spec.rb +++ b/spec/unit/entity_spec.rb @@ -130,7 +130,7 @@ def example it "should #{ validator }" do expect(JSON::Validator).to receive(validator) - .with subject.as_json, { }, working: true, opts: true + .with subject.as_json, { }, { working: true, opts: true } subject.send validator, { }, opts: true end end diff --git a/spec/unit/schema_spec.rb b/spec/unit/schema_spec.rb index 1a9b8f3..4567d21 100644 --- a/spec/unit/schema_spec.rb +++ b/spec/unit/schema_spec.rb @@ -181,7 +181,7 @@ def example describe "##{ validator }" do it "should #{ validator }" do expect(JSON::Validator).to receive(validator) - .with schema.as_json, { }, opts: true + .with schema.as_json, { }, { opts: true } schema.send validator, { }, opts: true end end