Skip to content
Permalink
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
pages:
- 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`

## JSON

* `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: [http://dry-rb.org/gems/dry-types](http://dry-rb.org/gems/dry-types)
@@ -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.](http://youtube.com/watch?v=7MaYeudL9yo)
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:

```sql
CREATE TABLE `books` (
`id` integer NOT NULL PRIMARY KEY AUTOINCREMENT,
`title` varchar(255),
`created_at` timestamp,
`updated_at` timestamp
);
```

This is the corresponding entity `Book`.

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

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

Let's instantiate it with proper values:

```ruby
book = Book.new(title: "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:

```ruby
book = Book.new(unknown: "value")
book.unknown # => NoMethodError
book.foo # => NoMethodError
```

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

---

It can coerce values:

```ruby
book = Book.new(created_at: "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:

```ruby
class Book
attr_accessor :id, :title
Book.new(created_at: "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.
</p>

```ruby
# lib/bookshelf/entities/user.rb
class User < Hanami::Entity
EMAIL_FORMAT = /\@/
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
end
end
```

**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.
```ruby
user = User.new(name: "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.
user.name # => "Luca"
user.age # => 34
user.email # => "luca@hanami.test"
user.codes # => nil
user.comments # => nil
```

See [Dependency Inversion Principle](http://en.wikipedia.org/wiki/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.
```ruby
user = User.new(codes: ["123", "456"])
user.codes # => [123, 456]
```

Other entities can be passed as concrete instance:

```ruby
class Book
include Hanami::Entity
attributes :title
end
user = User.new(comments: [Comment.new(text: "cool")])
user.comments
# => [#<Comment:0x007f966be20c58 @attributes={:text=>"cool"}>]
```

class NonFictionBook < Book
attributes :price
end
Or as data:

```ruby
user = User.new(comments: [{text: "cool"}])
user.comments
# => [#<Comment:0x007f966b689e40 @attributes={:text=>"cool"}>]
```

---

It enforces **data integrity** via exceptions:

```ruby
User.new(email: "foo") # => TypeError: "foo" (String) has invalid type for :email
User.new(comments: [: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.
</p>

---

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.