Pry (and Friends) with Rails

pryandrails

In my previous article, we looked at Pry, an extremely powerful alternative to the standard IRB shell that Ruby gives us.

For those who work with Rails, you will be happy to know that Pry has got your back. In this article, we look at the various ways Pry vastly improves our Rails workflow.

Do note that Pry works best with Ruby 1.9.3 and up.

Getting the Sample Application

For our sample application, we would be using the application written for the Rails 3 tutorial by Michael Hartl.

To grab the application:

git clone git@github.com:benjamintanweihao/sample_app.git

Installing Pry with Rails

You could install pry on Rails by adding the pry-rails gem to your gemfile:

gem 'pry-rails', :group => :development

Or you could get that and other goodies with Jazz Hands.

group :development do
  gem 'jazz_hands'
end

Out of the box, Jazz Hands not only includes pry-rails, but it also includes other niceties such as:

  • Awesome Print
  • Pry Doc
  • Pry Remote
  • Pry Debugger
  • Pry Stack Explorer

Once you have got that settled, change back into the sample_app directory and install all the necessary dependencies:

bundle install

Setting up the database

rake db:schema:load

Note that some of you might need to prefix the above command with bundle exec.

One final thing before we proceed to the fun stuff: Create .pryrc in your home directory:

% touch ~/.pryrc

Fill it in with the editor of your choice:

Pry.config.editor = 'vim'

Launching Pry

… is trivial. Simply do a

rails c

and you’re greeted with a pry console:

% rails c
Loading development environment (Rails 3.2.16)

Frame number: 0/3
[1] sample_app »

Exploring a Rails project

Pry gives you a few tools to poke around a project without having to open a single file.

show-models

The first thing we’ll do is see what models the project contains. This is exactly what show-models tell you:

[1] sample_app »  show-models
Micropost
  id: integer
  content: string
  user_id: integer
  created_at: datetime
  updated_at: datetime
  belongs_to :user
Relationship
  id: integer
  follower_id: integer
  followed_id: integer
  created_at: datetime
  updated_at: datetime
  belongs_to :followed
  belongs_to :follower
User
  id: integer
  name: string
  email: string
  created_at: datetime
  updated_at: datetime
  password_digest: string
  remember_token: string
  admin: boolean
  has_many :followed_users (through :relationships)
  has_many :followers (through :reverse_relationships)
  has_many :microposts
  has_many :relationships
  has_many :reverse_relationships

Very awesome! Notice that the output has some very pretty syntax highlighting.

show-routes

rake routes is something I do pretty frequently. Sometimes, when I’m in the midst of a console session, it can be quite inconvenient and distracting to switch windows/tabs just to display the routes.

That is, until show-routes came along:

[2] sample_app »  show-routes
following_user GET    /users/:id/following(.:format) users#following
followers_user GET    /users/:id/followers(.:format) users#followers
         users GET    /users(.:format)               users#index
               POST   /users(.:format)               users#create
      new_user GET    /users/new(.:format)           users#new
     edit_user GET    /users/:id/edit(.:format)      users#edit
          user GET    /users/:id(.:format)           users#show
               PUT    /users/:id(.:format)           users#update
               DELETE /users/:id(.:format)           users#destroy
      sessions POST   /sessions(.:format)            sessions#create
   new_session GET    /sessions/new(.:format)        sessions#new
       session DELETE /sessions/:id(.:format)        sessions#destroy
    microposts POST   /microposts(.:format)          microposts#create
     micropost DELETE /microposts/:id(.:format)      microposts#destroy
 relationships POST   /relationships(.:format)       relationships#create
  relationship DELETE /relationships/:id(.:format)   relationships#destroy
          root        /                              static_pages#home
        signup        /signup(.:format)              users#new
        signin        /signin(.:format)              sessions#new
       signout DELETE /signout(.:format)             sessions#destroy
          help        /help(.:format)                static_pages#help
         about        /about(.:format)               static_pages#about
       contact        /contact(.:format)             static_pages#contact

show-routes has another really useful feature: For larger Rails projects, simply displaying all the routes would easily flood your screen and cause your eyes to water.

Let’s find out what else show-routes is capable of. Prefix the command with a ?):

[3] sample_app »  ? show-routes

From: /usr/local/var/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/pry-rails-0.3.2/lib/pry-rails/commands/show_routes.rb
Number of lines: 6

Usage: show-routes [-G]

show-routes displays the current Rails app's routes.

    -G, --grep      Filter output by regular expression
    -h, --help      Show this message.

So, if I wanted to recall whether it was sign_up or signup, I can do a quick filter:

[3] sample_app »  show-routes -G sign
        signup        /signup(.:format)              users#new
        signin        /signin(.:format)              sessions#new
       signout DELETE /signout(.:format)             sessions#destroy

To display all the POST actions:

[4] sample_app »  show-routes -G POST
               POST   /users(.:format)               users#create
      sessions POST   /sessions(.:format)            sessions#create
    microposts POST   /microposts(.:format)          microposts#create
 relationships POST   /relationships(.:format)       relationships#create

Awesome Printing is Awesome.

(Note: This is not part of pry-rails, but is installed with Jazz Hands. You can get awesome_print by installing the gem standalone, if needed.)

When you do Model.all, the result is usually an unintelligible mess. Here’s a sample of how awesome-print eases that pain (I went ahead and created some Micropost records):

[5] sample_app »  Micropost.all
  Micropost Load (0.2ms)  SELECT "microposts".* FROM "microposts" ORDER BY microposts.created_at DESC
=> [
  [0] #<Micropost:0x007ff7dcda7038> {
            :id => 2,
       :content => "Sure is! Wait till you combine it with Rails!",
       :user_id => 1,
    :created_at => Mon, 27 Jan 2014 15:13:35 UTC +00:00,
    :updated_at => Mon, 27 Jan 2014 15:13:35 UTC +00:00
  },
  [1] #<Micropost:0x007ff7dd66e770> {
            :id => 1,
       :content => "Hello! Isn't Pry awesome?",
       :user_id => 1,
    :created_at => Mon, 27 Jan 2014 15:13:21 UTC +00:00,
    :updated_at => Mon, 27 Jan 2014 15:13:21 UTC +00:00
  }
]

Once you are used to this beautifully formatted, color-coded output, you will never turn back to the plain old rails console ever again.

show-doc

Do not forget that the entire documentation is within easy reach:

[6] sample_app »  cd Array
[7] sample_app(../Array) »  show-doc each_cons

From: enum.c (C Method):
Owner: Enumerable
Visibility: public
Signature: each_cons(arg1)
Number of lines: 14

Iterates the given block for each array of consecutive <n>
elements.  If no block is given, returns an enumerator.

e.g.:
    (1..10).each_cons(3) { |a| p a }
    # outputs below
    [1, 2, 3]
    [2, 3, 4]
    [3, 4, 5]
    [4, 5, 6]
    [5, 6, 7]
    [6, 7, 8]
    [7, 8, 9]
    [8, 9, 10]

Debugging

Pry really shines is its debugging capabilities. Let’s purposely introduce a bug into our project directly from Pry.

First, navigate to the UsersController:

[8] sample_app »  cd UsersController
[9] sample_app(../UsersController) »

show-source

Recall that we can inspect the file source with the show-source command:

[10] sample_app(../UsersController) »  show-source

From: /Users/rambo/Desktop/sample_app/app/controllers/users_controller.rb @ line 1:
Class name: UsersController
Number of monkeypatches: 4. Use the `-a` option to display all available monkeypatches
Number of lines: 74

class UsersController < ApplicationController
  before_filter :signed_in_user,
                only: [:index, :edit, :update, :destroy, :following, :followers]
  before_filter :correct_user,   only: [:edit, :update]
  before_filter :admin_user,     only: :destroy

  def index
    @users = User.paginate(page: params[:page])
  end

  def show
    @user = User.find(params[:id])
    @microposts = @user.microposts.paginate(page: params[:page])
  end

  # Other code omitted ...
end

edit-method

Now, let’s introduce the bug in the index action:

[10] sample_app(../UsersController) »  edit index

Notice that vim (or whatever editor you configured previously) shows up, with the cursor directly at the start of the method definition.

Now, just add a raise at the bottom of the index action:

def index
  @users = User.paginate(page: params[:page])
  raise # <-- Add this
end

After you save and exit, you will be returned to the console.

Debugging with binding.pry

Obviously, when you navigate to http://localhost:3000/users, it will blow up.

Let’s assume you encounter some error in your app. Here are the steps which I usually take:

  • cd into the class
  • edit the offending method
  • add binding.pry

So let’s go back into our console, and do just that:

def index
  @users = User.paginate(page: params[:page])
  binding.pry
  raise
end

This time, navigate to http://localhost:3000/users. However, instead of blowing up, notice that the browser just hangs there.

Check your console window where your launched the rails server. You will notice that there’s another Pry session all prepared for you:

Frame number: 0/66

From: /Users/rambo/Desktop/sample_app/app/controllers/users_controller.rb @ line 9 UsersController#index:

     7: def index
     8:   @users = User.paginate(page: params[:page])
 =>  9:   binding.pry
    10:   raise
    11: end

[1] sample_app(#<UsersController>) »

In this session, you can easily examine all the variables that are in the scope.

We can inspect the contents of @users:

[3] sample_app(#<UsersController>) »  @users
=> [
  [0] #<User:0x007f847f108168> {
                 :id => 1,
               :name => "Benjamin Tan Wei Hao",
         :created_at => Mon, 27 Jan 2014 14:18:22 UTC +00:00,
         :updated_at => Mon, 27 Jan 2014 14:18:22 UTC +00:00,
              :admin => false
  }
]

Let’s try params:

[2] sample_app(#<UsersController>) »  params
=> {
      "action" => "index",
  "controller" => "users"
}

session works too:

[4] sample_app(#<UsersController>) »  session
=> {
   "session_id" => "812d9cfb949e795ed2fef78c2188fc0e",
  "_csrf_token" => "e+MnQKg2URyBEhkXdxpwcmJnyeRT5J+Tc1PGMvYpoU4="
}

Once done, you can go ahead and use edit method_name to remove binding.pry and the raise.

Also, remember to exit, otherwise the request would be blocked indefinitely.

Better Errors

Sometimes, you need binding.pry to poke around variables and trace methods. It is also a good way to impress your programmer friends/colleagues.

But when you are done with that, you would soon realise that it slowly becomes a hassle. Thankfully, there’s the better_errors gem that does away with all the ceremony.

Installation

group :development do
  gem "better_errors"
  gem "binding_of_caller"
end

As the README notes, do not place this under the production group.

I included the raise error again in UsersController#index. If you take a look at the browser, the usual Rails error page is replaced with something much, much, MUCH better:

img

Wrapping Up

Hopefully I have convinced you, once again, of the awesomeness of Pry. Maybe you’re even inspired to use some of the techniques here to improve your Rails workflow.

Happy Prying!

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • soulcutter

    I also have an initializer for better_errors with something to the effect of BetterErrors.use_pry! if Rails.env.development?

  • Low-Wee Heng

    Great article! Easy to follow, clear and concise.

  • Alex Bunardzic

    This is really great. I can’t see why I’ll ever feel the need to go back to the Rails console. Thanks!