class Hanami::Extensions::View::SliceConfiguredView

Provides slice-specific configuration and behavior for any view class defined within a slice’s module namespace.

@api public @since 2.1.0

Constants

PARTS_DIR
SCOPES_DIR
TEMPLATES_DIR
VIEWS_DIR

Attributes

slice[R]

Public Class Methods

new(slice) click to toggle source

@api private @since 2.1.0

Calls superclass method
# File lib/hanami/extensions/view/slice_configured_view.rb, line 21
def initialize(slice)
  super()
  @slice = slice
end

Public Instance Methods

extended(view_class) click to toggle source

@api private @since 2.1.0

# File lib/hanami/extensions/view/slice_configured_view.rb, line 28
def extended(view_class)
  load_app_view
  configure_view(view_class)
  define_inherited
end
inspect() click to toggle source

@return [String]

@api public @since 2.1.0

# File lib/hanami/extensions/view/slice_configured_view.rb, line 38
def inspect
  "#<#{self.class.name}[#{slice.name}]>"
end

Private Instance Methods

app?() click to toggle source
# File lib/hanami/extensions/view/slice_configured_view.rb, line 237
def app?
  slice.app == slice
end
configure_view(view_class) click to toggle source

rubocop:disable Metrics/AbcSize

# File lib/hanami/extensions/view/slice_configured_view.rb, line 58
def configure_view(view_class)
  view_class.settings.each do |setting|
    next unless slice.config.views.respond_to?(setting.name)

    # Configure the view from config on the slice, _unless it has already been configured by
    # a parent slice_, and re-configuring it for this slice would make no change.
    #
    # In the case of most slices, its views config is likely to be the same as its parent
    # (since each slice copies its `config` from its parent), and if we re-apply the config
    # here, then it may possibly overwrite config customisations explicitly made in parent
    # view classes.
    #
    # For example, given an app-level base view class, with custom config:
    #
    #   module MyApp
    #     class View < Hanami::View
    #       config.layout = "custom_layout"
    #     end
    #   end
    #
    # And then a view in a slice inheriting from it:
    #
    #   module MySlice
    #     module Views
    #       class SomeView < MyApp::View
    #       end
    #     end
    #   end
    #
    # In this case, `SliceConfiguredView` will be extended two times:
    #
    # 1. When `MyApp::View` is defined
    # 2. Again when `MySlice::Views::SomeView` is defined
    #
    # If we blindly re-configure all view settings each time `SliceConfiguredView` is
    # extended, then at the point of (2) above, we'd end up overwriting the custom
    # `config.layout` explicitly configured in the `MyApp::View` base class, leaving
    # `MySlice::Views::SomeView` with `config.layout` of `"app"` (the default as specified
    # at `Hanami.app.config.views.layout`), and not the `"custom_layout"` value configured
    # in its immediate superclass.
    #
    # This would be surprising behavior, and we want to avoid it.
    slice_value = slice.config.views.public_send(setting.name)
    parent_value = slice.parent.config.views.public_send(setting.name) if slice.parent

    next if slice.parent && slice_value == parent_value

    view_class.config.public_send(
      :"#{setting.name}=",
      setting.mutable? ? slice_value.dup : slice_value
    )
  end

  view_class.config.inflector = inflector

  # Configure the paths for this view if:
  #   - We are the app, and a user hasn't provided custom `paths` (in this case, we need to
  #     set the defaults)
  #   - We are a slice, and the view's inherited `paths` is identical to the parent's config
  #     (which would result in the view in a slice erroneously trying to find templates in
  #     the app)
  if view_class.config.paths.empty? ||
     (slice.parent && view_class.config.paths.map(&:dir) == [templates_path(slice.parent)])
    view_class.config.paths = templates_path(slice)
  end

  view_class.config.template = template_name(view_class)
  view_class.config.default_context = Extensions::View::Context.context_class(slice).new

  view_class.config.part_class = part_class
  view_class.config.scope_class = scope_class

  if (part_namespace = namespace_from_path("#{VIEWS_DIR}/#{PARTS_DIR}"))
    view_class.config.part_namespace = part_namespace
  end
  if (scope_namespace = namespace_from_path("#{VIEWS_DIR}/#{SCOPES_DIR}"))
    view_class.config.scope_namespace = scope_namespace
  end
end
define_inherited() click to toggle source

rubocop:enable Metrics/AbcSize

Calls superclass method
# File lib/hanami/extensions/view/slice_configured_view.rb, line 139
def define_inherited
  template_name = method(:template_name)

  define_method(:inherited) do |subclass|
    super(subclass)
    subclass.config.template = template_name.(subclass)
  end
end
inflector() click to toggle source
# File lib/hanami/extensions/view/slice_configured_view.rb, line 178
def inflector
  slice.inflector
end
load_app_view() click to toggle source

If the given view doesn’t inherit from the app view, attempt to load it anyway, since requiring the app view is necessary for its ‘SliceConfiguredView` hook to execute and define the app-level part and scope classes that we refer to here.

# File lib/hanami/extensions/view/slice_configured_view.rb, line 47
def load_app_view
  return if app?

  begin
    slice.app.namespace.const_get(:View, false)
  rescue NameError => e
    raise unless e.name == :View
  end
end
namespace_from_path(path) click to toggle source
# File lib/hanami/extensions/view/slice_configured_view.rb, line 156
def namespace_from_path(path)
  path = "#{slice.slice_name.path}/#{path}"

  begin
    require path
  rescue LoadError => exception
    raise exception unless exception.path == path
  end

  begin
    inflector.constantize(inflector.camelize(path))
  rescue NameError # rubocop: disable Lint/SuppressedException
  end
end
part_class() click to toggle source
# File lib/hanami/extensions/view/slice_configured_view.rb, line 182
def part_class
  @part_class ||=
    if views_namespace.const_defined?(:Part)
      views_namespace.const_get(:Part)
    else
      views_namespace.const_set(:Part, Class.new(part_superclass).tap { |klass|
        # Give the slice to `configure_for_slice`, since it cannot be inferred when it is
        # called via `.inherited`, because the class is anonymous at this point
        klass.configure_for_slice(slice)
      })
    end
end
part_superclass() click to toggle source
# File lib/hanami/extensions/view/slice_configured_view.rb, line 195
def part_superclass
  return Hanami::View::Part if app?

  begin
    inflector.constantize(inflector.camelize("#{slice.app.slice_name.name}/views/part"))
  rescue NameError
    Hanami::View::Part
  end
end
scope_class() click to toggle source
# File lib/hanami/extensions/view/slice_configured_view.rb, line 205
def scope_class
  @scope_class ||=
    if views_namespace.const_defined?(:Scope)
      views_namespace.const_get(:Scope)
    else
      views_namespace.const_set(:Scope, Class.new(scope_superclass).tap { |klass|
        # Give the slice to `configure_for_slice`, since it cannot be inferred when it is
        # called via `.inherited`, since the class is anonymous at this point
        klass.configure_for_slice(slice)
      })
    end
end
scope_superclass() click to toggle source
# File lib/hanami/extensions/view/slice_configured_view.rb, line 218
def scope_superclass
  return Hanami::View::Scope if app?

  begin
    inflector.constantize(inflector.camelize("#{slice.app.slice_name.name}/views/scope"))
  rescue NameError
    Hanami::View::Scope
  end
end
template_name(view_class) click to toggle source
# File lib/hanami/extensions/view/slice_configured_view.rb, line 171
def template_name(view_class)
  inflector
    .underscore(view_class.name)
    .sub(/^#{slice.slice_name.path}\//, "")
    .sub(/^#{VIEWS_DIR}\//, "")
end
templates_path(slice) click to toggle source
# File lib/hanami/extensions/view/slice_configured_view.rb, line 148
def templates_path(slice)
  if slice.app.equal?(slice)
    slice.root.join(APP_DIR, TEMPLATES_DIR)
  else
    slice.root.join(TEMPLATES_DIR)
  end
end
views_namespace() click to toggle source
# File lib/hanami/extensions/view/slice_configured_view.rb, line 228
def views_namespace
  @slice_views_namespace ||=
    if slice.namespace.const_defined?(:Views)
      slice.namespace.const_get(:Views)
    else
      slice.namespace.const_set(:Views, Module.new)
    end
end