Adding Parameterized Layouts to phlex-rails

by Jared Norman
published April 16, 2023
❗️

I believe the hack described in this post broke due to changes in Phlex. The good news is that the latest version of Phlex supports parameterized layouts now, so this whole post is moot.

As of publishing, phlex-rails doesn’t allow you to parameterize your layout components. There’s a reason for it concerning how Rails handles rendering layouts. Instead, they recommend using content_for and yield. This works fine for some use cases, but it doesn’t work great when you’re converting a layout that uses instance variables to control aspects of the layout.

You can bring your own parameterized layouts by adding the following code to your ApplicationLayout class:

class Wrapper
  def initialize(klass:, args:, kwargs:)
    @klass = klass
    @instance = klass.new(*args, **kwargs)
  end

  def render(view, _locals, &block)
    @instance.call(view_context: view) do |yielded|
      case yielded
      when Symbol
        output = view.view_flow.get(yielded)
      else
        output = yield
      end

      case output
      when ActiveSupport::SafeBuffer
        @instance.unsafe_raw output
      end

      nil
    end
  end

  def identifier
    @klass.identifier
  end

  def virtual_path
    @klass.virtual_path
  end
end

def self.with(*args, **kwargs)
  Wrapper.new(klass: self, args: args, kwargs: kwargs)
end

With this, the standard approach to selecting your layout will still work.

layout -> { ApplicationLayout }

When you need to parameterize your layout, use the with method instead.

layout -> { ApplicationLayout.with(theme: :blue, show_header: false) }

I hope phlex-rails will eventually support this use case, but this snippet solves the problem for now.