Hanami 2.0 Ideas


Container for application

We can create the global container for all hanami application. It will be super useful for initializers, some service objects, etc. In my mind I’m thinking about something like this:

# config/initializers/redis.rb

AppName::Container.register :redis do
  ConnectionPool.new(size: 10, timeout: 3) { Redis.new(host: 'localhost', port: 6379) }

# spec/spec_helper.rb

AppName::Container.register :redis do
  ConnectionPool.new(size: 10, timeout: 3) { MockRedis.new }

# in action, view, repository, etc

module Web::Controllers::Contributors
  class Show
    include Web::Action

    def call(params)
      user = container[:redis].with { |c| c.get(:key) }

In this example container[:redis] is not a part of hanami. Developer should define this container for his application by self.

Why containers will good for developers:

  1. you can set special container for each envs
  2. you will have general way for working with shared code
  3. hanami-gems developers will have the general way to share gem code in hanami app

Ideas where you can use it now

  1. container['interactors.interactor_name']
  2. Any code in /config/initialization/ folder (for example I haveMarkdown` class for converting md to html)
  3. some api clients without global configuration (twitter = Twitter.new(config); twitter.tweet('text'))
  4. any services from lib/ folder


@davydovanton I agree and I already outlined this proposal (see Hanami 2.0 Ideas, “IoC” section).

We already have a working implementation: Hanami::Components, which works very well. It’s a matter of enhance it a bit and mark as Public API. The plan is to rename it too.


GraphQL plugin for hanami

I’m not sure about this idea but I think we can create hanami-graphql plugin for graphql endpoint in hanami. This is a just idea and I’ll be happy to discuss it.

Some links:


Register options for Hanami::Components

Inspired by dry-containers


As followup on the CLI state: we implemented hanami-cli gem from scratch. It’s a replacement for thor, and it can be used to build CLIs with Ruby.

Based on that gem, we rewrote from scratch hanami CLI (aka all the hanami server commands et all). https://github.com/hanami/hanami/pull/808

It cuts down the maintenance burden for us, and it allows third-party developers to add their own generators and CLI commands.

This feature will be available as preview from August 2017 with hanami-1.1.0.beta1.


I see GraphQL as third-party gem, not in the core of the framework.


yep, only third-party gem without changing core lib :+1:


After discussion with @jodosha, we decided that this library should look like another gem.
Also, we think that hanami-events should use different adapters and be without a global state.

Why different adapters?

I think hanami events should be a library for building Event-Driven Architecture. In this case, a library should work with different hanami project instances:

In this case, we can provide different transport layers for a event storage like:

  • Memory
  • Postgres
  • Kafka
  • Kinesis
  • etc

And that’s why we need to replace wispers gem as an adapter.

From which hanami events should be?

I think that we need to split all logic to 3 different parts:

  1. Broadcaster
  2. Subscriber
  3. Routing

All these parts should be without a global state. And here my ideas about API for all this.


events = Hanami::Events.new(:adapter)
events.broadcast('user.created', user: user)


kafka_events = Hanami::Events.new(:kafka)
memory_events = Hanami::Events.new(:memory)

memory_events.subscribe('user.signup', Mailer)

# or in class
class Signup
  inclide Hanami::Events::Subscriber
  subscribe_to memory_events, 'user.signup'
  subscribe_to kafka_events, 'user.signup'

  def call(payload)
    # ...

Also, I think we should have an ability for subscribing to all events:

kafka_events = Hanami::Events.new(:kafka)

kafka_events.listen do |event|
  event.name    # => 'user.signup'
  event.payload # => { ... }


Routing should route all events and call specific code for each one. It will be helpful for manage all events in one place (look like HTTP routers):

class WebHandler < Hanami::Events::Handler
  on('user.created') { |payload| UserRepository.new.create(payload) }
  on('user.updated') { |payload| payload }

class AnaliticHandler < Hanami::Events::Handler
  on('*', AnaliticSender)
  on('user.*') { |payload| payload }
  on('user.signup') { |payload| payload }

class NotificationHandler < Hanami::Events::Handler
  on('user.created') { |payload| payload }
  on('post.created') { |payload| payload }

class EventRouting < Hanami::Events::Routing
  mount UserHandler, Hanami.app?(:user_events) # load specific handlers for instance
  mount AnaliticHandler
  mount NotificationHandler

events = Hanami::Events.new(:adapter)
events.listen { |event| EventRouter.new.resolve(event) }


We will start work on proof of concept for this idea. It’s mean that we will implement only broadcaster and simple subscriber without routing and other features.



Remove view escaping dark magic

class Web::Views::Foo::Index
  include Web::View

  def date

def test_view_method
  view = Web::Views::Foo::Index.new('apps/web/templates/foo/index.html.slim', Hash[])
  assert_equal "10/10/10", view.date
--- expected
+++ actual
@@ -1,2 +1,2 @@
-# encoding: UTF-8
+# encoding: ASCII-8BIT

This is because of this :mage:

Template engines

  • slim: escapes by default (== to unescape)
  • haml with :escape_html option: escapes by default (!= to unescape)
  • erb: doesn’t escape


Stop using ERB to parse .erb templates, instead, use Erubi, which has an :escape option, turning <%= to escaped and <%== to unescaped, thus rendering the :mage: useless for all 3 main template file formats.