Skip to content
Browse files

Updated model guides: overview, entities, and repositories. Introduce…

…d data-types guides article.
  • Loading branch information...
jodosha committed Nov 13, 2016
1 parent 8e2bc25 commit d2868cb99035fa46957eabe45af9a3da34546680
@@ -52,8 +52,9 @@ categories:
- path: models
- path: overview
- path: entities
- path: repositories
- path: entities
- path: data-types
- path: use-your-own-orm
title: Use Your Own ORM
- path: migrations
@@ -0,0 +1,89 @@
title: Guides - Data Types

# Data Types

Data types are available for custom [entities schema](/guides/models/entities#custom-schema), which is isn't mandatory to use.

We have 5 data types:

* **Definition** - base type definition
* **Strict** - strict type with primitive type check
* **Coercible** - type with constructor that applies a coercion to given input
* **Form** - type with constructor that applies a non-strict coercion, specific to HTTP params
* **JSON** - type with constructor that applies a non-strict coercion, specific to JSON

## Definition

* `Types::Nil`
* `Types::String`
* `Types::Symbol`
* `Types::Int`
* `Types::Float`
* `Types::Decimal`
* `Types::Class`
* `Types::Bool`
* `Types::True`
* `Types::False`
* `Types::Date`
* `Types::DateTime`
* `Types::Time`
* `Types::Array`
* `Types::Hash`

## Strict

* `Types::Strict::Nil`
* `Types::Strict::String`
* `Types::Strict::Symbol`
* `Types::Strict::Int`
* `Types::Strict::Float`
* `Types::Strict::Decimal`
* `Types::Strict::Class`
* `Types::Strict::Bool`
* `Types::Strict::True`
* `Types::Strict::False`
* `Types::Strict::Date`
* `Types::Strict::DateTime`
* `Types::Strict::Time`
* `Types::Strict::Array`
* `Types::Strict::Hash`

## Coercible

* `Types::Coercible::String`
* `Types::Coercible::Int`
* `Types::Coercible::Float`
* `Types::Coercible::Decimal`
* `Types::Coercible::Array`
* `Types::Coercible::Hash`

## Form

* `Types::Form::Nil`
* `Types::Form::Int`
* `Types::Form::Float`
* `Types::Form::Decimal`
* `Types::Form::Bool`
* `Types::Form::True`
* `Types::Form::False`
* `Types::Form::Date`
* `Types::Form::DateTime`
* `Types::Form::Time`
* `Types::Form::Array`
* `Types::Form::Hash`


* `Types::Json::Nil`
* `Types::Json::Decimal`
* `Types::Json::Date`
* `Types::Json::DateTime`
* `Types::Json::Time`
* `Types::Json::Array`
* `Types::Json::Hash`


Hanami model data types are based on `dry-types` gem. Learn more at: [](
@@ -4,9 +4,10 @@ title: Guides - Entities

# Entities

An _entity_ is an object that is defined by its identity. See ["Domain Driven Design" by Eric Evans.](
An entity is model domain object that is defined by its identity.
See "Domain Driven Design" by Eric Evans.

An entity is the core of an application, where the part of the domain logic is implemented.
An entity is at the core of an application, where the part of the domain logic is implemented.
It's a small, cohesive object that expresses coherent and meaningful behaviors.

It deals with one and only one responsibility that is pertinent to the
@@ -16,61 +17,163 @@ or validations.
This simplicity of design allows developers to focus on behaviors, or
message passing if you will, which is the quintessence of Object Oriented Programming.

All the entities live under `lib/` directory of our application.
## Entity Schema

## Interface
Internally, an entity holds a schema of the attributes, made of their names and types.
The role of a schema is to whitelist the data used during the initialization, and to enforce data integrity via coercions or exceptions.

We'll see concrete examples in a second.

### Automatic Schema

When using a SQL database, this is derived automatically from the table definition.

Imagine to have the `books` table defined as:

CREATE TABLE `books` (
`title` varchar(255),
`created_at` timestamp,
`updated_at` timestamp

This is the corresponding entity `Book`.

# lib/bookshelf/entities/book.rb
class Book
include Hanami::Entity
attributes :title
class Book < Hanami::Entity

When a class includes `Hanami::Entity` it receives the following interface:

Let's instantiate it with proper values:

book = "Hanami")
book.title # => "Hanami"
book.created_at # => nil

The `created_at` attribute is `nil` because it wasn't present when we have instantiated `book`.


It ignores unknown attributes:

book = "value")
book.unknown # => NoMethodError # => NoMethodError

It raises a `NoMethodError` both for `unknown` and `foo`, because they aren't part of the internal schema.


It can coerce values:

book = "Sun, 13 Nov 2016 09:41:09 GMT")
book.created_at # => 2016-11-13 09:41:09 UTC
book.class # => Time

* `#id`
* `#id=`
* `#initialize(attributes = {})`
An entity tries as much as it cans to coerce values according to the internal schema.

`Hanami::Entity` also provides the `.attributes` for defining attribute accessors for the given names.

If we expand the code above in **pure Ruby**, it would be:
It enforces **data integrity** via exceptions:

class Book
attr_accessor :id, :title "foo") # => ArgumentError

If we use this feature, in combination with [database constraints](/guides/migrations/create-table#constraints) and validations, we can guarantee a **strong** level of **data integrity** for our projects.

### Custom Schema

def initialize(attributes = {})
@id, @title = attributes.values_at(:id, :title)
We can take data integrity a step further: we can **optionally** define our own entity internal schema.

<p class="notice">
Custom schema is <strong>optional</strong> for SQL databases, while it's mandatory for entities without a database table, or while using with a non-SQL database.

# lib/bookshelf/entities/user.rb
class User < Hanami::Entity
attributes do
attribute :id, Types::Int
attribute :name, Types::String
attribute :email, Types::String.constrained(format: EMAIL_FORMAT)
attribute :age, Types::Int.constrained(gt: 18)
attribute :codes, Types::Collection(Types::Coercible::Int)
attribute :comments, Types::Collection(Comment)
attribute :created_at, Types::Time
attribute :updated_at, Types::Time

**Hanami::Model** ships `Hanami::Entity` for developers's convenience.
Let's instantiate it with proper values:

**Hanami::Model** depends on a narrow and well-defined interface for an Entity - `#id`, `#id=`, `#initialize(attributes={})`.
If your object implements that interface then that object can be used as an Entity in the **Hanami::Model** framework.
user = "Luca", age: 34, email: "test@hanami.test")
However, we suggest to implement this interface by including `Hanami::Entity`, in case that future versions of the framework will expand it. # => "Luca"
user.age # => 34 # => "luca@hanami.test" # => nil
user.comments # => nil

See [Dependency Inversion Principle]( for more on interfaces.

## Inheritance
It can coerce values:

When a class extends a `Hanami::Entity` class, it will also *inherit* its mother's attributes.
user = ["123", "456"]) # => [123, 456]

Other entities can be passed as concrete instance:

class Book
include Hanami::Entity
attributes :title
user = [ "cool")])
# => [#<Comment:0x007f966be20c58 @attributes={:text=>"cool"}>]

class NonFictionBook < Book
attributes :price
Or as data:

user = [{text: "cool"}])
# => [#<Comment:0x007f966b689e40 @attributes={:text=>"cool"}>]


It enforces **data integrity** via exceptions:

```ruby "foo") # => TypeError: "foo" (String) has invalid type for :email [:foo]) # => TypeError: :foo must be coercible into Comment

That is, `NonFictionBook`'s attributes carry over `:title` attribute from `Book`,
thus is `:id, :title, :price`.

<p class="warning">
Custom schema <strong>takes precedence</strong> over automatic schema. If we use custom schema, we're need to add manually all the new columns from the corresponding SQL database table.


Learn more about data types in the [dedicated article](/guides/models/data-types).

0 comments on commit d2868cb

Please sign in to comment.
You can’t perform that action at this time.