# Dry::Data ![Join the chat at https://gitter.im/dryrb/chat](https://badges.gitter.im/Join%20Chat.svg)
![Gem Version](https://badge.fury.io/rb/dry-data.svg)
![Build Status](https://travis-ci.org/dryrb/dry-data.svg?branch=master)
![Dependency Status](https://gemnasium.com/dryrb/dry-data.svg)
![Code Climate](https://codeclimate.com/github/dryrb/dry-data/badges/gpa.svg)
![Documentation Status](http://inch-ci.org/github/dryrb/dry-data.svg?branch=master&style=flat)
A simple type-system for Ruby respecting ruby's built-in coercion mechanisms.
## Installation
Add this line to your application's Gemfile:
```ruby
gem 'dry-data'
```
And then execute:
$ bundle
Or install it yourself as:
$ gem install dry-data
## Usage
You can use `dry-data` for defining various data types in your application, like
domain entities and value objects or hashes with coercible values used to handle
params.
Built-in types are grouped under 5 categories:
- default: pass-through without any checks
- `strict` - doesn't coerce and checks the input type against the primitive class
- `coercible` - tries to coerce and raises type-error if it failed
- `form` - non-strict coercion types suitable for form params
- `maybe` - accepts either a nil or something else
### Built-in Type Categories
Coercible types using kernel coercion methods:
- `coercible.string`
- `coercible.int`
- `coercible.float`
- `coercible.decimal`
- `coercible.array`
- `coercible.hash`
Non-coercible:
- `nil`
- `true`
- `false`
- `date`
- `date_time`
- `time`
Form-coercible types:
- `form.date`
- `form.date_time`
- `form.time`
- `form.true`
- `form.false`
- `form.bool`
- `form.int`
- `form.float`
- `form.decimal`
### Accessing Built-in Types
``` ruby
# default passthrough category
float = Dry::Data["float"]
float[3.2] # => 3.2
float["3.2"] # "3.2"
# strict type-check category
int = Dry::Data["strict.int"]
int[1] # => 1
int['1'] # => raises TypeError
# coercible type-check group
string = Dry::Data["coercible.string"]
array = Dry::Data["coercible.array"]
string[:foo] # => 'foo'
array[:foo] # => [:foo]
# form group
date = Dry::Data["form.date"]
date['2015-11-29'] # => #
```
### Optional types
All built-in types have their optional versions too, you can access them under
`"maybe.strict"` and `"maybe.coercible"` categories:
``` ruby
maybe_int = Dry::Data["maybe.strict.int"]
maybe_int[nil] # None
maybe_int[123] # Some(123)
maybe_coercible_float = Dry::Data["maybe.coercible.float"]
maybe_int[nil] # None
maybe_int['12.3'] # Some(12.3)
```
You can define your own optional types too:
``` ruby
maybe_string = Dry::Data["optional"] | Dry::Data["string"]
maybe_string[nil]
# => None
maybe_string[nil].fmap(&:upcase)
# => None
maybe_string['something']
# => Some('something')
maybe_string['something'].fmap(&:upcase)
# => Some('SOMETHING')
maybe_string['something'].fmap(&:upcase).value
# => "SOMETHING"
```
### Defining a hash with explicit schema
The built-in hash type has constructors that you can use to define hashes with
explicit schemas and coercible values using the built-in types.
### Hash Schema
``` ruby
# using simple kernel coercions
hash = Dry::Data['hash'].schema(name: 'string', age: 'coercible.int')
hash[name: 'Jane', age: '21']
# => { :name => "Jane", :age => 21 }
# using form param coercions
hash = Dry::Data['hash'].schema(name: 'string', birthdate: 'form.date')
hash[name: 'Jane', birthdate: '1994-11-11']
# => { :name => "Jane", :birthdate => # }
```
### Strict Hash
Strict hash will raise errors when keys are missing or value types are incorrect.
``` ruby
hash = Dry::Data['hash'].strict(name: 'string', age: 'coercible.int')
hash[email: 'jane@doe.org', name: 'Jane', age: 21]
# => Dry::Data::SchemaKeyError: :email is missing in Hash input
```
### Symbolized Hash
Symbolized hash will turn string key names into symbols
``` ruby
hash = Dry::Data['hash'].symbolized(name: 'string', age: 'coercible.int')
hash['name' => 'Jane', 'age' => '21']
# => { :name => "Jane", :age => 21 }
```
### Defining a struct
You can define struct objects which will have attribute readers for specified
attributes using a simple dsl:
``` ruby
class User < Dry::Data::Struct
attribute :name, "maybe.coercible.string"
attribute :age, "coercible.int"
end
# becomes available like any other type
user_type = Dry::Data["user"]
user = user_type[name: nil, age: '21']
user.name # None
user.age # 21
user = user_type[name: 'Jane', age: '21']
user.name # => Some("Jane")
user.age # => 21
```
## WIP
This is early alpha with a rough plan to:
* Add constrained types (ie a string with a strict length, a number with a strict range etc.)
* Benchmark against other libs and make sure it's fast enough
## Development
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
## Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/dryrb/dry-data.