Phlex integration Issues

Dear Hanami,

I’m following the patterns established here by Paweł GitHub - katafrakt/palaver: a toy forum software in Hanami 2 to establish Phlex in my project. The two important pieces are this extension:

# frozen_string_literal: true

module Hanami
  module Extensions
    module Response
      def render(view, **args)
        context = UI::View::Context.new(self)
        layout = UI::Layout.new(view, context, **args)
        self.body = layout.call
      end
    end
  end
end

Hanami::Action::Response.prepend(Hanami::Extensions::Response)

And this override:

# frozen_string_literal: true

require 'hanami'

module MyApp
  # By default Hanami tries to find a matching class for current action,
  # infer its name and instantiate it. However, this does not work with Phlex
  # views. Hanami tries to create new instance with no arguments, but Phlex views
  # use arguments in initialization. Therefore we need to disable this feature by
  # creating a fake inferrer that always return empty array.
  class FakeViewNameInferrer
    def self.call(...) = []
  end

  class App < Hanami::App
    config.actions.sessions = :cookie, { secret: ENV.fetch('SESSION_SECRET', nil) }

    # disable default view inferrer
    config.actions.view_name_inferrer = FakeViewNameInferrer
  end
end

This all works but has two unfortunate consequences:

  1. I am no longer able to implicitly render view meaning I have to call:
response.render(Views::Home::Index)

in every action.

  1. More crucially I can no longer dynamically define a layout which is honestly something I need to do on a per slice basis.

Does anyone know how I might address either of these two issues?

Hey. Setting layouts dynamically should be pretty straightforward with something like:

module Hanami
  module Extensions
    module Response
      def render(view, **args)
        context = UI::View::Context.new(self)
        layout_class = args.fetch(:layout, UI::Layout)
        layout = layout_class.new(view, context, **args)
        self.body = layout.call
      end
    end
  end
end

then

response.render(Views::Home::Index, layout: UI::AdminLayout)

As for implicit rendering, this would be hard for reasons described in the comment to FakeViewNameInferrer. Or at least was hard when I wrote that code. Perhaps something has change that I’m not aware of.

1 Like

First and foremost thank you for the example on Phlex integration its been a tremendous help.

As for the layout I’m using this as a work around:

# frozen_string_literal: true

module MyApp
  class View < UI::Component
    class << self
      def layout
        @layout ||= Layout::Default
      end

      protected

      def use_layout(layout)
        @layout = layout
      end

      private

      def inherited(subclass)
        super

        subclass.send(:use_layout, layout)
      end
    end

    def initialize(context = nil, **args) # rubocop:disable Lint/MissingSuper
      @context = context
      args.each_pair { |k, v| instance_variable_set(:"@#{k}", v) }
    end

    def csrf_token
      @context&.csrf_token
    end

    def flash
      @context&.flash
    end
  end
end
# frozen_string_literal: true

module MyApp
  module Extensions
    module Response
      def render(view_or_view_class, **args)
        view_class = view_or_view_class.is_a?(Class) ? view_or_view_class : view_or_view_class.class
        context = Complish::View::Context.new(self)
        layout = view_class.layout.new(view_class, context, **args)
        self.body = layout.call
      end
    end
  end
end

Hanami::Action::Response.prepend(Complish::Extensions::Response)
1 Like