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