Class: Hanami::View Abstract

Inherits:
Object show all
Extended by:
Dry::Configurable
Defined in:
lib/hanami/view.rb,
lib/hanami/view/html.rb,
lib/hanami/view/part.rb,
lib/hanami/view/path.rb,
lib/hanami/view/tilt.rb,
lib/hanami/view/cache.rb,
lib/hanami/view/scope.rb,
lib/hanami/view/errors.rb,
lib/hanami/view/context.rb,
lib/hanami/view/version.rb,
lib/hanami/view/exposure.rb,
lib/hanami/view/rendered.rb,
lib/hanami/view/renderer.rb,
lib/hanami/view/exposures.rb,
lib/hanami/view/rendering.rb,
lib/hanami/view/erb/engine.rb,
lib/hanami/view/erb/parser.rb,
lib/hanami/view/erb/template.rb,
lib/hanami/view/part_builder.rb,
lib/hanami/view/scope_builder.rb,
lib/hanami/view/erb/filters/block.rb,
lib/hanami/view/rendering_missing.rb,
lib/hanami/view/tilt/haml_adapter.rb,
lib/hanami/view/tilt/slim_adapter.rb,
lib/hanami/view/helpers/tag_helper.rb,
lib/hanami/view/decorated_attributes.rb,
lib/hanami/view/erb/filters/trimming.rb,
lib/hanami/view/helpers/escape_helper.rb,
lib/hanami/view/html_safe_string_buffer.rb,
lib/hanami/view/helpers/tag_helper/tag_builder.rb,
lib/hanami/view/helpers/number_formatting_helper.rb

Overview

This class is abstract.

Subclass this and provide your own configuration and exposures to define your own view (along with a custom ‘#initialize` if you wish to inject dependencies into your subclass)

A standalone, template-based view rendering system that offers everything you need to write well-factored view code.

This represents a single view, holding the configuration and exposures necessary for rendering its template.

Since:

  • 2.1.0

Defined Under Namespace

Modules: DecoratedAttributes, ERB, HTML, Helpers, Tilt Classes: Cache, Context, Error, Exposure, Exposures, HTMLSafeStringBuffer, Part, PartBuilder, Path, Rendered, Renderer, Rendering, RenderingMissing, RenderingMissingError, Scope, ScopeBuilder, TemplateNotFoundError, UndefinedConfigError

Constant Summary collapse

DEFAULT_RENDERER_OPTIONS =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Since:

  • 2.1.0

{default_encoding: "utf-8"}.freeze
VERSION =

Since:

  • 2.1.0

"3.0.0.rc1"

Configuration collapse

Exposures collapse

Scope collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeView

Returns an instance of the view. This binds the defined exposures to the view instance.

Subclasses can define their own ‘#initialize` to accept injected dependencies, but must call `super()` to ensure the standard view initialization can proceed.

Since:

  • 2.1.0



551
552
553
554
555
556
557
# File 'lib/hanami/view.rb', line 551

def initialize
  self.class.config.finalize!
  ensure_config

  @config_data = config.to_data
  @exposures = self.class.exposures.bind(self)
end

Class Method Details

.cacheObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 2.1.0



540
541
542
# File 'lib/hanami/view.rb', line 540

def self.cache
  Cache
end

.decorate(*names, **options, &block) ⇒ Object

Defines an exposure that will be decorated with a matching Part.

This is a shorthand for ‘expose(…, decorate: true)`.

See Also:

Since:

  • 2.1.0



436
437
438
# File 'lib/hanami/view.rb', line 436

def self.decorate(*names, **options, &block)
  expose(*names, **options, decorate: true, &block)
end

.config.decorate_exposures=(value) ⇒ Object

Controls whether exposures are decorated by default.

When set to ‘true`, all exposures will be decorated with matching Parts unless explicitly marked with `decorate: false`.

When set to ‘false` (the default), exposures will not be decorated unless explicitly marked with `decorate: true`, or declared with `decorate`.

Defaults to ‘false`.

Parameters:

  • value (Boolean)

    whether to decorate exposures by default

Since:

  • x.x.x

Since:

  • 2.1.0



251
# File 'lib/hanami/view.rb', line 251

setting :decorate_exposures, default: false

.config.default_context=(context) ⇒ Object

Set the default context object to use when rendering. This will be used unless another context object is applied at render-time to ‘View#call`

Defaults to a frozen instance of ‘Hanami::View::Context`.

Parameters:

See Also:

Since:

  • 2.1.0

Since:

  • 2.1.0



150
# File 'lib/hanami/view.rb', line 150

setting :default_context, default: Context.new.freeze

.config.default_format=(format) ⇒ Object

Set the default format to use when rendering.

Defaults to ‘:html`.

Parameters:

  • format (Symbol)

Since:

  • 2.1.0

Since:

  • 2.1.0



161
# File 'lib/hanami/view.rb', line 161

setting :default_format, default: :html

.expose(name, **options, &block) ⇒ Object .expose(name, **options) ⇒ Object .expose(name, **options) ⇒ Object .expose(*names, **options) ⇒ Object

Overloads:

  • .expose(name, **options, &block) ⇒ Object

    Define a value to be passed to the template. The return value of the block will be passed to the template.

    The block will be evaluated with the view instance as its ‘self`. The block’s parameters will determine what it is given:

    • To receive other exposure values, provide positional parameters matching the exposure names. These exposures will already by decorated by their Parts.

    • To receive the view’s input arguments (whatever is passed to ‘View#call`), provide matching keyword parameters. You can provide default values for these parameters to make the corresponding input keys optional

    • To receive the Context object, provide a ‘context:` keyword parameter

    • To receive the view’s input arguments in their entirety, provide a keywords splat parameter (i.e. ‘**input`)

    Examples:

    Accessing input arguments

    expose :article do |slug:|
      article_repo.find_by_slug(slug)
    end

    Accessing other exposures

    expose :articles do
      article_repo.listing
    end
    
    expose :featured_articles do |articles|
      articles.select(&:featured?)
    end

    Parameters:

    • name (Symbol)

      name for the exposure

    • options (Hash)

      the exposure’s options

    Options Hash (**options):

    • :layout (Boolean)

      expose this value to the layout (defaults to false)

    • :decorate (Boolean)

      decorate this value in a matching Part (defaults to false, or the value of ‘config.decorate_exposures`)

    • :as (Symbol, Class)

      an alternative name or class to use when finding a matching Part

  • .expose(name, **options) ⇒ Object

    Define a value to be passed to the template, provided by an instance method matching the name. The method’s return value will be passed to the template.

    The method’s parameters will determine what it is given:

    • To receive other exposure values, provide positional parameters matching the exposure names. These exposures will already by decorated by their Parts.

    • To receive the view’s input arguments (whatever is passed to ‘View#call`), provide matching keyword parameters. You can provide default values for these parameters to make the corresponding input keys optional

    • To receive the Context object, provide a ‘context:` keyword parameter

    • To receive the view’s input arguments in their entirety, provide a keywords splat parameter (i.e. ‘**input`)

    Examples:

    Accessing input arguments

    expose :article
    
    def article(slug:)
      article_repo.find_by_slug(slug)
    end

    Accessing other exposures

    expose :articles
    expose :featured_articles
    
    def articles
      article_repo.listing
    end
    
    def featured_articles
      articles.select(&:featured?)
    end

    Parameters:

    • name (Symbol)

      name for the exposure

    • options (Hash)

      the exposure’s options

    Options Hash (**options):

    • :layout (Boolean)

      expose this value to the layout (defaults to false)

    • :decorate (Boolean)

      decorate this value in a matching Part (defaults to false, or the value of ‘config.decorate_exposures`)

    • :as (Symbol, Class)

      an alternative name or class to use when finding a matching Part

  • .expose(name, **options) ⇒ Object

    Define a single value to pass through from the input data (when there is no instance method matching the ‘name`). This value will be passed to the template.

    Parameters:

    • name (Symbol)

      name for the exposure

    • options (Hash)

      the exposure’s options

    Options Hash (**options):

    • :layout (Boolean)

      expose this value to the layout (defaults to false)

    • :decorate (Boolean)

      decorate this value in a matching Part (defaults to false, or the value of ‘config.decorate_exposures`)

    • :as (Symbol, Class)

      an alternative name or class to use when finding a matching Part

    • :default (Boolean)

      a default value to provide if there is no matching input data

  • .expose(*names, **options) ⇒ Object

    Define multiple values to pass through from the input data (when there is no instance methods matching their names). These values will be passed through to the template.

    The provided options will be applied to all the exposures.

    Parameters:

    • names (Symbol)

      names for the exposures

    • options (Hash)

      the exposure’s options

    Options Hash (**options):

    • :layout (Boolean)

      expose this value to the layout (defaults to false)

    • :decorate (Boolean)

      decorate this value in a matching Part (defaults to false, or the value of ‘config.decorate_exposures`)

    • :as (Symbol, Class)

      an alternative name or class to use when finding a matching Part

    • :default (Boolean)

      a default value to provide if there is no matching input data

See Also:

Since:

  • 2.1.0



410
411
412
413
414
415
416
417
418
# File 'lib/hanami/view.rb', line 410

def self.expose(*names, **options, &block)
  if names.length == 1
    exposures.add(names.first, block, **options)
  else
    names.each do |name|
      exposures.add(name, **options)
    end
  end
end

.exposuresExposures

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the defined exposures. These are unbound, since bound exposures are only created when initializing a View instance.

Returns:

Since:

  • 2.1.0



445
446
447
# File 'lib/hanami/view.rb', line 445

def self.exposures
  @exposures ||= Exposures.new
end

.gem_loaderObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 2.1.0



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/hanami/view.rb', line 25

def self.gem_loader
  @gem_loader ||= Zeitwerk::Loader.new.tap do |loader|
    root = File.expand_path("..", __dir__)
    loader.tag = "hanami-view"
    loader.push_dir(root)
    loader.ignore(
      "#{root}/hanami-view.rb",
      "#{root}/hanami/view/version.rb",
      "#{root}/hanami/view/errors.rb"
    )
    loader.inflector = Zeitwerk::GemInflector.new("#{root}/hanami-view.rb")
    loader.inflector.inflect(
      "erb" => "ERB",
      "html" => "HTML",
      "html_safe_string_buffer" => "HTMLSafeStringBuffer"
    )
  end
end

.config.inflector=(inflector) ⇒ Object

Set an inflector to provide to the part_builder and scope_builder.

Defaults to ‘Dry::Inflector.new`.

Parameters:

  • inflector

Since:

  • 2.1.0

Since:

  • 2.1.0



234
# File 'lib/hanami/view.rb', line 234

setting :inflector, default: Dry::Inflector.new

.inherited(klass) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 2.1.0



291
292
293
294
295
296
297
# File 'lib/hanami/view.rb', line 291

def self.inherited(klass)
  super

  exposures.each do |name, exposure|
    klass.exposures.import(name, exposure)
  end
end

.config.layout=(name) ⇒ Object

Set the name of the layout to render templates within. Layouts will be looked up within the configured ‘layouts_dir`, within the configured `paths`.

A false or nil value will use no layout. Defaults to ‘nil`.

Parameters:

  • name (String, FalseClass, nil)

    layout name, or false to indicate no layout

Since:

  • 2.1.0

Since:

  • 2.1.0



112
# File 'lib/hanami/view.rb', line 112

setting :layout, default: false

.config.layouts_dir=(dir) ⇒ Object

Set the name of the directory (within the configured ‘paths`) holding the layouts. Defaults to `“layouts”`

Parameters:

  • dir (String)

    directory name

Since:

  • 2.1.0

Since:

  • 2.1.0



122
# File 'lib/hanami/view.rb', line 122

setting :layouts_dir, default: "layouts"

.config.part_builder=(part_builder) ⇒ Object

Set a custom part builder class

Parameters:

  • part_builder (Class)

Since:

  • 2.1.0

Since:

  • 2.1.0



191
# File 'lib/hanami/view.rb', line 191

setting :part_builder, default: PartBuilder

.config.part_class=(part_class) ⇒ Object

Set a custom default part class.

Parameters:

  • part_class (Class)

Since:

  • 2.1.0

Since:

  • 2.1.0



170
# File 'lib/hanami/view.rb', line 170

setting :part_class, default: Part

.config.scope_namespace=(namespace) ⇒ Object

Set a namespace that will be searched when building scope classes.

Parameters:

  • namespace (Module, Class)

See Also:

Since:

  • 2.1.0

Since:

  • 2.1.0



182
# File 'lib/hanami/view.rb', line 182

setting :part_namespace

.config.paths=(paths) ⇒ Object

Set an array of directories that will be searched for all templates (templates, partials, and layouts).

These will be converted into Path objects and used for template lookup when rendering.

This is a **required setting**.

Parameters:

Since:

  • 2.1.0

Since:

  • 2.1.0



69
70
71
# File 'lib/hanami/view.rb', line 69

setting :paths, constructor: -> paths {
  Array(paths).map { |path| Path[path] }
}

.private_expose(*names, **options, &block) ⇒ Object

See Also:

Since:

  • 2.1.0



424
425
426
# File 'lib/hanami/view.rb', line 424

def self.private_expose(*names, **options, &block)
  expose(*names, **options, private: true, &block)
end

.config.renderer_engine_mapping=(mapping) ⇒ Object

A hash specifying the (Tilt-compatible) template engine class to use for a given format. Template engine detection is automatic based on format; use this setting only if you want to force a non-preferred engine.

Examples:

config.renderer_engine_mapping = {erb: Tilt::ErubiTemplate}

Parameters:

  • mapping (Hash<Symbol, Class>)

    engine mapping

See Also:

Since:

  • 2.1.0

Since:

  • 2.1.0



286
# File 'lib/hanami/view.rb', line 286

setting :renderer_engine_mapping, default: {}

.config.renderer_options=(options) ⇒ Object

A hash of options to pass to the template engine. Template engines are provided by Tilt; see Tilt’s documentation for what options your template engine may support.

Defaults to ‘“utf-8”`. Any options passed will be merged onto the defaults.

Parameters:

  • options (Hash)

    renderer options

See Also:

Since:

  • 2.1.0

Since:

  • 2.1.0



267
268
269
# File 'lib/hanami/view.rb', line 267

setting :renderer_options, default: DEFAULT_RENDERER_OPTIONS, constructor: -> options {
  DEFAULT_RENDERER_OPTIONS.merge(options.to_h).freeze
}

.scope(scope_class = nil, &block) ⇒ Object

Creates and assigns a scope for the current view.

The newly created scope is useful to add custom logic that is specific to the view.

The scope has access to locals, exposures, and inherited scope (if any)

If the view already has an explicit scope the newly created scope will inherit from the explicit scope.

There are two cases when this may happen:

1. The scope was explicitly assigned (e.g. `config.scope = MyScope`)
2. The scope has been inherited by the view superclass

If the view doesn’t have an already existing scope, the newly scope will inherit from ‘Hanami::View::Scope` by default.

However, you can specify any base class for it. This is not recommended, unless you know what you’re doing.

Examples:

Basic usage

class MyView < Hanami::View
  config.scope = MyScope

  scope do
    def greeting
      _locals[:message].upcase + "!"
    end

    def copyright(time)
      "Copy #{time.year}"
    end
  end
end

# my_view.html.erb
# <%= greeting %>
# <%= copyright(Time.now.utc) %>

MyView.new.(message: "Hello") # => "HELLO!"

Inherited scope

class MyScope < Hanami::View::Scope
  private

  def shout(string)
    string.upcase + "!"
  end
end

class MyView < Hanami::View
  config.scope = MyScope

  scope do
    def greeting
      shout(_locals[:message])
    end

    def copyright(time)
      "Copy #{time.year}"
    end
  end
end

# my_view.html.erb
# <%= greeting %>
# <%= copyright(Time.now.utc) %>

MyView.new.call(message: "Hello") # => "HELLO!"

Parameters:

  • scope (Hanami::View::Scope)

    the current scope (if any), or the default base class will be ‘Hanami::View::Scope`

  • block (Proc)

    the scope logic definition

Since:

  • 2.1.0



136
# File 'lib/hanami/view.rb', line 136

setting :scope

.config.scope_builder=(scope_builder) ⇒ Object

Set a custom scope builder class

Parameters:

  • scope_builder (Class)

See Also:

Since:

  • 2.1.0

Since:

  • 2.1.0



223
# File 'lib/hanami/view.rb', line 223

setting :scope_builder, default: ScopeBuilder

.config.scope_class=(scope_class) ⇒ Object

Set a custom default scope class.

Parameters:

  • scope_class (Class)

Since:

  • 2.1.0

Since:

  • 2.1.0



200
# File 'lib/hanami/view.rb', line 200

setting :scope_class, default: Scope

.config.scope_namespace=(namespace) ⇒ Object

Set a namespace that will be searched when building scope classes.

Parameters:

  • namespace (Module, Class)

See Also:

Since:

  • 2.1.0

Since:

  • 2.1.0



212
# File 'lib/hanami/view.rb', line 212

setting :scope_namespace

.config.template=(name) ⇒ Object

Set the name of the template for rendering this view. Template name should be relative to the configured ‘paths`.

This is a **required setting**.

Parameters:

  • name (String)

    template name

Since:

  • 2.1.0

Since:

  • 2.1.0



83
# File 'lib/hanami/view.rb', line 83

setting :template

.config.template_inference_base=(base_path) ⇒ Object

Set the base path to strip away when when inferring a view’s template names from its class name.

**This setting only applies for views within an Hanami application.**

For example, given a view ‘Main::Views::Articles::Index`, in the `Main` slice, and a template_inference_base of “views”, the inferred template name will be “articles/index”.

Parameters:

  • base_path (String, nil)

    base templates path

Since:

  • 2.1.0

Since:

  • 2.1.0



99
# File 'lib/hanami/view.rb', line 99

setting :template_inference_base

Instance Method Details

#call(format: config_data.default_format, context: config_data.default_context, layout: config_data.layout, **input) ⇒ Rendered

Renders the view.

Parameters:

  • format (Symbol) (defaults to: config_data.default_format)

    template format to use

  • context (Context) (defaults to: config_data.default_context)

    context object to use

  • layout (String, FalseClass, nil) (defaults to: config_data.layout)

    layout name, or false to indicate no layout

  • input

    input data for preparing exposure values

Returns:

Since:

  • 2.1.0



587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
# File 'lib/hanami/view.rb', line 587

def call(format: config_data.default_format,
         context: config_data.default_context,
         layout: config_data.layout,
         **input)
  rendering = self.rendering(format: format, context: context)
  scope_class = config_data.scope

  locals = locals(rendering, input)
  output = rendering.template(config_data.template, rendering.scope(scope_class, locals))

  if layout
    output = rendering.template(
      layout_path(layout),
      rendering.scope(scope_class, layout_locals(locals))
    ) { output }
  end

  Rendered.new(output: output, locals: locals)
end

#configObject

Returns the view’s configuration.

Since:

  • 2.1.0



563
564
565
# File 'lib/hanami/view.rb', line 563

def config
  self.class.config
end

#exposuresExposures

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the view’s bound exposures.

Returns:

Since:

  • 2.1.0



572
573
574
# File 'lib/hanami/view.rb', line 572

def exposures # rubocop:disable Style/TrivialAccessors
  @exposures
end

#rendering(format: config_data.default_format, context: config_data.default_context) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 2.1.0



608
609
610
# File 'lib/hanami/view.rb', line 608

def rendering(format: config_data.default_format, context: config_data.default_context)
  Rendering.new(config_data:, format:, context:)
end