dry-rb / dry-struct

Typed struct and value objects
https://dry-rb.org/gems/dry-struct
MIT License
410 stars 62 forks source link

Error while using optional on Dry::Struct #156

Closed bilby91 closed 3 years ago

bilby91 commented 4 years ago

I can't use the optional type with structs.

A clear and concise description of what the bug is.

require'byebug'
require'rom'
require'rom-http'
require 'crack'
require 'dry-types'
require 'dry-struct'
require 'dry/monads'

Dry::Types.load_extensions(:maybe)
module Types
  include Dry.Types()
end

class TestStruct < Dry::Struct
  attribute :foo, Dry::Struct.optional do
    attribute :bar, Types::String
  end
end
Traceback (most recent call last):
    6: from app.rb:14:in `<main>'
    5: from app.rb:15:in `<class:TestStruct>'
    4: from /home/bilby91/.rvm/gems/ruby-2.7.0/gems/dry-struct-1.3.0/lib/dry/struct/class_interface.rb:97:in `attribute'
    3: from /home/bilby91/.rvm/gems/ruby-2.7.0/gems/dry-struct-1.3.0/lib/dry/struct/class_interface.rb:473:in `build_type'
    2: from /home/bilby91/.rvm/gems/ruby-2.7.0/gems/dry-struct-1.3.0/lib/dry/struct/struct_builder.rb:20:in `call'
    1: from /home/bilby91/.rvm/gems/ruby-2.7.0/gems/dry-struct-1.3.0/lib/dry/struct/struct_builder.rb:58:in `const_name'
/home/bilby91/.rvm/gems/ruby-2.7.0/gems/dry-struct-1.3.0/lib/dry/struct/struct_builder.rb:45:in `array?': undefined method `primitive' for #<Dry::Types::Sum::Constrained:0x000055670c111430> (NoMethodError)
Did you mean?  primitive?

Provide detailed steps to reproduce, an executable script would be best.

The foo will be a optional Struct type.

bilby91 commented 4 years ago

This test seems to reproduce the bug:

diff --git a/spec/integration/struct_spec.rb b/spec/integration/struct_spec.rb
index 889637e..d88d8ae 100644
--- a/spec/integration/struct_spec.rb
+++ b/spec/integration/struct_spec.rb
@@ -205,6 +205,22 @@ RSpec.describe Dry::Struct do
     end
   end

+  describe 'optional values' do
+    subject(:struct) do
+      Class.new(Dry::Struct) do
+        attribute :person, Dry::Struct.optional do
+          attribute :name, Dry::Types['strict.string']
+        end
+      end
+    end
+
+    it 'sets missing values using default-value types' do
+      attrs = {}
+
+      expect(struct.new({}).to_h).to eql(attrs)
+    end
+  end
+
mtarnovan commented 4 years ago

I think my problem is related:

2.5.8 :001 > module T
2.5.8 :002?>   include Dry.Types
2.5.8 :003?>   end
 => T 
2.5.8 :006 > class TestStruct < Dry::Struct
2.5.8 :007?>     attribute :foo, T::Strict::String
2.5.8 :008?>     attribute :bar, T::Strict::String.optional
2.5.8 :009?>   end
 => TestStruct 
2.5.8 :010 > TestStruct.new(foo: "asd")
Traceback (most recent call last):
        1: from (irb):10
Dry::Struct::Error ([TestStruct.new] :bar is missing in Hash input)

I had to downgrade to:

gem 'dry-types', '0.9.3'
gem 'dry-struct', '0.1.1'

to get it working again. Not sure if other, higher versions work, these are the versions that I had before upgrading to latest (struct 1.3, types 1.4)

bilby91 commented 4 years ago

@mtarnovan .optional let's bar be nil, but needs to be present in the schema. I think what you are looking is this:

class TestStruct < Dry::Struct
  attribute :foo, T::Strict::String
  attribute? :bar, T::Strict::String
end
mtarnovan commented 4 years ago

I see. Thanks @bilby91