Announcing Hanami v1.1.0.beta1
Luca Guidi
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)
associations do
belongs_to :author
end
aggregate(:author).where(id: id).map_to(Book).one
end
end
One-to-one (has_one)
associations do
has_one :avatar
end
aggregate(:avatar).where(id: id).map_to(User).one
end
assoc(:avatar).create(data)
end
assoc(:avatar, user).delete
end
assoc(:avatar, user).add(data)
end
assoc(:avatar, user).update(data)
end
assoc(:avatar, user).replace(data)
end
end
Many-to-many (has_many :through)
associations do
has_many :books
has_many :reviews, through: :books
end
aggregate(:reviews).where(authors__id: id).map_to(Author).one
end
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.
desc "Generate Webpack configuration"
# 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:
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!
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)
mount Api::Application, at: '/api'
end
if Hanami.app?(:admin)
mount Api::Application, at: '/admin'
end
if Hanami.app?(:web)
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.beta1hanami-model-1.1.0.beta1hanami-assets-1.1.0.beta1hanami-cli-0.1.0.beta1hanami-mailer-1.1.0.beta1hanami-helpers-1.1.0.beta1hanami-view-1.1.0.beta1hamami-controller-1.1.0.beta1hanami-router-1.1.0.beta1hanami-validations-1.1.0.beta1hanami-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! 🌸