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)