lib/hanami/extensions/view/context.rb in hanami-2.0.3 vs lib/hanami/extensions/view/context.rb in hanami-2.1.0.beta1
- old
+ new
@@ -1,9 +1,7 @@
# frozen_string_literal: true
-require "hanami/view"
-require "hanami/view/context"
require_relative "../../errors"
module Hanami
module Extensions
module View
@@ -11,95 +9,166 @@
#
# This is NOT RELEASED as of 2.0.0.
#
# @api private
module Context
- def self.included(context_class)
- super
+ class << self
+ # Returns a context class for the given slice. If a context class is not defined, defines
+ # a class named `Views::Context` within the slice's namespace.
+ #
+ # @api private
+ def context_class(slice)
+ views_namespace = views_namespace(slice)
- context_class.extend(Hanami::SliceConfigurable)
- context_class.extend(ClassMethods)
- context_class.prepend(InstanceMethods)
- end
+ if views_namespace.const_defined?(:Context)
+ return views_namespace.const_get(:Context)
+ end
- module ClassMethods
- def configure_for_slice(slice)
- extend SliceConfiguredContext.new(slice)
+ views_namespace.const_set(:Context, Class.new(context_superclass(slice)).tap { |klass|
+ klass.configure_for_slice(slice)
+ })
end
- end
- module InstanceMethods
- # @see SliceConfiguredContext#define_new
- def initialize(**kwargs)
- defaults = {content: {}}
+ private
- super(**kwargs, **defaults)
+ def context_superclass(slice)
+ return Hanami::View::Context if Hanami.app.equal?(slice)
+
+ begin
+ slice.inflector.constantize(
+ slice.inflector.camelize("#{slice.app.slice_name.name}/views/context")
+ )
+ rescue NameError => e
+ raise e unless %i[Views Context].include?(e.name)
+
+ Hanami::View::Context
+ end
end
- def inflector
- _options.fetch(:inflector)
+ # TODO: this could be moved into the top-level Extensions::View
+ def views_namespace(slice)
+ if slice.namespace.const_defined?(:Views)
+ slice.namespace.const_get(:Views)
+ else
+ slice.namespace.const_set(:Views, Module.new)
+ end
end
+ end
- def routes
- _options.fetch(:routes)
+ module ClassExtension
+ def self.included(context_class)
+ super
+
+ context_class.extend(Hanami::SliceConfigurable)
+ context_class.extend(ClassMethods)
+ context_class.prepend(InstanceMethods)
end
- def settings
- _options.fetch(:settings)
+ module ClassMethods
+ def configure_for_slice(slice)
+ extend SliceConfiguredContext.new(slice)
+ end
end
- def assets
- unless _options[:assets]
- raise Hanami::ComponentLoadError, "hanami-assets gem is required to access assets"
+ module InstanceMethods
+ attr_reader :inflector
+
+ attr_reader :settings
+
+ # @see SliceConfiguredContext#define_new
+ def initialize( # rubocop:disable Metrics/ParameterLists
+ inflector: nil,
+ settings: nil,
+ routes: nil,
+ assets: nil,
+ request: nil,
+ **args
+ )
+ @inflector = inflector
+ @settings = settings
+ @routes = routes
+ @assets = assets
+ @request = request
+
+ @content_for = {}
+
+ super(**args)
end
- _options[:assets]
- end
+ def initialize_copy(source)
+ # The standard implementation of initialize_copy will make shallow copies of all
+ # instance variables from the source. This is fine for most of our ivars.
+ super
- def content_for(key, value = nil, &block)
- content = _options[:content]
- output = nil
+ # Dup any objects that will be mutated over a given rendering to ensure no leakage of
+ # state across distinct view renderings.
+ @content_for = source.instance_variable_get(:@content_for).dup
+ end
- if block
- content[key] = yield
- elsif value
- content[key] = value
- else
- output = content[key]
+ def with(**args)
+ self.class.new(
+ inflector: @inflector,
+ settings: @settings,
+ assets: @assets,
+ routes: @routes,
+ request: @request,
+ **args
+ )
end
- output
- end
+ def assets
+ unless @assets
+ raise Hanami::ComponentLoadError, "the hanami-assets gem is required to access assets"
+ end
- def current_path
- request.fullpath
- end
+ @assets
+ end
- def csrf_token
- request.session[Hanami::Action::CSRFProtection::CSRF_TOKEN]
- end
+ def request
+ unless @request
+ raise Hanami::ComponentLoadError, "only views rendered from Hanami::Action instances have a request"
+ end
- def request
- _options.fetch(:request)
- end
+ @request
+ end
- def session
- request.session
- end
+ def routes
+ unless @routes
+ raise Hanami::ComponentLoadError, "the hanami-router gem is required to access routes"
+ end
- def flash
- response.flash
- end
+ @routes
+ end
- private
+ def content_for(key, value = nil)
+ if block_given?
+ @content_for[key] = yield
+ elsif value
+ @content_for[key] = value
+ else
+ @content_for[key]
+ end
+ end
- # TODO: create `Request#flash` so we no longer need the `response`
- def response
- _options.fetch(:response)
+ def current_path
+ request.fullpath
+ end
+
+ def csrf_token
+ request.session[Hanami::Action::CSRFProtection::CSRF_TOKEN]
+ end
+
+ def session
+ request.session
+ end
+
+ def flash
+ request.flash
+ end
end
end
end
end
end
end
-Hanami::View::Context.include(Hanami::Extensions::View::Context)
+Hanami::View::Context.include(Hanami::Extensions::View::Context::ClassExtension)