class Hanami::View
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.
@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)
@api public @since 2.1.0
Constants
- DEFAULT_RENDERER_OPTIONS
-
@api private @since 2.1.0
- VERSION
-
@api public @since 0.1.0
Public Class Methods
Source
# File lib/hanami/view.rb, line 522 def self.cache Cache end
@api private @since 2.1.0
Source
# 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
@overload expose(name, **options, &block)
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`) @example Accessing input arguments expose :article do |slug:| article_repo.find_by_slug(slug) end @example Accessing other exposures expose :articles do article_repo.listing end expose :featured_articles do |articles| articles.select(&:featured?) end @param name [Symbol] name for the exposure @macro exposure_options
@overload expose(name, **options)
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`) @example Accessing input arguments expose :article def article(slug:) article_repo.find_by_slug(slug) end @example Accessing other exposures expose :articles expose :featured_articles def articles article_repo.listing end def featured_articles articles.select(&:featured?) end @param name [Symbol] name for the exposure @macro exposure_options
@overload expose(name, **options)
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. @param name [Symbol] name for the exposure @macro exposure_options @option options [Boolean] :default a default value to provide if there is no matching input data
@overload expose(*names, **options)
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. @param names [Symbol] names for the exposures @macro exposure_options @option options [Boolean] :default a default value to provide if there is no matching input data
@see dry-rb.org/gems/dry-view/exposures/
@api public @since 2.1.0
Source
# File lib/hanami/view.rb, line 420 def self.exposures @exposures ||= Exposures.new end
Returns the defined exposures. These are unbound, since bound exposures are only created when initializing a View
instance.
@return [Exposures] @api private @since 2.1.0
Source
# 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
@api private @since 2.1.0
Source
# File lib/hanami/view.rb, line 277 def self.inherited(klass) super exposures.each do |name, exposure| klass.exposures.import(name, exposure) end end
@api private @since 2.1.0
Source
# File lib/hanami/view.rb, line 516 def self.layout_path(layout) File.join(*[config.layouts_dir, layout].compact) end
@api private @since 2.1.0
Source
# File lib/hanami/view.rb, line 533 def initialize self.class.config.finalize! ensure_config @exposures = self.class.exposures.bind(self) end
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.
@api public @since 2.1.0
Source
# File lib/hanami/view.rb, line 410 def self.private_expose(*names, **options, &block) expose(*names, **options, private: true, &block) end
@see expose
@api public @since 2.1.0
Source
# File lib/hanami/view.rb, line 506 def self.scope(scope_class = nil, &block) scope_class ||= config.scope || config.scope_class config.scope = Class.new(scope_class, &block) end
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.
@param scope [Hanami::View::Scope] the current scope (if any), or the
default base class will be `Hanami::View::Scope`
@param block [Proc] the scope logic definition
@api public
@example 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!"
@example 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!"
@api public @since 2.1.0
Public Instance Methods
Source
# 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
Renders the view.
@param format [Symbol] template format to use @param context [Context] context object to use @param layout [String, FalseClass, nil] layout name, or false to indicate no layout @param input input data for preparing exposure values
@return [Rendered] rendered view object
@api public @since 2.1.0
Source
# File lib/hanami/view.rb, line 544 def config self.class.config end
Returns the view’s configuration.
@api public @since 2.1.0
Source
# File lib/hanami/view.rb, line 554 def exposures @exposures end
Returns the view’s bound exposures.
@return [Exposures]
@api private @since 2.1.0
Source
# 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
@api private @since 2.1.0
Private Instance Methods
Source
# File lib/hanami/view.rb, line 593 def ensure_config raise UndefinedConfigError, :paths unless Array(config.paths).any? raise UndefinedConfigError, :template unless config.template end
Source
# File lib/hanami/view.rb, line 608 def layout_locals(locals) locals.each_with_object({}) do |(key, value), layout_locals| layout_locals[key] = value if exposures[key].for_layout? end end
Source
# File lib/hanami/view.rb, line 598 def locals(rendering, input) exposures.(context: rendering.context, **input) do |value, exposure| if exposure.decorate? && value rendering.part(exposure.name, value, as: exposure.options[:as]) else value end end end