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:

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! 🌸