hanami / model

Ruby persistence framework with entities and repositories
http://hanamirb.org
MIT License
445 stars 152 forks source link
database entity entity-framework hanami persistence persistence-framework persistence-layer repository repository-pattern ruby

Hanami::Model (deprecated)

Important notice

NOTE: Hanami::Model was the persistence layer for Hanami 1.x. This library will not receive any updates.

For the persistence layer for Hanami 2.x, please see hanami/db

Contact

Rubies

Hanami::Model supports Hanami 1.x only, and Ruby (MRI) 2.6 and 2.7.

Installation

Add this line to your application's Gemfile:

gem 'hanami-model'

And then execute:

$ bundle

Or install it yourself as:

$ gem install hanami-model

Usage

This class provides a DSL to configure the connection.

require 'hanami/model'
require 'hanami/model/sql'

class User < Hanami::Entity
end

class UserRepository < Hanami::Repository
end

Hanami::Model.configure do
  adapter :sql, 'postgres://username:password@localhost/bookshelf'
end.load!

repository = UserRepository.new
user       = repository.create(name: 'Luca')

puts user.id # => 1

found = repository.find(user.id)
found == user # => true

updated = repository.update(user.id, age: 34)
updated.age # => 34

repository.delete(user.id)

Concepts

Entities

A 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. 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 domain of the application, without caring about details such as persistence 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.

require 'hanami/model'

class Person < Hanami::Entity
end

Repositories

An object that mediates between entities and the persistence layer. It offers a standardized API to query and execute commands on a database.

A repository is storage independent, all the queries and commands are delegated to the current adapter.

This architecture has several advantages:

When a class inherits from Hanami::Repository, it will receive the following interface:

A relation is a homogenous set of records. It corresponds to a table for a SQL database or to a MongoDB collection.

All the queries are private. This decision forces developers to define intention revealing API, instead of leaking storage API details outside of a repository.

Look at the following code:

ArticleRepository.new.where(author_id: 23).order(:published_at).limit(8)

This is bad for a variety of reasons:

There is a better way:

require 'hanami/model'

class ArticleRepository < Hanami::Repository
  def most_recent_by_author(author, limit: 8)
    articles.where(author_id: author.id).
      order(:published_at).
      limit(limit)
  end
end

This is a huge improvement, because:

Mapping

Hanami::Model can automap columns from relations and entities attributes.

When using a sql adapter, you must require hanami/model/sql before Hanami::Model.load! is called so the relations are loaded correctly.

However, there are cases where columns and attribute names do not match (mainly legacy databases).

require 'hanami/model'

class UserRepository < Hanami::Repository
  self.relation = :t_user_archive

  mapping do
    attribute :id,   from: :i_user_id
    attribute :name, from: :s_name
    attribute :age,  from: :i_age
  end
end

NOTE: This feature should be used only when automapping fails because of the naming mismatch.

Conventions

Thread safety

Hanami::Model's is thread safe during the runtime, but it isn't during the loading process. The mapper compiles some code internally, so be sure to safely load it before your application starts.

Mutex.new.synchronize do
  Hanami::Model.load!
end

This is not necessary when Hanami::Model is used within a Hanami application.

Features

Timestamps

If an entity has the following accessors: :created_at and :updated_at, they will be automatically updated when the entity is persisted.

require 'hanami/model'
require 'hanami/model/sql'

class User < Hanami::Entity
end

class UserRepository < Hanami::Repository
end

Hanami::Model.configure do
  adapter :sql, 'postgresql://localhost/bookshelf'
end.load!

repository = UserRepository.new

user = repository.create(name: 'Luca')

puts user.created_at.to_s # => "2016-09-19 13:40:13 UTC"
puts user.updated_at.to_s # => "2016-09-19 13:40:13 UTC"

sleep 3
user = repository.update(user.id, age: 34)
puts user.created_at.to_s # => "2016-09-19 13:40:13 UTC"
puts user.updated_at.to_s # => "2016-09-19 13:40:16 UTC"

Configuration

Logging

In order to log database operations, you can configure a logger:

Hanami::Model.configure do
  # ...
  logger "log/development.log", level: :debug
end

It accepts the following arguments:

Versioning

Hanami::Model uses Semantic Versioning 2.0.0

Contributing

  1. Fork it ( https://github.com/hanami/model/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

Copyright

Copyright © 2014-2021 Luca Guidi – Released under MIT License

This project was formerly known as Lotus (lotus-model).