From b0b7715af283aecdcda0604fc66119548e647a3a Mon Sep 17 00:00:00 2001 From: Sean Collins Date: Thu, 14 Aug 2025 10:56:49 -0600 Subject: [PATCH 1/5] Update CI to modern ruby versions --- .github/workflows/ci.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f4e0828..59183c6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,10 +9,14 @@ jobs: strategy: fail-fast: false matrix: - ruby-version: ['2.7', '3.0', '3.1', '3.2'] + ruby-version: + - '3.2' + - '3.3' + - '3.4' + - 'ruby-head' steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Ruby uses: ruby/setup-ruby@v1 From 6c9844fd5c6e95d53809417af08f775ca9fd1c77 Mon Sep 17 00:00:00 2001 From: Sean Collins Date: Thu, 14 Aug 2025 11:00:36 -0600 Subject: [PATCH 2/5] Specify license in mutant config --- config/mutant.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/mutant.yml b/config/mutant.yml index 54be0c4..68d0e60 100644 --- a/config/mutant.yml +++ b/config/mutant.yml @@ -10,3 +10,4 @@ mutation: timeout: 1.0 coverage_criteria: timeout: true +usage: opensource From ab91770fb8b8d0cc6ab55a9cd69b2f4f2884eb1b Mon Sep 17 00:00:00 2001 From: Sean Collins Date: Thu, 14 Aug 2025 11:07:14 -0600 Subject: [PATCH 3/5] Add bigdecimal to Gemfile, for Ruby 3.4+ --- Gemfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Gemfile b/Gemfile index ea82bc2..87a1fb0 100644 --- a/Gemfile +++ b/Gemfile @@ -9,6 +9,8 @@ group :development, :test do gem 'mutant', github: 'mbj/mutant' gem 'mutant-rspec', github: 'mbj/mutant' + gem 'bigdecimal' + source 'https://oss:sxCL1o1navkPi2XnGB5WYBrhpY9iKIPL@gem.mutant.dev' do gem 'mutant-license' end From 7d71dc98e36b3ec6e092c6b2f8094a159668cd29 Mon Sep 17 00:00:00 2001 From: Sean Collins Date: Thu, 14 Aug 2025 11:24:25 -0600 Subject: [PATCH 4/5] Add support for deep freezing Data classes --- lib/ice_nine.rb | 1 + lib/ice_nine/freezer/data.rb | 31 ++++++++++ spec/shared/data_deep_freeze.rb | 17 ++++++ .../data/class_methods/deep_freeze_spec.rb | 58 +++++++++++++++++++ 4 files changed, 107 insertions(+) create mode 100644 lib/ice_nine/freezer/data.rb create mode 100644 spec/shared/data_deep_freeze.rb create mode 100644 spec/unit/ice_nine/freezer/data/class_methods/deep_freeze_spec.rb diff --git a/lib/ice_nine.rb b/lib/ice_nine.rb index a659785..eece797 100644 --- a/lib/ice_nine.rb +++ b/lib/ice_nine.rb @@ -6,6 +6,7 @@ require 'ice_nine/freezer/object' require 'ice_nine/freezer/no_freeze' require 'ice_nine/freezer/array' +require 'ice_nine/freezer/data' require 'ice_nine/freezer/false_class' require 'ice_nine/freezer/hash' diff --git a/lib/ice_nine/freezer/data.rb b/lib/ice_nine/freezer/data.rb new file mode 100644 index 0000000..50c3455 --- /dev/null +++ b/lib/ice_nine/freezer/data.rb @@ -0,0 +1,31 @@ +# encoding: utf-8 + +module IceNine + class Freezer + + # A freezer class for handling Data objects + class Data < Object + + # Deep Freeze a Data object + # + # @example + # Person = Data.define(:name, :age) + # person = Person.new(name: 'John', age: 30) + # frozen_person = IceNine::Freezer::Data.deep_freeze(person) + # frozen_person.name.frozen? # => true + # + # @param [Data] data + # @param [RecursionGuard] recursion_guard + # + # @return [Data] + def self.guarded_deep_freeze(data, recursion_guard) + super + data.to_h.each_value do |value| + Freezer.guarded_deep_freeze(value, recursion_guard) + end + data + end + + end # Data + end # Freezer +end # IceNine diff --git a/spec/shared/data_deep_freeze.rb b/spec/shared/data_deep_freeze.rb new file mode 100644 index 0000000..c3ba086 --- /dev/null +++ b/spec/shared/data_deep_freeze.rb @@ -0,0 +1,17 @@ +# encoding: utf-8 + +shared_examples 'IceNine::Freezer::Data.deep_freeze' do + it 'returns the object' do + should be(value) + end + + it 'keeps the object frozen' do + expect(subject).to be_frozen + end + + it 'freezes each attribute value' do + subject.to_h.each_value do |attribute_value| + expect(attribute_value).to be_frozen + end + end +end diff --git a/spec/unit/ice_nine/freezer/data/class_methods/deep_freeze_spec.rb b/spec/unit/ice_nine/freezer/data/class_methods/deep_freeze_spec.rb new file mode 100644 index 0000000..2018bf5 --- /dev/null +++ b/spec/unit/ice_nine/freezer/data/class_methods/deep_freeze_spec.rb @@ -0,0 +1,58 @@ +# encoding: utf-8 + +require 'spec_helper' +require 'ice_nine' + +describe IceNine::Freezer::Data, '.deep_freeze' do + subject { object.deep_freeze(value) } + + let(:object) { described_class } + + context 'with a Data object' do + let(:klass) { Data.define(:name, :age) } + let(:value) { klass.new(name: 'John', age: 30) } + + context 'without a circular reference' do + it_behaves_like 'IceNine::Freezer::Data.deep_freeze' + end + + context 'with a circular reference' do + let(:klass) { Data.define(:name, :age, :refs) } + let(:refs) { [] } + let(:value) { klass.new(name: 'John', age: 30, refs: refs) } + + before { refs << value } + + it_behaves_like 'IceNine::Freezer::Data.deep_freeze' + end + + context 'with nested Data objects' do + let(:address_class) { Data.define(:street, :city) } + let(:person_class) { Data.define(:name, :address) } + let(:address) { address_class.new(street: '123 Main St', city: 'Anytown') } + let(:value) { person_class.new(name: 'Jane', address: address) } + + it_behaves_like 'IceNine::Freezer::Data.deep_freeze' + + it 'deeply freezes nested Data objects' do + expect(subject.address).to be_frozen + expect(subject.address.street).to be_frozen + expect(subject.address.city).to be_frozen + end + end + + context 'with mutable values' do + let(:klass) { Data.define(:items, :metadata) } + let(:value) { klass.new(items: ['apple', 'banana'], metadata: { count: 2 }) } + + it_behaves_like 'IceNine::Freezer::Data.deep_freeze' + + it 'deeply freezes attribute values' do + expect(subject.items).to be_frozen + expect(subject.items.first).to be_frozen + expect(subject.metadata).to be_frozen + expect(subject.metadata[:count]).to be_frozen + end + end + end +end From 8c4b19054d69acd304273cb9e4e69ff4894db14d Mon Sep 17 00:00:00 2001 From: Sean Collins Date: Thu, 14 Aug 2025 11:45:33 -0600 Subject: [PATCH 5/5] Remove useless 'super' Found via mutant --- lib/ice_nine/freezer/data.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/ice_nine/freezer/data.rb b/lib/ice_nine/freezer/data.rb index 50c3455..4b8857e 100644 --- a/lib/ice_nine/freezer/data.rb +++ b/lib/ice_nine/freezer/data.rb @@ -19,7 +19,6 @@ class Data < Object # # @return [Data] def self.guarded_deep_freeze(data, recursion_guard) - super data.to_h.each_value do |value| Freezer.guarded_deep_freeze(value, recursion_guard) end