This library officially supports the following Ruby versions:
- MRI
>= 3.0
- jruby
>= 9.4
(not tested on CI)
See LICENSE
file.
Flexible type system for Ruby with coercions and constraints
Home Page: https://dry-rb.org/gems/dry-types
License: MIT License
This library officially supports the following Ruby versions:
>= 3.0
>= 9.4
(not tested on CI)See LICENSE
file.
Some as struct but with an equalizer
I realized I cannot use |
to create sum types with Dry::Types::Struct
. The following doesn't work with the latest version from Rubygems:
class DiscreetFilter < Dry::Types::Struct
attribute :target_column, Types::Strict::String
attribute :name, Types::Strict::String
attribute :type, Types::Strict::String.enum("DISCREET")
attribute :id, Types::Strict::String
attribute :active, Types::Bool
attribute :values, Types::Array.member(Types::DiscreetFilterValues)
end
class RangeFilter < Dry::Types::Struct
RangeFilterValue = Types::Hash.schema({
range_max: Types::Coercible::Float,
range_min: Types::Coercible::Float
})
attribute :target_column, Types::Strict::String
attribute :name, Types::Strict::String
attribute :type, Types::Strict::String.enum("RANGE")
attribute :unit, Types::Strict::String
attribute :id, Types::Strict::String
attribute :active, Types::Bool
attribute :values, Types::Array.member(RangeFilterValue)
end
#throws: NoMethodError: undefined method `|' for Types::DiscreetFilter:Class
LineFilter = Types::DiscreetFilter | Types::RangeFilter
Is there a reason why this shouldn't be added? It seems to me sum types should be supported here to, right?
I also tried naively to do Dry::Types::Sum.new Types::DiscreetFilter, Types::RangeFilter
but that doesn't work because there's a try
method missing.
Currently constrained cannot be used with sums
The internal use of the method try
in combination with method_missing runs into an error as soon as active_support with it's try
core_ext is required.
require 'bundler/inline'
gemfile true do
gem 'dry-types'
gem 'activesupport', require: false
end
include Dry::Types.module
CustomType = Strict::String.enum("val1", "val2")
CustomStringArray = Strict::Array.member(CustomType)
p CustomStringArray.call(["val1"])
require "active_support/core_ext/object/try"
p CustomStringArray.call(["val1"])
I'm not sure the method_missing cascade is used similar in other scenarios.
Given how common active_support is even outside rails applications this seems like a issue that should be fixed. Without to much insight into the code renaming the try method to something unique or changing how the method_missing cascade is used here might be an option so solve this.
Hello, @solnic!
I revisited your gem again and now it looks better to me. In fact, I find it somehow ideal because it gives maximum flexibility and the API is pretty good. I cannot believe because the first time I found dry-data
very ugly.
So the last thing that bothers me is a name. What's wrong with it? Is it a reference to Haskell data
keyword or what? If so then algebraic data types (structs and sum types in your terms) are not a whole picture. dry-data
works not only with them but with primitive types too. If it's not a reference to data
keyword then what it is? It looks that dry-data
is not about data but types. Do you agree with me or maybe I'm missing something?
http://dry-rb.org/gems/dry-types/ links to http://dry-rb.org/gems/dry-types/structs-and-values which 404s
to_h
is a useful method on structs.
#39 was closed but it seems that we can still have a value that violates type check/coercion rule.
[4] pry(main)> Types::Maybe::Strict::Int.default(nil)[nil]
=> nil
I expected to receive an error on calling default(nil)
or None
as a result of the entire expression :)
On a large scale I want to have something like this:
class MyOptionalValue < Dry::Types::Value
constructor_type :schema
attribute :foo, Types::Maybe::Strict::Int.default(nil)
end
v = MyOptionalValue.new({})
v.foo # => None
I store an empty hash value in DB but I want more strictness for my domain models. I put keys to the database only if they have values.
Hey there, long-time fan of Virtus here. Trying to switch over to Dry::Data.
We're using Rails and it appears I keep stumbling on There is already an item registered with the key
errors. I believe that's due to how Rails reload classes and such upon file changes.
The only way I found to circumvent that was to use:
Dry::Data.remove_instance_variable :@container
... to reset the container. However, I had to do that manually in the process.
Any idea how to simplify that?
Dry::Data::SumType
uses kliesli
gem, which overrides basic Ruby object (Object
and Kernel
) and method_added
.
It also somehow conflicts with Rails and ActiveSupport: https://gist.github.com/kirs/6bdcb0ed284d94c76a5f
Hello,
I was a little bit confused with next scenario:
2.3.0 :001 > require 'dry-data'
=> true
2.3.0 :002 > billing_period = Dry::Data['int'].enum(1, 3, 6, 12)
=> #<Dry::Data::Enum:0x007fe1fc85de88 @type=#<Dry::Data::Constrained:0x007fe1fc85ded8 @type=#<Dry::Data::Type constructor=#<Method: Dry::Data::Type.constructor> options={:primitive=>Integer}>, @options={:rule=>#<Dry::Logic::Rule::Value name=Integer predicate=#<Dry::Logic::Predicate id=:inclusion?>>}, @rule=#<Dry::Logic::Rule::Value name=Integer predicate=#<Dry::Logic::Predicate id=:inclusion?>>>, @options={:values=>[1, 3, 6, 12]}, @values=[1, 3, 6, 12]>
2.3.0 :003 > billing_period[3]
=> 12
2.3.0 :004 >
Here you can see that due to special Fixnum processing actual billing period was treated as an array index. That led me to some strange troubles. My spec builds model then persists it (occasionally with wrong value 12 instead of 3). After that when I try to fetch that record from DB the spec fails with an error "nil violates constraints" because it reads 12 and then tries to construct a value by calling PeriodType[12].
It is quite confusing, isn't it? What do you think? :)
P.S. Piotr I want to thank you very much for all that cool stuff. Great work!
Nikita
While integrating dry-data into a small project of mine I happened to try to initialize a dry struct before specifying any attributes
>> require 'dry/data'
=> true
>>
>> class User < Dry::Data::Struct
| end
=> nil
>>
>> params = {}
=> {}
>>
>> User.new(params)
NoMethodError: undefined method `[]' for nil:NilClass
from /Users/johnbackus/.rvm/gems/ruby-2.3.0/gems/dry-data-0.4.2/lib/dry/data/struct.rb:34:in `new'
Personally, I think this should just initialize an object with no members. I'm not sure what you intend the behavior will be in this case though. It could also throw a custom error saying you can't create structs with zero attributes.
I'd be happy to contribute a fix once I know how this case should behave.
It would be great to have the built-in File
type in dry-data
and dry-validation
.
Since dry-validation
is a solution for request parameters / form validation, File
a kind of must have type for forms.
@solnic what's your opinion?
class User < Dry::Data::Struct
attribute :name, 'strict.string'
attribute :email # <-- whoops I forgot the "type"
end
blows up with something like this:
/vendor/bundle/gems/dry-data-0.0.1/lib/dry/data.rb:42:in
block in []': undefined method match' for nil:NilClass (NoMethodError)
which is quite ironic since the README states "(NoMethodError: undefined methodsize' for nil:NilClass` anyone?)" =P
I'm aware this lib is not stable yet ;)
Example
require 'dry/types'
class Parent < Dry::Types::Struct
attribute :foo, 'strict.string'
end
class Child < Parent
end
Parent.new(foo: 'bar') # => #<Parent foo="bar">
Parent.new(foo: 1) # !> [Parent.new] 1 (Fixnum) has invalid type for :foo (Dry::Types::StructError)
Parent.new # !> [Parent.new] :foo is missing in Hash input (Dry::Types::StructError)
Child.new(foo: 'bar') # => #<Child>
Child.new(foo: 1).foo # => 1
Child.new # => #<Child>
Child.new.foo # => nil
Right now Struct
s are "strict" by default. They error if you omit a key but they don't error if you supply extra keys. Not exactly my definition of "strict" but close enough.
If I want to specify that a field has a default value though I still have to provide key => nil
for that field if I want to use the strict type. If I want the default to be applied when the key is omitted then I have to specify constructor_type(:schema)
. Now I can omit all keys though! Oh boy.
Out of curiosity I played with the three options to see how they behave in different scenarios:
Description | :strict |
:schema |
:symbolized |
---|---|---|---|
Omitting key for default value | StructError |
Sets default value | Sets default value |
Including extra junk keys | Value silently ignored | Value silently ignored | Value silently ignored |
Providing nil for field with default value | Sets default value | Sets default value | Sets default value |
Providing nil for field without default | StructError |
ConstraintError |
ConstraintError |
Providing invalid type for coercible field | StructError |
TypeError |
TypeError |
Initializing with empty hash | StructError |
values without defaults set to nil |
values without defaults set to nil |
Providing valid input but it string keys | StructError |
all values set to nil |
all values set properly |
So should we add a few more constructor_type
s? I don't think so. A wise man once said
a swiss army knife wonโt ever replace a set of proper tools dedicated for specific tasks. Coercion logic varies, it depends on the context, in a web application there are different coercion rules than in a context of loading data from, letโs say, a relational databases. We canโt just dump this logic into one bucket.
I think that is good advice for this situation. The keys in a hash are just as important and potentially complex in terms of coercion rules as the values.
I think a constructor for a Struct
should be configurable in the following ways:
I should be able to specify...
I should be able to specify these rules for part of a hash or a whole hash.
Thoughts? I have ideas for the implementation but this is already a long post
Hi there,
As a first-time user of dry-types, I'm reporting a 'code smell' that confused me when I ran into it.
I created a TrimmedString type using Strict::String and a custom constructor which trims the supplied input: TrimmedString = Strict::String.constructor { |value| value.strip }
What I expected:
TrimmedString[' Trim me ']
, the supplied String value is trimmed and returnedTrimmedString[3]
, a 'Dry::Types::ConstraintError" is thrown since input is not a StringFor TrimmedString[3]
, this is not what happened. Instead, the code threw an exception because the strip method was not found on the Fixnum 3. This is the code smell: I didn't expect the custom constructor to be called, since the input is not a String (and I specified Strict::String). The fix is easy enough: TrimmedString = Strict::String.constructor { |value| value.kind_of?(::String) ? value.strip : value }
I realise that I will get things wrong since I'm a new user of dry-types, and am piecing things together from blog posts etc (the docs for dry-types latest are not complete). However, this is something that didn't feel intuitively right, and may trip up others.
Thanks for a nifty library!
require 'bundler/setup'
require 'dry-types'
module Types
include Dry::Types.module
TrimmedString = Strict::String.constructor { |value|
# Throws exception: undefined method `strip', when supplied Fixnum
value.strip
# Replace with type-checked line below to stop exception
# value.kind_of?(::String) ? value.strip : value
}
end
puts Types::TrimmedString[' Trim me ']
puts Types::TrimmedString[2]
# Output
Trim me
app/types/wtf.rb:10:in `block in <module:Types>': undefined method `strip' for 2:Fixnum (NoMethodError)
from /home/vagrant/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/bundler/gems/dry-types-ed7a7f605dfa/lib/dry/types/constructor.rb:28:in `[]'
from /home/vagrant/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/bundler/gems/dry-types-ed7a7f605dfa/lib/dry/types/constructor.rb:28:in `call'
from app/types/wtf.rb:18:in `<main>'
One of the problems that I have collided with over the last 10 years of ruby-goodness is incompatible definitions of a point (e.g. location) in the world. Strictly speaking a location is a latitude, longitude, altitude, and a datum such as WGS84. This "type" is sometimes implemented as a class with attributes that intersect the four mentioned with various abbreviations such as lat, lng, long, alt etc.
Sometimes the "type" is implemented as a hash with again the same parade of abbreviations and full attribute names as the keys - sometimes as strings with various cases or symbols also with various cases.
Many times I have had to do one-off conversion methods to convert from one location type to another.
I sense the possibility with Dry::Data::Struct of constructing a family of types that are related - I guess thats why its called a family :)
class Location < Dry::Data::Struct
attribute :latitude, "maybe.coercible.float"
attribute :longitude, "maybe.coercible.float"
attribute :altitude, "maybe.coercible.float"
attrobite :datum, "coercible.string"
end
class AnotherLocation < Location
attribute :lat, renames: :latitude
attribute :long, renames: :longitude
attribute :alt, renames: :altitude
end
class YetAnotherLocation < Location
attribute :lat, renames: :latitude, "coercible.string"
attribute :lng, renames: :longitude, "coercible.string"
end
class AndYetAnotherLocation < Location
attribute :lat, renames: :latitude, "coercible.hms_location"
attribute :lng, renames: :longitude, "coercible.hms_location"
end
class HmsLocation < Dry::Data::Struct
attribute :hours, "maybe.coercible.float"
attribute :minutes, "maybe.coercible.float"
attribute :seconds, "maybe.coercible.float"
end
I think I may have talked myself out of the idea. The idea was that a super class can be the way in which two related sub-classes can be coerced;
Not sure if this is a bad idea or not, but here it is anyway.
message_input = Dry::Types['hash'].schema(
body: 'strict.string',
from_id: 'strict.int',
created_at: Dry::Types['strict.time'].default{ Time.now }
)
Hi there,
Would it be possible for a constrained-type to be its own unique class?
So, for a value
of constrained-type, value.class == constrained-type-class
. constrained-type-class
should be a subclass of the base-class
it constrains.
This is not currently the case. Instead, value.class == base-class
.
Below is an example program showing a constrained-type that would benefit from this change, and a list of motivations for the change.
# Using dry-types head
require 'bundler/setup'
require 'dry-types'
module Types
include Dry::Types.module
# Constrained email type: a string of certain form and size, and normalised
EmailString = Strict::String.constrained(
format: /\A[^\s@]+@([^\s@.]+\.)+([^\s@.]+)\z/u, # Must loosely look like email
max_size: 254 # Maximum possible length of email
).constructor { |value|
value.kind_of?(::String) ? value.strip.downcase : value # Normalisation
}
end
value = Types::EmailString[' [email protected] ']
puts value
puts value.class
# Expected output
# [email protected]
# String
# Preferred output
# [email protected]
# EmailString (which subclasses String)
Motivations for this change:
value.class == constrained-type-class
:
value
has validated qualities (of a certain format, size, ..)value
has been normalised in a particular wayvalue
elsewhere, we don't need to create a new instance of constrained-type[value]
just to be sure it's truly of constrained-type
: we know it is alreadyconstrained-type-class
subclasses base-class
, value
will still function as the base-class
, but a more specialised versionIt looks like we have some issues with kleisli gem. First of all the author didn't release the new gem version that would include much welcome removal of obsolete blank_slate hack. When I tried to add dry-types to my existing rails project this first thing I saw was StackOverflowError. It if very unfriendly error tbh :) After some time of debugging I figured out that the problem was caused by other gem that uses blankslate (without reason, actually). I absolutely don't blame the author at all, it is OSS so he can do whatever he wants.
The second issue is that kleisli adds some pollution to the global environment. E.g. it adds #Maybe
and #Right
methods to object so the become a private members of almost any object in runtime. I think it is against our rules :) Also I Maybe/Either methods a lot and found a tricky problem with using them inside a namespaced blocks of dry-container because NamespaceDSL uses SimpleDelegator that inherits from BasicObject and does not delegate to private methods so I got really unexpected NoMethodError.
Third is that I think we do not need most of kleisli classes. I doubt we need more than Maybe + Either. And from the other side we can add some other stuff to a potential monad/fp dependency.
Finally I think the API of kleisli is quite good but not perfect. Twisting it a bit would be really nice.
I propose to create a minor dry-monad/dry-fp dependency so we can solve all that issues. I quickly made a couple of files (untested :)) that can become a new gem:
Maybe: https://gist.github.com/flash-gordon/a6e6c0956b802d387e2fe81d9b7530b7
Either: https://gist.github.com/flash-gordon/446d38a1c7295e1b9ffcbe1302741522
Any thoughts?
Would be nice to add support for:
Nominal::Symbol
Strict::Symbol
Coercible::Symbol
Hi Piotr,
Since the gem isn't yet available using gem install dry-types
, I cloned the master branch of the git repo, built it locally, and installed from that. After doing a require 'dry-types'
, however, I get this error:
/Users/jg/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/dry-logic-0.1.4/lib/dry/logic/rule_compiler.rb:18:in `visit': undefined method `visit_type?' for #<Dry::Logic::RuleCompiler:0x007f91552edef0> (N
oMethodError)
Should I be using another method for installation?
On every rake test
for my gem (you can see the code here: https://github.com/grempe/tss-rb) I see the following warnings which seems pretty excessive to me.
...
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-configurable-0.1.4/lib/dry/configurable.rb:31: warning: method redefined; discarding old _settings
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-configurable-0.1.4/lib/dry/configurable.rb:31: warning: method redefined; discarding old _settings
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-container-0.3.1/lib/dry/container/mixin.rb:29: warning: method redefined; discarding old _container
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-configurable-0.1.4/lib/dry/configurable.rb:31: warning: method redefined; discarding old _settings
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/options.rb:18: warning: method redefined; discarding old meta
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:62: warning: instance variable @schema not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:56: warning: instance variable @constructor_type not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-configurable-0.1.4/lib/dry/configurable.rb:31: warning: method redefined; discarding old _settings
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-configurable-0.1.4/lib/dry/configurable.rb:31: warning: method redefined; discarding old _settings
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:62: warning: instance variable @schema not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:56: warning: instance variable @constructor_type not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:62: warning: instance variable @schema not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:62: warning: instance variable @schema not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:62: warning: instance variable @schema not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:62: warning: instance variable @schema not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:62: warning: instance variable @schema not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:62: warning: instance variable @schema not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:62: warning: instance variable @schema not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:62: warning: instance variable @schema not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:62: warning: instance variable @schema not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:62: warning: instance variable @schema not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:62: warning: instance variable @schema not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:62: warning: instance variable @schema not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:62: warning: instance variable @schema not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:62: warning: instance variable @schema not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:62: warning: instance variable @schema not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:62: warning: instance variable @schema not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:62: warning: instance variable @schema not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:62: warning: instance variable @schema not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:62: warning: instance variable @schema not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:62: warning: instance variable @schema not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:62: warning: instance variable @schema not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:62: warning: instance variable @schema not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:62: warning: instance variable @schema not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:62: warning: instance variable @schema not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:56: warning: instance variable @constructor_type not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:62: warning: instance variable @schema not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:62: warning: instance variable @schema not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:62: warning: instance variable @schema not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:62: warning: instance variable @schema not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:62: warning: instance variable @schema not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:62: warning: instance variable @schema not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:62: warning: instance variable @schema not initialized
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-types-0.7.1/lib/dry/types/struct.rb:62: warning: instance variable @schema not initialized
...
I think it would be good to allow types to be registered under custom keys, this way we can register under symbol keys and reduce allocations. Would also be good to allow for aliases (i.e. :int
=> :integer
, :bool
=> :boolean
). This should also allow us to get rid of the const_missing
definition and I think the interface will be nicer.
Also, perhaps we should move the registration out of Dry::Data::Struct
and make it explicit (i.e. Dry::Data.register(:name, Type, aliases: [:alias1, :alias2]
).
This should make the library much more flexible, the obvious detriment is that there will be a bit of setup involved. I think it would also be good to make it so that a user can get a container without any of the built in types, and just create Dry::Data
container and register the built-in types.
class Sku < Dry::Types::Struct
attribute :price, Types::Coercible::Decimal
end
p Sku.new(price: 1.2).price
# dry-types-0.6.0/lib/dry/types/constructor.rb:30:in `BigDecimal': can't omit precision for a Float. (ArgumentError)
I think ideally dry-types would convert floats to a string before doing BigDecimal.new(input)
If I create a Struct type and then take the resulting object and convert it to hash again and pass it to the exact same Struct, shouldn't it be valid? Maybe it's very obvious why they shouldn't, but given my very limited experience with very strict functional code it made sense to open the issue.
Consider this:
class MyStruct < Dry::Data::Struct
attribute :a, Types::String
attribute :b, Types::String.optional
end
x = MyStruct.new a: 2, b: nil
#=> {
# :a => 2,
# :b => None
#}
y = MyStruct.new x.to_h
#=> {
# :a => 2,
# :b => Some(None) <----- Shouldn't this simply be None?
#}
I would expect the resulting hash for y
to be equivalent to x
because the meaning on None
should mean the same as nil
in this case. I understand how they are different because nil
is not a "value" but a "wrapper" of no value. However shouldn't the coercion of no value be the same for nil?
This project and related ones like ROM have moved my code to a new level. I appreciate the effort. It's still quite different for me and I'm still getting used to it, so I'd appreciate any assistance in understanding wether this is a bug or not. ;)
I'm not sure if this is a bug or the intended behavior.
type = Dry::Types["string"].constrained(min_size: 5).default("asd")
type.call(nil) # => "asd"
type.call("asd") # => Dry::Types::ConstraintError: "asd" violates constraint
For attempting to set a default value false
to Types::Bool, I am getting constraint violation because of the following line of code:
https://github.com/dry-rb/dry-types/blob/master/lib/dry/types/builder.rb#L26
because false
in there was ignored and block wasn't given. Is this something expected? Or is Bool by default has a default value of false?
So far I worked around it by doing Types::Bool.default { false }
, but having to create a proc does drag the performance down a little bit.
If I have a type like this:
Types::Form::Bool.default(false)
I'll get this error:
NoMethodError: undefined method `default' for #<Dry::Data::SumType:0x007f805b9a51b8>
Should it be possible to allow default values for sum types? Seems like in this case at least it'd make sense.
Let me know what you think and I'd be happy to take a stab at implementing this.
Thanks!
Using gem version 0.7.1
.
I might be misunderstanding |
, but I expected it to work as a fallback if the left coercion fails.
(Types::Form::Date | Types::Strict::Nil)[nil]
Dry::Types::ConstraintError: nil violates constraints (#<struct Dry::Types::Result::Failure input=nil, error="no implicit conversion of nil into String">)
from dry-types-0.7.1/lib/dry/types/sum.rb:31:in `block in call'
from dry-types-0.7.1/lib/dry/types/sum.rb:44:in `try'
from dry-types-0.7.1/lib/dry/types/sum.rb:30:in `call'
nil
(Types::Form::Decimal | Types::Strict::Nil)[nil]
works btw, so it seems to be specific to the Form::Date
type.
I believe the Coercible
types may cause confusion as people may expect complex coercion logic under this category whereas it only provides Kernel
type constructor methods (like Kernel.Integer
etc.).
vendor/bundle/gems/dry-data-0.3.0/lib/dry/data/constraints.rb:1:in
require': cannot load such file -- dry-equalizer (LoadError)`
I have some dry type classes that have defaults. I want to be able to omit keys and have them assume the default value so I've been using constructor_type(:schema)
.
I would expect that no matter what constructor type I use I would still end up with a valid object. When I use the constructor_type(:schema)
, however, I can initialize objects that don't fit the type constraints of the class.
class Foo < Dry::Types::Struct
constructor_type(:schema)
attribute :a, Dry::Types['strict.int']
attribute :b, Dry::Types['strict.int'].default(17)
end
Foo.new(a: 1) # => #<Foo a=1 b=17>
Foo.new(a: nil) # => Dry::Types::ConstraintError: nil violates constraints (type?(Integer) failed)
Foo.new(a: '1') # => Dry::Types::ConstraintError: "1" violates constraints (type?(Integer) failed)
All reasonable^ But then...
Foo.new # => #<Foo a=nil b=17>
Wait, what? a
should not be able to be nil
.
This also gets propagated through other dry types that rely on Foo
.
class Bar < Dry::Types::Struct
attribute :foo, Dry::Types['foo']
end
Bar.new(foo: Foo.new(a: 1)) # => #<Bar foo=#<Foo a=1 b=17>>
Good so far.
Bar.new(foo: nil) # => #<Bar foo=#<Foo thing=nil>>
Not so good...
[:type, ["hash", [:schema, []]]
causes the compiler to crash, it should be allowed to create a hash with empty schema.
I'm confused by the example:
float["3.2"] # "3.2"
I don't see what float
is doing for me, if it's not going to do anything with anything I pass to it. At the very least, I'd expect the default to be for float
to do something, with the option of having it perform a no-op.
joevandyk@84979b0 has the failing test.
Test output:
Failures:
1) Dry::Types::Struct.attribute works on blank structs
Failure/Error: super(constructor[attributes])
NoMethodError:
undefined method `[]' for nil:NilClass
# ./lib/dry/types/struct.rb:48:in `new'
# ./spec/dry/types/struct_spec.rb:97:in `block (3 levels) in <top (required)>'
Finished in 0.09924 seconds (files took 0.92103 seconds to load)
173 examples, 1 failure
Initializing a struct or value object with an existing instance just returns that object:
require 'dry/types'
class Name < Dry::Types::Value
attribute :first, 'strict.string'
attribute :last, 'strict.string'
end
name1 = Name.new(first: 'john', last: 'doe') # => #<Name first="john" last="doe">
name2 = Name.new(name1) # => #<Name first="john" last="doe">
name1.equal?(name2) # => true
name1.first.reverse!
name1.first # => "nhoj"
name2.first # => "nhoj"
I would say that dry-types
should at least freeze each attribute's value. I realize that a Dry::Types::Value
instance itself is frozen but its members should probably also be frozen.
I have class in my project
class PhoneToken::Generate < Dry::Types::Struct
...
end
It works fine until I edit file containing this code and do a request. Rails tries to reload edited file which leads to this error:
Dry::Container::Error - There is already an item registered with the key "phone_token.generate"
The same behavior can be reproduced using reload!
in rails console.
Would it be possible to have optional attributes when using Dry::Data::Struct
? When I try to instantiate an "incomplete" object, I get the strict hash SchemaKey
error (well, the StructError
since that's caught by the Struct
class).
I might not be using this gem right, but some of my models are not "complete" sometimes and it doesn't matter (stuff is lazily loaded later when required.)
A more precise use-case: a MongoDB::Collection
model has the attributes :ns
(string) and :count
(integer). However, sometimes we don't get the count right away due to the fact that it's an extra call to the database.
Would it be desirable to set nil
on the attribute if that hash key is not present on instantiation? It would only work with the optional types, and so would be aligned with the basic premise of this gem.
Falling AST:
ast = [
:type, [
"hash", [
:schema, [
[:key, [:skills, [:type, "array"]]]
]
]
]
]
This fails because visit_array
tries to define a member rule every time, even when they don't exist.
I did some small tests and this seemed to solve the issue:
def visit_array(node)
if node
registry['array'].member(call(node))
else
registry['array']
end
end
But I'm not sure if this would impact other usages of the AST with an array.
This is basically needed for schema validations that are done like that: required(:skills).filled(:array?)
, in which we just want to ensure that the key has an array, but the values inside of it doesn't matter.
Related: dry-rb/dry-validation#170
This relates to dry-rb/dry-validation#102, in which I outlined that there's no simple way to include empty arrays in HTML form submissions. We've been exploring thus further with our formalist-generated forms, and have come to this arrangement:
When a form is expected to submit an array as one of its fields, but that array is empty, then instead replace it with something like this:
<input type="hidden" name="category_ids">
This means that we now get params like this being included in our form submission for an array-type field with nothing included:
{"category_ids" => ""}
Now, if we had a schema like this:
schema = Dry::Validation.Form do
key(:category_ids).each(:int?)
end
When we run it with that input, right now we get an error, because there's no specific form coercion rules for arrays:
schema.("category_ids" => "").messages
# => {:category_ids=>["must be an array"]}
Since the empty string is an HTML form's standard "empty value", I'd like to propose that we add a form coercion for this when we expect the field to be an array. In this case, I'd propose that we actually coerce it to nil
, like we do for other HTML type coercions here, like int
:
def self.to_int(input)
return if empty_str?(input)
# ... rest of coercion logic ...
end
By coercing to nil
, then we can have the flexibility to use a type in our validation schema with a default value (like an empty array), which is what you suggested in dry-rb/dry-validation#102, @solnic.
Would you be happy to have this coercion added to dry-types? I'll go ahead and build it if so.
Thanks!
The installation instructions don't work right now because dry-types
have never been released to rubygems. Only dry-data
so far :)
Related: #21
While working on a more tolerant Dry::Data::Struct
, I experimented with both Dry::Data::Type::Hash#symbolized
and #schema
. Turns out the symbolized
schema will only apply string keys.
hash = Dry::Data::Type::Hash.symbolized(name: Types::String)
hash['name' => 'hello'] # => {:name => 'hello'}
hash[name: 'hello'] # => {}
I understand the documentation is pretty clear about the purpose of symbolized schemas:
Symbolized hash will turn string key names into symbols
but I was wondering if it might be nice to just #to_sym
whatever key passed in (String
, Symbol
) to both the schema creation and type instantiation so something like that would work:
hash = Dry::Data::Type::Hash.symbolized(
name: Types::String,
'age' => Types::Integer
)
hash['name' => 'hello', age: 10] # => {:name => 'hello', :age => 10}
I'd be happy to work on the implementation if that's a desirable feature.
This page makes reference to Types::Form::String
, but that doesn't appear to exist. However, in chatting with @timriley it doesn't appear to just be a bug, as there's mention of a specific behaviour (that ""
would be coerced to nil
). Is this a case of the documentation being out-of-date (or perhaps ahead-of-date) or something else? :)
Currently we use Types::Safe
with Types::Hash::Schema::Symbolized
for input-processors. Unfortunately its current behavior has a major issue - when applying, value coercion results in a type error, the safe type will rescue from that and return original input. This is not acceptable in dry-v context since it results in getting the original input back so we cannot apply validation rules as basic key?
checks fails since we don't even get symbolized keys back.
We need two things:
Type#try
for value coercion and setting original value in the resulting hash if result is a failureRefs dry-rb/dry-validation#131
Refs dry-rb/dry-validation#132
Refs dry-rb/dry-validation#133
Currently we do this here. This is a big mistake since ruby coercion logic in to_i
is weird ie:
irb(main):001:0> "12fafsa".to_i
=> 12
Would be great to fix it using an approach that won't be too slow. Probably applying a regex is the only solution here though.
Refs dry-rb/dry-validation#132
Refs dry-rb/dry-validation#135
It would be convenient if one could define default values in structs, like you can in virtus.
I thought this should work, but it doesn't...
require 'dry-data'
module Types
end
Dry::Data.configure do |config|
config.namespace = Types
end
Dry::Data.finalize
class Book < Dry::Data::Value
attribute :name, Types::Maybe::Strict::String
end
class Shelf < Dry::Data::Value
attribute :books, Types::Strict::Array.member(Book)
end
book1 = Book.new(:name => 'A book name')
book2 = Book.new(:name => 'Another book')
puts Shelf.new(:books => [book1, book2]).inspect
Dry::Data::StructError: [Shelf.new] [#<Book name=Some("A book name")>, #<Book name=Some("Another book")>] (Array) has invalid type for :books
from /usr/lib/ruby/gems/2.3.0/gems/dry-data-0.5.1/lib/dry/data/struct.rb:36:in `rescue in new'
from /usr/lib/ruby/gems/2.3.0/gems/dry-data-0.5.1/lib/dry/data/struct.rb:34:in `new'
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.