Responsibly Use Peek to Inspect Rails

January 07, 2015

Reading time ~11 minutes

I love developing applications with the peek gem. It's designed for development or staging environments; there are a few tricks if you don't want it on production or test environments.

This gem puts a little bar on your pages which gives you all sorts of helpful information about the request you just made.

peek screenshot

As you can see there is information about the branch you're on, performance metrics, database queries, caches, shared memory, and job queues in there. Read the documentation for peek on GitHub and find the plugins that work best for you. They're excellent.

The trouble is I definitely don't want to install Peek on production, or in my test environments, and the peek usage instructions would, indeed, require me to do so for a few key reasons: initializers, routes, assets, and views. Using peek requires a few steps of setup in your application. Each of the areas I enumerated requires peek to be a loaded gem in the environment you're running on.

My first attempt to work around this involved wrapping calls with a defined? test. For example, in config/routes.rb:

1mount Peek::Railtie => '/peek' if defined?(Peek)

To style and script peek bar you need some CSS and JavaScript assets as well, which are loaded through the asset pipeline. Problem is, if the gem isn't in your bundle those loads fail, and so does pre-compilation if you attempt it.

Finally, your views should be checking the peek_enabled? method, which is defined by peek in your ApplicationController class. So, for example, when rendering the peek bar itself you should be doing this:

1body
2  = render 'peek/bar' if peek_enabled?

Again, if peek isn't in your Bundle, such as in the production environment, this render will fail.

I'm not particularly enthusiastic about this situation, so here is my guide to using peek responsibly. There are enough moving parts I decided to use the Table of Contents.

Gemfile

Peek must be loaded into our environment. I'm loading it into my :development environment only, along with a list of Peek plugins I want to use.

1group :development do
2  # Peek Bar
3  gem 'peek'
4  gem 'peek-git'
5  gem 'peek-gc'
6  gem 'peek-performance_bar'
7  gem 'peek-pg'
8end

PeekBar Class

This class is meant to answer two questions:

  1. Is Peek available for use? This question is asked when we start and configure the application.
  2. Should peek be enabled for this request? This question is asked during requests. Specifically during view rendering for any view that incorporates peek, which is most likely a layout.

Definition

I put this class in lib/peek_bar.rb.

 1class PeekBar
 2  def self.available?
 3    !defined?(Peek).nil?
 4  end
 5
 6  def self.enabled?(current_user)
 7    return false unless available?
 8
 9    # The default test.
10    %w(development staging).include? Rails.env
11  end
12end

Here you can see we've encapsulated the defined? test into PeekBar.available?. We use it in PeekBar.enabled? as the first test.

If peek is available, and we're in development or the current_user likes nerdy toys we enable peek.

Test

I use rspec for testing, so my spec is in spec/lib/peek_bar_spec.rb.

 1require 'rails_helper'
 2
 3RSpec.describe PeekBar do
 4  subject { described_class }
 5
 6  context 'without Peek' do
 7    before(:each) do
 8      Object.send(:remove_const, :Peek) if defined?(Peek)
 9    end
10
11    it "isn't available" do
12      expect(subject.available?).to be(false)
13    end
14
15    it "isn't enabled" do
16      expect(subject.enabled?(anything)).to be(false)
17    end
18  end
19
20  context 'with Peek' do
21    before(:each) { Peek = Class.new }
22    after(:each) { Object.send(:remove_const, :Peek) }
23
24    it 'is available' do
25      expect(subject.available?).to be(true)
26    end
27
28    it 'is enabled' do
29      allow(Rails.env).to receive(:development?).and_return(true)
30      expect(subject.enabled?(anything)).to be(true)
31    end
32  end
33end

In order to ensure this test is isolated I take extra precautions to ensure the Peek constant isn't available to my tests unless I explicitly allow it.

Initialization

By default Rails doesn't load ruby code from the lib/ directory, so lets be sure to tell it to. In config/application.rb add this:

1config.autoload_paths += %W(
2  #{config.root}/lib
3)

PeekBarHelper Module

As I said earlier peek implements a peek_enabled? method and suggests if you want to customize it define the method in your ApplicationController class. I recommend doing that in a helper class instead.

We need to know if peek should be enabled and to verify that we also need to know if it's available. With the PeekBar class this is simple.

Definition

In app/helpers/peek_bar_helper.rb.

1module PeekBarHelper
2  # Typical Peek Integration
3  def peek_enabled?
4    PeekBar.enabled?(current_user)
5  end
6end

Test

Since we're programming responsibly here's a test for the helper. In spec/helpers/peek_bar_helper_spec.rb.

 1require 'rails_helper'
 2
 3RSpec.describe PeekBarHelper, type: :helper do
 4  before(:each) do
 5    allow(helper).to receive(:current_user).and_return(anything)
 6  end
 7
 8  it 'is not enabled' do
 9    allow(PeekBar).to receive(:enabled?).and_return(false)
10    expect(helper.peek_enabled?).to be(false)
11  end
12
13  it 'is enabled' do
14    allow(PeekBar).to receive(:enabled?).and_return(true)
15    expect(helper.peek_enabled?).to be(true)
16  end
17end

Peek Initialization

The peek documentation explains how to set up config/initializers/peek.rb to enable the plugins you've selected. We need to first test for the availability of peek like this:

1if PeekBar.available?
2  Peek.into Peek::Views::Git
3  Peek.into Peek::Views::GC
4  Peek.into Peek::Views::PerformanceBar
5  Peek.into Peek::Views::PG
6end

Assets

We don't want to load or pre-compile the peek assets unless we intend to use them. My recommended approach is to wrap their inclusion in a new set of CSS and JavaScript files. I use the asset pipeline, so that's how I'll be including the gem's assets.

CSS and JavaScript Definition

CSS is in app/assets/stylesheets/peek_bar.scss.

 1//= require peek
 2//= require peek/views/performance_bar
 3
 4// Peek bar on bottom of page.
 5#peek {
 6  position: fixed;
 7  bottom:   0;
 8  left:     0;
 9  right:    0;
10  z-index:  999;
11}

I prefer CoffeeScript so I loaded the JavaScript in app/assets/javascripts/peek_bar.js.coffee.

1#= require peek
2#= require peek/views/performance_bar

Initialization

The asset pipeline complains if you don't tell it about additional assets you intend to include separately (see the next section on View Configuration). Thing is, we don't always intend to include them or pre-compile them in this case. Just like when we initialized peek itself we must check for its availability.

Add this to config/initializers/assets.rb:

1if PeekBar.available?
2  Rails.application.config.assets.precompile += %w(peek_bar.css peek_bar.js)
3end

Default Assets Gotcha

By default application.css and application.js include all the CSS and JavaScript in their respective directory trees. That's achieved through the use of require_tree . in each of those files.

Now that we've created a couple assets to conditionally load we need to remove require_tree. If you don't remove it your default assets will try to include peek_bar.* which will lead to the aforementioned failures on environments where peek is not installed.

If you were relying on it, which I don't recommend for reasons beyond the scope of this essay, learn how to deal with it by reading the Asset Pipeline Rails Guide.

View Configuration

All this hard work for what? Now it's time to tie it all together in our layout view. I like slim so this is an extremely condensed version of my default layout, app/views/layouts/application.html.slim.

 1doctype html
 2html
 3  head
 4    = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true
 5    - if peek_enabled?
 6      = stylesheet_link_tag 'peek_bar', media: 'all', 'data-turbolinks-track' => true
 7    = csrf_meta_tags
 8body
 9  = render 'peek/bar' if peek_enabled?
10  = yield
11  = javascript_include_tag 'application', 'data-turbolinks-track' => true
12  - if peek_enabled?
13    = javascript_include_tag 'peek_bar', 'data-turbolinks-track' => true

Finally

Voila!

At this point peek is incorporated into my application in a robust, responsible way. Environments we didn't install it into operate perfectly without it, and the ones we do have an encapsulation to use for managing its inclusion in our interface.

It was a little more work but it paid off. We have an excellent developer tool installed without hurting production.

If we want to use peek in production with only staff accounts, for example, all we have to do is change the Gemfile to install peek in the :production group and adjust the PeekBar.enabled? method:

1def self.enabled?(current_user)
2  return false unless available?
3
4  Rails.env.development? || current_user.staff?
5end

That PeekBar class really tied the room together, did it not?

Speaking at Your Event

My speaking schedule is getting complicated this year so I made a page to keep track.… Continue reading

Redefining Culture Fit

Published on November 06, 2015

The cloud-native future

Published on September 02, 2015