エンティティ

エンティティは、そのアイデンティティによって定義されるモデルドメインオブジェクトです。Eric Evansの “Domain Driven Design”を参照してください。エンティティは、ドメインロジックの一部が実装されているアプリケーションのコアです。それは、一貫性のある意味のある行動を表現する、小さくまとまったオブジェクトです。

永続性や検証などの詳細を気にすることなく、アプリケーションのドメインに関連する唯一の責任を扱います。

この単純な設計により、開発者は振る舞いやメッセージの受け渡しに集中することができます。これはオブジェクト指向プログラミングの本質です。

 

エンティティスキーマ

内部的には、エンティティは名前と型からなる属性のスキーマを保持しています。スキーマの役割は、初期化中に使用されたデータをホワイトリストに登録し、強制または例外を介してデータの整合性を強制することです。

具体的な例を少し見ていきます。

 

自動スキーマ

SQLデータベースを使用する場合、これはテーブル定義から自動的に導出されます。

していると想像してbooks、テーブルには次のように定義されます。

CREATE TABLE books (
    id integer NOT NULL,
    title text,
    created_at timestamp without time zone,
    updated_at timestamp without time zone
);

これは対応するエンティティBookです。

# lib/bookshelf/entities/book.rb
class Book < Hanami::Entity
end

適切な値でインスタンス化しましょう:

book = Book.new(title: "Hanami")

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

このcreated_at属性はnil、インスタンス化したときに存在しなかったためですbook


未知の属性を無視します。

book = Book.new(unknown: "value")

book.unknown # => NoMethodError
book.foo     # => NoMethodError

and は、内部スキーマの一部ではないため、とのNoMethodError両方を生成unknownfooます。


それは値を強制することができます:

book = Book.new(created_at: "Sun, 13 Nov 2016 09:41:09 GMT")

book.created_at       # => 2016-11-13 09:41:09 UTC
book.created_at.class # => Time

エンティティは、内部スキーマに従って値を強制するためにできる限り試行します。


これは、強制データの整合性を例外経由:

Book.new(created_at: "foo") # => ArgumentError

我々は、この機能を使用する場合は、との組み合わせで、データベースの制約および検証、我々は保証することができ、強いのレベルのデータの整合性を私たちのプロジェクトのために。

 

カスタムスキーマ

私たちは、さらに一歩、データの整合性を取ることができます:我々はできる、必要に応じて私たち自身のエンティティの内部スキーマを定義します。

カスタムスキーマはSQL データベースではオプションですが、データベーステーブルを持たないエンティティや非SQLデータベースでは必須です。

# 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

適切な値でインスタンス化しましょう:

user = User.new(name: "Luca", age: 34, email: "test@hanami.test")

user.name     # => "Luca"
user.age      # => 34
user.email    # => "luca@hanami.test"
user.codes    # => nil
user.comments # => nil

それは値を強制することができます:

user = User.new(codes: ["123", "456"])
user.codes # => [123, 456]

他のエンティティを具体的なインスタンスとして渡すことができます:

user = User.new(comments: [Comment.new(text: "cool")])
user.comments
  # => [#"cool"}>]

またはデータとして:

user = User.new(comments: [{text: "cool"}])
user.comments
  # => [#"cool"}>]

これは、強制データの整合性を例外経由:

User.new(email: "foo")     # => TypeError: "foo" (String) has invalid type for :email
User.new(comments: [:foo]) # => TypeError: :foo must be coercible into Comment

カスタムスキーマが自動スキーマよりも優先されます。カスタムスキーマを使用する場合は、対応するSQLデータベーステーブルからすべての新しい列を手動で追加する必要があります。


専用の記事でデータ型の詳細を学んでください。