Adding Parameterized Layouts to phlex-rails

by Jared Norman
published April 16, 2023

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.