diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index c4872f1..c5e52be 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -6,7 +6,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - ruby-version: ['2.7', '3.0', '3.1'] + ruby-version: ['3.1', '3.2', '3.3'] steps: - uses: actions/checkout@v3 - name: Set up Ruby diff --git a/README.md b/README.md index 1697744..e094675 100644 --- a/README.md +++ b/README.md @@ -847,7 +847,11 @@ I18n keys: #### `transform_to_value(value)` -Always returns ValidResult. The value is transformed to provided argument (disregarding the original value). See also [`default`](#defaultdefault_value-on-nil). +Always returns ValidResult. The value is transformed to provided argument (disregarding the original value). + +Returned value is deeply freezed with [`Ractor::make_shareable`](https://docs.ruby-lang.org/en/master/Ractor.html#method-c-make_shareable) to prevent application bugs due to modification of unintentionally shared value. If that effect is undesired, use [`transform { value }`](#transform--value--) instead. + +See also [`default`](#defaultdefault_value-on-nil). ### "Web-form" types diff --git a/datacaster.gemspec b/datacaster.gemspec index 76276c0..97bab05 100644 --- a/datacaster.gemspec +++ b/datacaster.gemspec @@ -8,7 +8,7 @@ Gem::Specification.new do |spec| spec.summary = %q{Run-time type checker and transformer for Ruby} spec.license = 'MIT' - spec.required_ruby_version = Gem::Requirement.new('>= 2.7.0') + spec.required_ruby_version = Gem::Requirement.new('>= 3.1') spec.metadata['source_code_uri'] = 'https://github.com/EugZol/datacaster' spec.homepage = 'https://github.com/EugZol/datacaster' diff --git a/lib/datacaster/absent.rb b/lib/datacaster/absent.rb index 517eb05..29ece75 100644 --- a/lib/datacaster/absent.rb +++ b/lib/datacaster/absent.rb @@ -19,5 +19,9 @@ def to_s def present? false end + + def ==(other) + other.is_a?(self.class) + end end end diff --git a/lib/datacaster/predefined.rb b/lib/datacaster/predefined.rb index 8b15aca..832046c 100644 --- a/lib/datacaster/predefined.rb +++ b/lib/datacaster/predefined.rb @@ -276,7 +276,7 @@ def switch(base = nil, **on_clauses) end def transform_to_value(value) - transform { value } + transform { Datacaster::Utils.deep_freeze(value) } end def with(keys, caster) diff --git a/lib/datacaster/utils.rb b/lib/datacaster/utils.rb index cd19596..e1f41fc 100644 --- a/lib/datacaster/utils.rb +++ b/lib/datacaster/utils.rb @@ -2,6 +2,10 @@ module Datacaster module Utils extend self + def deep_freeze(value, copy: true) + Ractor.make_shareable(value, copy:) + end + def merge_errors(left, right) add_error_to_base = ->(hash, error) { hash[:base] ||= [] diff --git a/lib/datacaster/version.rb b/lib/datacaster/version.rb index c11d121..f2deeb7 100644 --- a/lib/datacaster/version.rb +++ b/lib/datacaster/version.rb @@ -1,3 +1,3 @@ module Datacaster - VERSION = "3.2.8" + VERSION = "3.3.0" end diff --git a/spec/datacaster_spec.rb b/spec/datacaster_spec.rb index bb6b386..2a140b8 100644 --- a/spec/datacaster_spec.rb +++ b/spec/datacaster_spec.rb @@ -1033,10 +1033,24 @@ end describe "constant mapping" do + let(:transform_to_value_caster) do + Datacaster.schema { transform_to_value({ a: { b: { c: ["d"] } } }) } + end + + let(:returned_value) do + transform_to_value_caster.(123).value! + end + it "returns exact value" do - t = Datacaster.schema { transform_to_value("Test") } + expect(returned_value).to eq({ a: { b: { c: ["d"] } } }) + end + + it "freezes returned value" do + expect(returned_value).to be_frozen + end - expect(t.(123).to_dry_result).to eq Success("Test") + it "freezes deeply" do + expect { returned_value[:a][:b][:c].pop }.to raise_error FrozenError end end