Evil::Struct
Nested structure with type constraints, based on the dry-initializer DSL.
Installation
Add this line to your application's Gemfile:
gem 'evil-struct'
And then execute:
$ bundle
Or install it yourself as:
$ gem install evil-struct
Synopsis
The structure is like dry-struct except for it controls optional attributes and default values aside of type constraints.
Its DSL is taken from dry-initializer. Its method attribute
is just an alias for option
.
require "evil-struct"
require "dry-types"
class Product < Evil::Struct
attribute :title
attribute :price, Dry::Types["coercible.float"]
attribute :quantity, Dry::Types["coercible.int"], default: proc { 0 }
# shared options
attributes optional: true do
attribute :subtitle
attribute :description
end
end
# Accepts both symbolic and string keys.
# Class methods `[]`, `call`, and `load` are just aliases for `new`
product = Product[title: "apple", "price" => "10.9", description: "a fruit"]
# Attributes are available via methods or `[]`
product.title # => "apple"
product[:price] # => 10.9
product["quantity"] # => 0
product.description # => "a fruit"
# unassigned value differs from `nil`
product.subtitle # => Dry::Initializer::UNDEFINED
# Raises in case a mandatory value not assigned
Product.new # BOOM! because neither title, nor price are assigned
# Hashifies all attributes except for undefined subtitle
# You can use `dump` as an alias for `to_h`
product.to_h
# => { title: "apple", price: 10.9, description: "a fruit", quantity: 0 }
# The structure is comparable to any object that responds to `#to_h`
product == { title: "apple", price: 10.9, description: "a fruit", quantity: 0 }
# => true
The structure is designed for immutability. That's why it doesn't contain writers (but you can define them by yourself via attr_writer
).
Instead of mutating current instance, you can merge another hash to the object via merge
or deep_merge
.
new_product = product.merge(title: "orange")
new_product.class # => Product
new_product.to_h
# => { title: "orange", price: 10.9, description: "a fruit", quantity: 0 }
# you can merge any object that responds to `to_h` or `to_hash`
other = Product[title: "orange", price: 12]
new_product = product.merge(other)
new_product.to_h
# => { title: "orange", price: 12, description: "a fruit", quantity: 0 }
# merge_deeply (deep_merge) gracefully merge nested hashes or hashified objects
grape = Product.new title: "grape",
price: 30,
description: { country: "FR", year: 2016, sort: "Merlot" }
new_grape = grape.merge_deeply description: { year: 2017 }
new_grape.to_h
# => {
# title: "grape",
# price: 30,
# description: { country: "FR", year: 2017, sort: "Merlot" }
# }
Compatibility
Tested under rubies compatible to MRI 2.2+.
Contributing
- Fork the project
- Create your feature branch (
git checkout -b my-new-feature
) - Add tests for it
- Commit your changes (
git commit -am '[UPDATE] Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create a new Pull Request
License
The gem is available as open source under the terms of the MIT License.