Comments (7)
My subclassing suggestion wasn't really about custom classes, and more about the constrained semantics of a (primitive) value being fully communicated to the rest of a system's code via the type system (using a subclass).
dry-types Struct/Value classes look useful for representing composite data types of multiple attributes. The examples on the Struct & Value docs page are all composite data types. I've just created my first Struct class to represent a Message composed of an 'email' string and a 'content' string.
But if I'm understanding this correctly, a constrained-type represents a single primitive value. And the constraints (in the form of default, enum, format, max_size, etc) ensure that the value can only be drawn from a subset of the wider primitive type. But the explicitly-constrained nature of this value is not manifestly visible in the type system if Strict::String.primitive == String
. That's why I suggested the subclassing idea.
For example, if we had a LowercaseString constrained-type that lowercases all inputs, then it seems questionable that a LowercaseString value just has class String, not LowercaseString < String. Without additional information, anyone consuming such a value would think that "HeLLO" was a possible value when it's not; only "hello" is.
The reason I suggested the subclassing idea is drawn from a concrete example. I'm refactoring a JSON-api webapp, and want to separate input-validation (using dry-validation) from domain object construction (using dry-types). Once the inputs have been schema-validated and coercively typed, they should be in their final/immutable form; the input hash can then be used to construct a dry-types Struct domain object. Ideally I would like:
- Input-validation coerces input attributes into a hash of primitive types and constrained primitive types, with complete type information (e.g. LowercaseString).
- The Struct Domain object constructor consumes this hash, and uses the correctly typed attributes as is. There is no need to reconstruct them, as the attributes already have the desired types.
From debugging my code, this isn't happening right now. In the LowercaseString constrained-type example:
- During input validation, with a schema declaring 'word' attribute of LowercaseString:
{ "word": "HELLO" } becomes { :word: "hello" } (with type String)
. - During domain object construction, with a Struct declaration wanting the same 'word' attribute of LowercaseString: the already-validated hash of
{ :word: "hello" } is regenerated again as { :word: "hello" } (type String)
.
I find this lacking in two ways, which would be remedied by the subclassing idea (or something like it):
- Domain object construction has to reconstruct 'hello' attribute all over again using the LowercaseString constrained-type. This would be unnecessary if the attribute was declared of type LowercaseString; it would already pass type?(LowercaseString)
- Any code consuming the domain_object.word has no indication (through the type system) that the attribute is not just a String, but a lowercase String.
My two cents, and thanks for such a cool library :-)
from dry-types.
You can do this in your constructor:
require 'bundler/inline'
gemfile(true) { gem 'dry-types', github: 'dry-rb/dry-types' }
class EmailString < String
alias to_s dup
end
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) ? ::EmailString.new(value.strip.downcase) : value
}
end
value = Types::EmailString[' [email protected] ']
# => "[email protected]"
value
# => "[email protected]"
value.class
# => EmailString
from dry-types.
Thanks - that's both cool and hacky!
The constructed value now has type EmailString
subclassing String
. That's great.
But it does feel weird that there's both an EmailString
class, and a Types::EmailString
'pseudo-class'.
from dry-types.
We make a distinction between a dry-type and its primitive class, so Strict::String.primitive == String
etc. dry types are pretty much object constructors with optional constraints and possible additional behaviors (like default value, enum etc.). If you want custom classes you can use Struct
or Value
.
from dry-types.
@greghuc this is a very interesting suggestion, we could make some use of it. It's unlikely we'll something like this in 1.0 but this way of tagging values via subtyping can be fruitful in future. This may be tricky a bit now since we have AST representation of types but possible nevertheless. Thanks for the insight, I'll close this one because I'm grooming the backlog, further discussions can be continued on the discussion forum.
from dry-types.
I think this feature would be great. Just wondering if there has been any further discussion about it since 2018. I would love to contribute to get it done!
from dry-types.
@sadhu89 hey there was no progress because there was no follow up discussion on the forum. Feel free to re-start this thread on the forum and we can take it from there.
from dry-types.
Related Issues (20)
- Include `Types::Params::String` HOT 8
- Type#fallback to mock previous implementation of Type#default HOT 10
- Constructor is not applied properly to the result of types Sum HOT 5
- Types::Params::Integer incorrect coercion for numeric values HOT 9
- Types::Params::*.optional not handling empty string HOT 4
- Anonymous Dry::Types based module cannot be extended HOT 4
- Params namespace disapearing HOT 9
- Params coercion for Array works in an unintuitive way HOT 1
- https://dry-rb.org/gems/dry-types/ not at latest version HOT 1
- [Security] Workflow ci.yml is using vulnerable action actions/checkout
- Compound Types with Examples HOT 1
- Dry::Container::Error: There is already an item registered with the key "nominal.string" on jruby-head HOT 4
- Future-proof local override for Types::Instance/Constructor classes that potentially collide with predicates
- Unexpected error raised from Sum type HOT 7
- Types::Coeercible::Integer, should this use Base 10? HOT 2
- Add Zeitwerk autoloader
- Using transform_keys breaks error handling
- Undefined method after upgrading dependencies. HOT 5
- Missing method implementation for `Enum.each_value` HOT 1
- Types.Constructor failing on Ruby 3.1 HOT 8
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from dry-types.