V2.2: Context


When each view is rendered, a single context object provides access to common facilities from across the app, making these available to every template, partial, part and scope in that rendering.

These facilities are exposed via methods on the context object. For example, the app’s inflector is returned from the context’s #inflector method.

In templates and scopes, you can call these methods implicitly, without an explicit receiver:

<%= inflector.pluralize("koala") %>

In parts and helpers, you can access the context as context (as well as its alias _context, which may be used in in case of a name conflict on the value wrapped by a part). In these places, access the context methods by calling them on context directly:

# app/views/parts/post.rb

class Post < Bookshelf::Views::Part
  def koalas
    context.inflector.pluralize("koala")
  end
end

Standard context

The standard context in Hanami apps includes the following methods:

  • #inflector: the app’s configured inflector
  • #routes: the app’s named routes helpers, via #path and #url methods
  • #content_for: a method to get and set content strings to be shared across templates, such as defining a page title (applied in the layout) from a template
  • #assets: low-level access to front end assets (but you should prefer the assets helpers)

For views rendered from an action, the following methods are also available:

  • #request: the current request
  • #session: the session for the current request
  • #flash: flash messages from the previous request

You can access all these context methods in templates, partials, parts, helpers and scopes using the approaches outlined above.

Customizing the standard context

You customize Hanami’s standard context by defining your own class named Views::Context within your app or slice:

# app/views/context.rb

# auto_register: false

module Bookshelf
  module Views
    class Context < Hanami::View::Context
    end
  end
end

Here you can define your own methods that you wish to make available across all aspects of views. These can be completely independent, or they can work with the methods provided by the standard context.

For example, you could define a predicate method returning true when a given path matches the current request:

# app/views/context.rb

# auto_register: false

module Bookshelf
  module Views
    class Context < Hanami::View::Context
      def current_path?(path)
        request.fullpath == path
      end
    end
  end
end

The context class is compatible with the Deps mixin, so you can also include your own dependencies as required:

# app/views/context.rb

# auto_register: false

module Bookshelf
  module Views
    class Context < Hanami::View::Context
      include Deps["repos.user_repo"]

      def current_user
        return nil unless session["user_id"]

        @current_user ||= user_repo.get(session["user_id"])
      end
    end
  end
end

The context object will have #dup called once at the beginning of each rendering. As such, if your customizations to the context include any mutable variables (like arrays or hashes), you should ensure these are also duped via your own #initialize_copy:

# app/views/context.rb

# auto_register: false

module Bookshelf
  module Views
    class Context < Hanami::View::Context
      include Deps["repos.user_repo"]

      def initialize(*)
        super
        # Imagine your context exposes methods that modify this hash
        @my_hash = []
      end

      def initialize_copy(source)
        super
        @my_hash = source.instance_variable_get(:@my_hash).dup
      end
    end
  end
end

Decorating context attributes

Your custom context may have attributes that you want deocrated as parts. To do this, declare these attributes using decorate in your context class:

# app/views/context.rb

# auto_register: false

module Bookshelf
  module Views
    class Context < Hanami::View::Context
      # Return the current_user as a Bookshelf::Views::Parts::User
      decorate :current_user, as: :user

      def current_user
        # ...
      end
    end
  end
end

Providing an alternative context object

When rendering your views, you may choose to provide an alternative context object. To do this, pass it via context: when rendering views directly:

my_view.call(context: my_alternative_context)

Or when rendering in actions:

def handle(request, response)
  # ...
  responder.render(view, context: my_alternative_context)
end

You may also opt out of the Hanami standard context entirely by configuring a view’s default_context. To learn more, see Configuration.