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:

  • 0.1.0

"2.2.0"

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



533
534
535
536
537
538
# File 'lib/hanami/view.rb', line 533

def initialize
  self.class.config.finalize!
  ensure_config

  @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



522
523
524
# File 'lib/hanami/view.rb', line 522

def self.cache
  Cache
end

.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



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

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



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

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 decorated by a matching Part and 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 true)

    • :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 decorated by a matching Part and 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 entirey, 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 true)

    • :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 decorated by a matching Part and 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 true)

    • :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 decorated by matching Parts and 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 true)

    • :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



396
397
398
399
400
401
402
403
404
# File 'lib/hanami/view.rb', line 396

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



420
421
422
# File 'lib/hanami/view.rb', line 420

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



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

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



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

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



277
278
279
280
281
282
283
# File 'lib/hanami/view.rb', line 277

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



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

setting :layout, default: false

.layout_path(layout) ⇒ 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



516
517
518
# File 'lib/hanami/view.rb', line 516

def self.layout_path(layout)
  File.join(*[config.layouts_dir, layout].compact)
end

.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



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

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



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

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



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

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



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

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



71
72
73
# File 'lib/hanami/view.rb', line 71

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

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

See Also:

Since:

  • 2.1.0



410
411
412
# File 'lib/hanami/view.rb', line 410

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



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

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



252
253
254
# File 'lib/hanami/view.rb', line 252

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



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

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



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

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



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

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



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

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



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

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



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

setting :template_inference_base

Instance Method Details

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

Renders the view.

Parameters:

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

    template format to use

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

    context object to use

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

    layout name, or false to indicate no layout

  • input

    input data for preparing exposure values

Returns:

Since:

  • 2.1.0



569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
# File 'lib/hanami/view.rb', line 569

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

  locals = locals(rendering, input)
  output = rendering.template(config.template, rendering.scope(config.scope, locals))

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

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

#configObject

Returns the view’s configuration.

Since:

  • 2.1.0



544
545
546
# File 'lib/hanami/view.rb', line 544

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



554
555
556
# File 'lib/hanami/view.rb', line 554

def exposures
  @exposures
end

#rendering(format: config.default_format, context: config.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



587
588
589
# File 'lib/hanami/view.rb', line 587

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