Hanami 2.0 Ideas


#41

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) }
end

# spec/spec_helper.rb

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

# 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) }
    end
  end
end

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

#42

@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.


#43

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:



#44

Register options for Hanami::Components

Inspired by dry-containers


#45

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.


#46

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


#47

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


#48

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.

Broadcaster

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

Subscriber

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)
    # ...
  end
end

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 # => { ... }
end

Routing

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 }
end

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

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

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

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

Plan

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.

Links


#49

Remove view escaping dark magic

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

  def date
    "10/10/10"
  end
end

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
end
--- expected
+++ actual
@@ -1,2 +1,2 @@
-# encoding: UTF-8
-"10/10/10"
+# encoding: ASCII-8BIT
+"10&#x2F;10&#x2F;10"

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

Proposal

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.


#50

Get ride of controller callbacks and use Laravel like aproach.

Examples:
Middleware for route groups https://laravel.com/docs/5.6/authentication#protecting-routes, https://laravel.com/docs/5.6/authorization#via-middleware
Model binding https://laravel.com/docs/5.6/routing#route-model-binding


#51

Switching to Roda sounds nice!


#52

freeze object after expose :wink: and make view object immutable.


#53

I’d like to have the ability to override public methods in a repository. See my comment here in an old issue.

I think it’d also be worth adding better support for UUID primary keys. A recent change to the guides is a great start, but I realized after I proposed that change that default orderings will no longer work. Take FooRepository#first for example: it will sort randomly generated UUID's and return the first. That doesn’t make sense with a randomly generated primary key. Maybe this could be handled in the generator, where it could add implementations of #first and #lastthat order(:created_at)?

Lastly, I’d love to see more attention brought to vendored assets. This was a great start but lacks support for vendored assets used across multiple apps. I had to create my own S3/CloudFront-based CDN to vendor these assets.