Ruby on Rails: How do you list the partial paths that are rendered for a page?

Yes, there is so totally a way to do this in Rails!

As bdares pointed out in his comment, the lines for template rendering appear in the logs. But how do they even get there in the first place? Well, that has to do with a little something in Active Support called LogSubscriber. The documentation for this is pretty thorough:

ActiveSupport::LogSubscriber is an object set to consume ActiveSupport::Notifications with the sole purpose of logging them. The log subscriber dispatches notifications to a registered object based on its given namespace.

The thing that's putting the "Rendered ..." lines in the logs, is ActionView::LogSubscriber which is defined like this within Rails 3.2. Take a moment to read the code. It's nice and neat.

What this is doing is subscribing to a couple of events within Rails, namely the render_template, render_partial and render_collection methods. It's doing this by calling the ActiveSupport::LogSubscriber.attach_to method, which will attach a log subscriber to some events. That method is also interesting, and defined like this:

def attach_to(namespace, log_subscriber=new, notifier=ActiveSupport::Notifications)
  log_subscribers << log_subscriber
  @@flushable_loggers = nil

  log_subscriber.public_methods(false).each do |event|
    next if 'call' == event.to_s

    notifier.subscribe("#{event}.#{namespace}", log_subscriber)
  end
end

I've put this code in line because it's extremely relevant to our interests. What this is doing is calling subscribe on the notifier object. The notifier object is, by default ActiveSupport::Notifications, which is important to remember. The documentation for that class in Rails is in the API too.

When subscribe is called, it passes through the name of the event. What is this? Well, it's every single public method inside the class. When the LogSubscriber subclass is defined, it defines a couple of public methods: render_template, render_partial and render_collection. These are the events which will be subscribed to for this log subscriber.


Now, that's relevant to our interests because we're going to use the same thing to subscribe to these events inside the view. The first step is letting ActiveSupport::Notfications that we want to subscribe to all partial rendering events. We can do this by creating a new before_filter within ApplicationController:

before_filter :log_partial_events

def log_partial_events
  @partial_events = []
  ActiveSupport::Notifications.subscribe("render_partial.action_view") do |event_name, start_at, end_at, id, payload|
  @partial_events << payload[:identifier]
end

The event we're subscribing to is the render_partial event within the action_view namespace, which is exactly one of the events that ActionView::LogSubscriber is subscribing to. What will happen when a partial is rendered is that this block will be called and passed in some data.

event_name: The name of the event. start_at: The time the event started. end_at: The time the event ended. id: A unique identifier for this event. payload: Some payload information about this event.

When the hook's called, it'll add the identifier key from the payload to the array defined at the top of the method.

Then in the view, to access a list of these you can do this:

<%= debug @partial_events %>

The following example from the Notification API should help you:

ActiveSupport::Notifications.subscribe("render") do |*args|
  events << ActiveSupport::Notifications::Event.new(*args)
end

For your case (tracking rendered partials) you want the 'render_partial.action_view' notification. Your code might look something like this:

class ApplicationController < ActionController::Base
  before_filter :subscribe_to_render

  private
  def subscribe_to_render
    @render_events = []
    ActiveSupport::Notifications.subscribe("render_partial.action_view") do |*args|
      @render_events << ActiveSupport::Notifications::Event.new(*args)
    end
  end
end

And wherever you wanted to display this info, you could use this helper method:

def display_render_events
  raw(@render_events.map do |event|
    event.payload[:identifier].gsub(/^.*app\/views\//, '')
  end.join('<br/>'))
end

For lots of debugging info like this, have a look at rails-footnotes