Hexx::Entities
Base class for domain entities:
Check the documentation at [Read the Docs]
Synopsis
The [entity] describes (immutable) domain objects that have identity.
When descibing an entity you are expected to declare its attributes and validations in the following way:
require "hexx-entities"
class Item < Hexx::Entities::Base
# Defines attributes with their coercers
attribute :foo, coerce: ->(value) { value.to_s }
attribute :bar
# Defines validation for the entity
validate { invalid :blank_foo unless foo }
validate { invalid :blank_bar unless }
end # class Item
item = Item.new foo: 1
item.frozen?
# => true
item.attributes
# => { uuids: ["31a840f4-71e2-4de1-3a6f-04d2103aa2e8"], foo: "1", bar: nil }
item.validate!
# <Attestor::InvalidError> because bar is nil
To model parts of aggregate entities, that have no identity by themselves,
use Hexx::Entitites::Part. The part is just the entity without uuids.
Installation
Add this line to your application's Gemfile:
# Gemfile
gem "hexx-entities"
Then execute:
bundle
Or add it manually:
gem install hexx-entities
Usage
Entity Declaration
Define the entity as a subclass of Hexx::Entities::Base:
class Item < Hexx::Entities::Base
end
Identity and Equality
An entity is identified by its [uuids]. Uuids are either initialized, or assigned by default:
entity = Item.new
entity.uuids # => ["31a840f4-71e2-4de1-3a6f-04d2103aa2e8"]
The entity is equal to any entity that responds to uuids and
has at least one uuid, that is equal to its own:
a = Item.new uuids: [
"31a840f4-71e2-4de1-3a6f-04d2103aa2e8", # this value...
"78340523-8928-1ab0-0a3f-fa9eb07a7986"
]
b = Hexx::Entities::Base.new uuids: [
"9875023e-f483-cef9-ae09-27384015de79",
"31a840f4-71e2-4de1-3a6f-04d2103aa2e8" # ...is equal to this one
]
a == b # => true
Any uuid in the list is an instance of Hexx::Entities::UUID (kind of string).
The instance is validatable:
uuid = Hexx::Entities::UUID.new "9875023e-f483-cef9-ae09-27384015de79"
# => "9875023e-f483-cef9-ae09-27384015de79"
uuid.validate! # passes
uuid = Hexx::Entities::UUID.new "not a valid uuid"
# => "not a valid uuid"
uuid.validate! # <Attestor::InvalidError>
Attributes and their Initializer
The gem uses the eigindir library for attributes definition and coersion.
The uuids attribute is added by default.
All attributes can be initialized from hash:
class Foo < Hexx::Entities::Base
attribute :bar
attribute :baz, coerce: ->(value) { value.to_s }
end
foo = Foo.new bar: :bar, "baz" => :baz
foo.attributes
# => { uuids: ["9875023e-f483-cef9-ae09-27384015de79"], bar: :bar, baz: "baz" }
Validation
The gem uses the attestor library for validation. Any entity is
a validatable object and responds to #validate and #validate! instance
methods.
Validation rules should be defined on class level with .validate helper,
or with the help of externam policy objects by .validates (with s) method:
class Foo < Hexx::Entities::Base
attribute :bar
attribute :baz, coerce: ->(value) { Baz.new value }
validate { invalid :blank_bar unless }
# providing Baz responds to #validate! delegates validations:
validates :baz
end
Any enitiy is invalid when any of its uuids is invalid:
Foo = Class.new Hexx::Entities::Base
foo = Foo.new uuids: ["invalid uuid"]
foo.validate! # <Attestor::InvalidError>
This is the only validation rule that is added to entity by default.
Serialization
Entities can be converted to nested hash by the serialize method.
Any attribute is serialized recursively if it responds to serialize without
arguments:
class Foo < Hexx::Entities::Base
attribute :bar, coerce: ->(value) { value.to_f }
attribute :baz, coerce: ->(value) { value.to_s }
end
class Qux < Hexx::Entities::Base
attribute :foo, coerce: ->(value) { Foo.new value }
attribute :qux
end
foo = Foo.new bar: 1, baz: 1
qux = Qux.new foo: foo
qux.serialize
# {
# uuids: ["78340523-8928-1ab0-0a3f-fa9eb07a7986"],
# foo: {
# uuids: ["31a840f4-71e2-4de1-3a6f-04d2103aa2e8"],
# bar: 1.0,
# baz: "1"
# }
# }
Notice the method is not protected against looping. In cases the later is
possible, you'd better to redefine the serialize method by itself with
custom protection.
Compatibility
Tested under MRI rubies 2.1+. The module heavily uses refinements, not yet supported by Rubinius and JRuby.
Backed on libraries:
Uses RSpec 3.0+ for testing and hexx-suit for dev/test tools collection.
Contributing
- Read the STYLEGUIDE
- 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
See the MIT LICENSE.