Hello wonderful community!
This hot summer ☀ has some fresh news brought to you by Hanami 🌸 and its cool 😎 new features. 🍉
Today we're happy to announce v1.1.0.beta1
release 🙌 , with the stable release (v1.1.0
) scheduled for October 2017.
Between now and then, we'll release other beta and release candidate versions.
Features
So what's new and exiciting in the Hanami world?
New repository associations
We added new useful associations. 🎉 Let's quickly see them in action.
Many-to-One (belongs_to
)
class BookRepository < Hanami::Repository
associations do
belongs_to :author
end
def find_with_author(id)
aggregate(:author).where(id: id).map_to(Book).one
end
end
One-to-one (has_one
)
class UserRepository < Hanami::Repository
associations do
has_one :avatar
end
def find_with_avatar(id)
aggregate(:avatar).where(id: id).map_to(User).one
end
def create_with_avatar(data)
assoc(:avatar).create(data)
end
def remove_avatar(user)
assoc(:avatar, user).delete
end
def add_avatar(user, data)
assoc(:avatar, user).add(data)
end
def update_avatar(user, data)
assoc(:avatar, user).update(data)
end
def replace_avatar(user, data)
assoc(:avatar, user).replace(data)
end
end
Many-to-many (has_many :through
)
class AuthorRepository < Hanami::Repository
associations do
has_many :books
has_many :reviews, through: :books
end
def find_with_reviews(id)
aggregate(:reviews).where(authors__id: id).map_to(Author).one
end
def top_reviews_for(author)
assoc(:reviews, author).where("usefulness_count > 10").to_a
end
end
Rewritten CLI
We have rewritten from scratch our CLI, by replacing thor
with a new gem hanami-cli
.
Despite the name, hanami-cli
is not about Hanami commands (eg. hanami server
), but instead it's a general purpose Command Line Interface (CLI) framework for Ruby.
We worked with dry-rb team to ship this new gem, which is be used by Hanami, and it will be used soon by dry-rb, ROM and Trailblazer to build their CLI too.
Thanks to hanami-cli
we built a new CLI architecture that allows third-party developers to integrate with Hanami CLI.
Let's say we want to build a fictional gem hanami-webpack
.
require "hanami/cli/commands"
module Hanami
module Webpack
module CLI
module Commands
class Configuration < Hanami::CLI::Commands::Command
desc "Generate Webpack configuration"
def call(*)
# generate configuration
end
end
end
end
end
end
Hanami::CLI.register "generate webpack", Hanami::Webpack::CLI::Commands::Configuration
When a developer adds hanami-webpack
to their Gemfile
, then the command is available.
$ bundle exec hanami generate
Commands:
hanami generate action APP ACTION # Generate an action for app
hanami generate app APP # Generate an app
hanami generate mailer MAILER # Generate a mailer
hanami generate migration MIGRATION # Generate a migration
hanami generate model MODEL # Generate a model
hanami generate secret [APP] # Generate session secret
hanami generate webpack # Generate Webpack configuration
Extra behaviors for entity manual schema
Entities by default infer their schema (set of attributes) from the corresponding database table.
For instance, if people
table has id
, name
, created_at
, and updated_at
columns, then Person
will have the same attributes.
It may happen that you're not happy with this inferring, and you want to customize the schema. We call it this feature "manual schema".
It was introduced with Hanami 1.0 and this is how it works:
class Person < Hanami::Entity
attributes do
attribute :id, Types::Int
attribute :name, Types::String
end
end
Person.new
=> #<Person:0x007ff859e34118 @attributes={}>
Person.new(id: 1)
# => #<Person:0x007ff85acbcfc8 @attributes={:id=>1}>
Person.new(id: "1")
# => #<Person:0x007ff85a04d558 @attributes={:id=>"1"}>
Person.new(id: 1, name: "Luca")
# => #<Person:0x007ff85ab20200 @attributes={:id=>1, :name=>"Luca"}>
Person.new(id: 1, name: "Luca", foo: "bar")
# => #<Person:0x007ff859e44ea0 @attributes={:id=>1, :name=>"Luca"}>
Person.new(foo: "bar")
# => #<Person:0x007ff859e4d1e0 @attributes={}>
With Hanami 1.1, you can expand the behavior of your manual schema. Do you want a stricter policy for entity initialization? You got it!
class Person < Hanami::Entity
attributes :strict do
attribute :id, Types::Strict::Int
attribute :name, Types::Strict::String
end
end
Person.new
# => ArgumentError: :id is missing in Hash input
Person.new(id: 1)
# => ArgumentError: :name is missing in Hash input
Person.new(id: 1, name: "Luca")
# => #<Person:0x007f8476816c88 @attributes={:id=>1, :name=>"Luca"}>
Person.new(id: 1, name: "Luca", foo: "bar")
# => ArgumentError: unexpected keys [:foo] in Hash input
Person.new(foo: "bar")
# => ArgumentError: unexpected keys [:foo] in Hash input
Person.new(id: "1", name: "Luca")
# => TypeError: "1" (String) has invalid type for :id violates constraints (type?(Integer, "1") failed)
Selectively boot apps
With Hanami you can build your project by following the Monolith-First principle. You add more and more code to the project, but growing it organically, by using several Hanami apps.
There are cases of real world products using a dozen of Hanami apps in the same project (eg web
for the frontend, admin
for the administration, etc..)
They deploy the project on several servers, by booting only a subset of these apps.
So the servers A, B, and C are for customers (web
application), D is for administration (admin
application), while E, and F are for API (api
application)
To serve this purpose we introduced selective booting feature.
# config/environment.rb
# ...
Hanami.configure do
if Hanami.app?(:api)
require_relative '../apps/api/application'
mount Api::Application, at: '/api'
end
if Hanami.app?(:admin)
require_relative '../apps/admin/application'
mount Api::Application, at: '/admin'
end
if Hanami.app?(:web)
require_relative '../apps/web/application'
mount Api::Application, at: '/'
end
end
Then from the CLI, you use the HANAMI_APPS
env var.
$ HANAMI_APPS=web,api bundle exec hanami server
With the command above we start only web
and api
applications.
Logger filtering
With this release, we automatically log the payload from non-GET HTTP requests. When a user submits a form, all the fields and their values will appear in the log:
[bookshelf] [INFO] [2017-08-11 18:17:54 +0200] HTTP/1.1 POST 302 ::1 /signup 5 {"signup"=>{"username"=>"jodosha", "password"=>"secret", "password_confirmation"=>"secret", "bio"=>"lorem"}} 0.00593
To avoid sensitive informations to be logged, you can filter them:
# config/environment.rb
# ...
Hanami.configure do
# ...
environment :development do
logger level: :debug, filter: %w[password password_confirmation]
end
end
Now the output will be:
[bookshelf] [INFO] [2017-08-11 18:17:54 +0200] HTTP/1.1 POST 302 ::1 /signup 5 {"signup"=>{"username"=>"jodosha", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]", "bio"=>"lorem"}} 0.00593
It also supports fine grained patterns to disambiguate params with the same name. For instance, we have a billing form with street number and credit card number, and we want only to filter the credit card:
# config/environment.rb
# ...
Hanami.configure do
# ...
environment :development do
logger level: :debug, filter: %w[credit_card.number]
end
end
[bookshelf] [INFO] [2017-08-11 18:43:04 +0200] HTTP/1.1 PATCH 200 ::1 /billing 2 {"billing"=>{"name"=>"Luca", "address"=>{"street"=>"Centocelle", "number"=>"23", "city"=>"Rome"}, "credit_card"=>{"number"=>"[FILTERED]"}}} 0.009782
Note that billing => address => number
wasn't filtered while billing => credit_card => number
was filtered instead.
Minor Changes
For the entire list of changes, please have a look at our CHANGELOG and features list.
Released Gems
hanami-1.1.0.beta1
hanami-model-1.1.0.beta1
hanami-assets-1.1.0.beta1
hanami-cli-0.1.0.beta1
hanami-mailer-1.1.0.beta1
hanami-helpers-1.1.0.beta1
hanami-view-1.1.0.beta1
hamami-controller-1.1.0.beta1
hanami-router-1.1.0.beta1
hanami-validations-1.1.0.beta1
hanami-utils-1.1.0.beta1
Contributors
We're grateful for each person who contributed to this release. These lovely people are:
- Alfonso Uceda
- Anton Davydov
- Bartosz Bonisławski
- Ben Johnson
- Cecile Veneziani
- Daniel Amireh
- David Dymko
- Dmitriy Ivliev
- Ferdinand Niedermann
- Gabriel Gizotti
- Gernot Poetsch
- Hélio Costa e Silva
- Jaymie Jones
- John Hager
- Kai Kuchenbecker
- Karolis Mažukna
- Koichi ITO
- Luca Guidi
- Lucas Hosseini
- Marcello Rocha
- Marion Duprey
- Marion Schleifer
- Maurizio De Magnis
- Nick Pridorozhko
- Nikita Shilnikov
- Oana Sipos
- Paweł Świątkowski
- Radan Skorić
- Rogério Zambon
- Sean Collins
- Semyon Pupkov
- Sergey Sein
- Tim Riley
- Tudor Pavel
- akhramov
- autopp
- chenge
- derekpovah
- jarosluv
- mbajur
- milovidov
- morrme
- ryu39
- sovetnik
- yjukaku
How to try it
gem install hanami --pre
hanami new bookshelf
What's next?
We'll release the stable release on October 2017, in the meantime, please try this beta and report issues. Happy coding! 🌸