How do you not have to provide every hash key to Dry::Struct


#1

How do you allow a missing attribute for dry-struct/dry-types? In the following code I would not always want have to provide the “interesting_fact” attribute.

I see mention of “maybe” in the introduction documentation: http://dry-rb.org/gems/dry-types/optional-values/

…but I haven’t been able to apply it.

 require 'dry-struct'                                                               
 #require 'dry-types' needed?                                                       
                                                                                    
 module Types                                                                       
   include Dry::Types.module                                                        
 end                                                                                
                                                                                    
 class User < Dry::Struct                                                           
   attribute :name, Types::String.optional                                          
   attribute :interesting_fact, Types::String                                      
 end                                                                                
                                                                                    
 user = User.new(name: nil)                                                         
                                                                                    
 #-> error :interesting_fact is missing in Hash input  
                                                                       

…trying to use maybe results in constant undefined

Types::Maybe::Coercible
# -> error uninitialized constant Types (NameError)

#2

Oops, we forgot to update this section (I’ll do it shortly). maybe types are now provided by an extension, to load it you need to do this:

Dry::Types.load_extensions(:maybe)

It requires dry-monads which you need to add to your Gemfile if you want to use this extension.

Re struct attributes, you can configure a constructor that will not expect all keys to be present, so just do this:

require 'dry-struct'                                                               
require 'dry-types'
                                                                                    
 module Types                                                                       
   include Dry::Types.module                                                        
 end                                                                                
                                                                                    
 class User < Dry::Struct
   constructor_type(:schema)
                                                           
   attribute :name, Types::String.optional                                          
   attribute :interesting_fact, Types::String                                      
 end                                                                                
                                                                                    
 user = User.new(name: nil)               

#3

Thanks for the reply @solnic , really appreciate it!

The “constructor_types(:schema)” works great. …however I came across this related issue https://github.com/dry-rb/dry-types/issues/19

…where you say “However, I think this feature should not be used typically. Structs and Values are meant to be strict, you are constructing them from input that is supposed to be in valid state and have all the keys and values in place. For dealing with untrusted input it’s better to use dry-validation’s schemas.”

…and this would seem to apply to my use-case. My intent is to create a business object that should be validated and coerced.

I reworked the example but I’m not quite sure if this is the best approach. I guess I’m a little uncertain at how the validation and coercion fit together with ‘dry-validation’ and ‘dry-types’.

Below:
I use ‘dry-types’ to provide a default. (not sure if this can be done in better fashion?)
I’m not sure how to coerce the optional age to be an integer, when providing a string

require 'dry-validation'                                                           
                                                                                    
 module Types                                                                       
   include Dry::Types.module                                                        
 end                                                                                
                                                                                    
 class User                                                                         
   attr_reader :name, :age, :interesting_fact                                       
                                                                                    
   def initialize(values, schema = BasicSchema)                                     
     errors = schema.call(values)                                                   
     raise ArgumentError.new(errors.messages) if errors.failure?                    
                                                                                    
     # would this be the proper approach?                                           
     attributes = schema.(values).to_h                                              
     @name = attributes[:name]                                                      
     @age = attributes[:age]                                                        
     @interesting_fact = Types::String.default("I'm not interesting :(")[attributes[:interesting_fact]]
   end                                                                              
 end                                                                                
                                                                                    
                                                                                    
 BasicSchema = Dry::Validation.Schema do                                            
   configure { config.input_processor = :sanitize }                                 
   required(:name).filled                                                           
   optional(:age).maybe(:int?) #<- whats the prober way to coerce to an integer? 
   optional(:interesting_fact)                                                      
 end                                                                                
                                                                                    
 user = User.new(name: "david")                                                     
 puts "name: #{user.name}, age: #{user.age}, interesting fact: #{user.interesting_fact}"
                                                                                    
 # I want potentially pass a string for age and have it coerced to int before validation.
 user = User.new(name: "jay", age: "30", interesting_fact: "I like dogs")           
 puts "name: #{user.name}, age: #{user.age}, interesting fact: #{user.interesting_fact}"

result

name: david, age: , interesting fact: I'm not interesting :(
# not sure how best to coerce the string to int and have it as an optional key
dry-types.rb:27:in `initialize': {:age=>["must be an integer"]} (ArgumentError)

I’m trying to get the best approach/basics down, on the use of your gems. Conceptually I love the way they are done and I’ve convinced my work to allow me to create a project using them. …so I want to make sure I’m using them to full effect! :wink:


#4

What’s the source of the data that you’re using? ie is it some JSON sent from an external system?


#5

At this point we are not sure of the back-end. We are experimenting with delaying that choice and building up our domain objects first. …most likely it will be in Postgres and we plan to stick to your ecosystem and use rom-rb. …however it could be Rethink for example.


#6

Oh in that case, just define strict structs with all fields that are expected to be there. If you are modeling your domain obejcts, and eventually you’re gonna provide data for them from your database, then you don’t need validation.

Fantastic :slight_smile: There’s a big focus on great support for Postgres in rom-sql, we already support its custom types (and it’s easy to extend it) and there’s support for native UPSERT too, more goodies are coming soon.

The future of RethinkDB is uncertain as they recently announced they are shutting down the project :frowning:


#7

O boy. I didn’t hear about Rethink. …that’s sad news I was a fan. I hope the product makes the transition to opensource successfully.

I have started diving into rom-rb. …I have so many questions! lol
I’ll create new threads for them in support when I nail down my focus. I know your probably really busy but are there any plans to pump out some more documentation?

Thanks again for the help! …o and I really enjoyed your talk (https://www.youtube.com/watch?v=jZ0Xf47P6oo) …ran across that while researching. …it help fill in some blanks for me, regarding your gems. I’m very aligned it your approach and the desire to have a more functional like approach to ruby development.


#8

Me too. Such an ambitious OSS project w/o a company behind it will be a challenge to work on, so I’m not really optimistic about its future to be honest :frowning:

I can imagine :slight_smile: Just ask!

I’ll be adding more docs before the next release of rom-repository. I hope it’ll be before the end of the year. Anything specific that you think we should focus on more? I’ve found it to be very hard to write docs for rom-rb, mostly because it’s hard for me to figure out what to focus on exactly.

Ah awesome, I’m glad it helped :slight_smile:


#9

Here is a quick recap of my experience in case it helps. Found rom-rb, read though the documentation, and loved the approach.

Biggest hurdle just seemed to be the setup and figuring out how to fit it into an application. (At this time I didn’t have any understanding of all the dry gems that are in use)

Ended up leaning heavily on the examples in article:
http://icelab.github.io/conversational-intro-to-rom-rb/part-1.html
http://icelab.github.io/conversational-intro-to-rom-rb/part-2.html

This got me through the setup phase and after that it was more specific issues with understanding how types worked. …as I was having issues with a create command, with a table with an array column and a JsonB column.

So I started reading more about the dry gems themselves. For most of them I didn’t immediately see there use case.

Found your video and it made a lot of things clearer. …even helped with understanding Mapper’s. (currently the mapper area is empty)

…also this video https://www.youtube.com/watch?v=GhPcW6D_qjY&t=1055s helped me understand gateways and a few other pieces of the puzzle.

…since then it’s just been a lot of playing around in the console trying to piece it all together. I need to play with aggregates and the combines functionality to understand the differences better.

Anyway the more I learn the more I realise the documentation is quite good and covers most of the basics, it’s just hard to piece it together when seeing it for the first time. Other then a comprehensive tutorial, that walks you through the whole thing, I’m not sure how best to tackle that initial learning curve.

At this point I would love to see more code examples, that demonstrate a “completed” app. …an example of how a project is setup to use all these awesome gems. The directory structure, how things are name spaced and surfaced to the rest of the app, etc. I believe this is the hard part for a lot of us moving away from frameworks like rails.

…anyway hope that helps? and once my own understanding gets higher I would happily help out with documentation.

Thanks, and keep up the good work :wink:


#10

Ah yes, step-by-step guides is something we’re desperately missing. What are you considering for your stack exactly? Rails? Hanami? Sinatra? dry-web-roda?

You can see a real-world dry-rb + rom-rb app here, FWIW :slight_smile:

Please feel free to ask rom/dry rb questions in discuss.rom-rb.org as it might be helpful for other people too.


#11

Great I’ll check it out.
…and we are cleaning up a large Rails monolith. …trying to own our domain, and initially its just a lot of automation processes that we are removing from (Cron jobs -> Rake tasks -> Delayed jobs; to an EventMachine application that calls into our new (currently building) domain.