/ Tags: DEVELOPER TIPS / Categories: TIPS

Pry Power User — ls, cd, show-source, and the Commands That Replace Grep

Most people install Pry, use it as a slightly nicer IRB with a binding.pry breakpoint, and never touch the rest. That’s a shame, because the interesting part isn’t the REPL — it’s that Pry turns a running process into a filesystem you can navigate. Once ls, cd, and show-source are in your fingers, a lot of questions that would send you to the editor get answered in the console in about four seconds.

ls — What Can This Object Do?


ls on an object lists its methods, grouped by where they come from. That grouping is the entire value.

Example:

[1] pry(main)> order = Order.first
[2] pry(main)> ls order
ActiveRecord::Core#methods: ==  <=>  encode_with  freeze  hash  inspect  ...
Order#methods: total  total_with_tax  line_items  customer  apply_discount!
Order::GeneratedAssociationMethods#methods: customer  customer=  line_items  line_items=
instance variables: @attributes  @association_cache  @new_record
locals: _  __  _dir_  _ex_  _file_  _in_  _out_  order

The section headers tell you which class or module contributed each method — that’s your ancestor chain, rendered usefully. When you’re staring at an object from an unfamiliar codebase, this is the fastest orientation available.

Narrow it with a grep flag:

[3] pry(main)> ls order -M --grep total
[4] pry(main)> ls Order -m          # methods defined on the class itself
[5] pry(main)> ls order -i          # instance variables only
[6] pry(main)> ls order -c          # constants

ls -M --grep <pattern> is the one you’ll use constantly. “Is there a method for this?” answered without leaving the console.

cd — Change the Receiver


cd moves self into an object. Every subsequent expression evaluates in that context, including private methods.

Example:

[1] pry(main)> cd Order.first
[2] pry(#<Order>)> total
=> 4200
[3] pry(#<Order>)> calculate_discount   # private — works, because self is the order
=> 200
[4] pry(#<Order>)> cd customer
[5] pry(#<Customer>)> name
=> "Acme Corp"
[6] pry(#<Customer>)> cd ..
[7] pry(#<Order>)> cd /
[8] pry(main)>

The prompt shows where you are. cd .. goes up one level, cd / returns to main, and nesting prints the full stack.

Calling private methods without send is the practical win here. Debugging an object’s internals normally means typing order.send(:calculate_discount) over and over; cd removes that entirely.

show-source — Read the Method Without Finding the File


Example:

[1] pry(main)> show-source Order#total
From: /app/models/order.rb:31:
Owner: Order
Visibility: public
Signature: total()
Number of lines: 5

def total
  line_items.sum(&:subtotal) - discount_cents
end

It works on classes, modules, and gem code:

[2] pry(main)> show-source ActiveRecord::Persistence#save
[3] pry(main)> show-source Order              # the whole class, all reopenings
[4] pry(main)> show-source -d Order#total     # include the docs

show-source Order on a Rails model shows every place the class was reopened, including concerns and gem patches, concatenated in load order. That’s a genuinely different view than opening order.rb.

show-doc and ?
[5] pry(main)> ? Array#flatten
[6] pry(main)> show-doc Enumerable#each_slice

Requires the documentation to be installed — gem install rdoc and rvm docs generate or equivalent. Worth setting up once.

find-method — Grep the Runtime, Not the Filesystem


You remember there’s a method involving “discount” somewhere and you don’t know the class.

Example:

[1] pry(main)> find-method discount Order
Order
Order#apply_discount!
Order#discount_cents
Order#calculate_discount
Order::DiscountRules.applicable_for

Search method bodies rather than names with -c:

[2] pry(main)> find-method -c "Stripe::Charge" PaymentService

That finds every method in the namespace whose source mentions Stripe::Charge. It’s grep, scoped to loaded code, which means it also searches gems and generated methods that don’t exist in any file you’d grep.


Drop a breakpoint and you get the surrounding source plus full local scope.

Example:

def build_line_item(product, quantity)
  price = current_price_for(product)
  binding.pry
  LineItem.new(product:, quantity:, price_cents: price)
end

At the prompt:

[1] pry(#<InvoiceBuilder>)> whereami       # show surrounding source
[2] pry(#<InvoiceBuilder>)> whereami 20    # 20 lines of context
[3] pry(#<InvoiceBuilder>)> price          # inspect a local
[4] pry(#<InvoiceBuilder>)> product.sku
[5] pry(#<InvoiceBuilder>)> exit           # continue to the next hit
[6] pry(#<InvoiceBuilder>)> exit-program   # kill the process

exit inside a loop hits the breakpoint again on the next iteration. exit! or exit-program is how you get out when you’ve seen enough — otherwise you’re pressing enter four hundred times.

Add pry-byebug for actual stepping:

[1] pry(#<InvoiceBuilder>)> next     # next line
[2] pry(#<InvoiceBuilder>)> step     # into the method call
[3] pry(#<InvoiceBuilder>)> finish   # run until this method returns
[4] pry(#<InvoiceBuilder>)> continue # resume

Ruby 3.1+ ships debug in the standard library with binding.break, which is faster and better maintained. If you’re starting fresh, use that for stepping and keep Pry for exploration — they solve different problems well.

The Command History Tricks


Example:

[1] pry(main)> hist                # session history
[2] pry(main)> hist --grep order   # filtered
[3] pry(main)> hist --replay 4..7  # re-run lines 4 through 7
[4] pry(main)> _                   # last return value
[5] pry(main)> _out_[3]            # output of line 3
[6] pry(main)> _ex_                # last exception raised

_ex_ after something blows up gives you the exception object, and _ex_.backtrace gives you the trace, without having to reproduce it. wtf? prints the last exception’s backtrace directly, and wtf?!!! prints more lines — one exclamation mark per additional five frames, which is a very Pry sort of interface.

Editing In Place


Example:

[1] pry(main)> edit Order#total

Opens your $EDITOR at the exact method, and reloads the file when you close it. The change is live in the session. For quick iteration on a method you’re debugging, this beats the edit-restart-console-resetup loop by a wide margin.

[2] pry(main)> edit --ex        # open the file and line of the last exception

That one is excellent. Exception, edit --ex, you’re at the failing line in your editor.

Pro-Tip: cd into an object and run ls — then do the same thing on a class rather than an instance. cd Order followed by ls -m shows class methods including those inherited from concerns and gem modules, and because self is now the class, you can call private class methods and even define methods on the fly to test an idea: def self.experiment; where(status: 'pending').count; end. The definition lives only in that session. This is the fastest possible way to prototype a scope or a class method against real data before writing it into a file, and it beats editing the model and reloading because there’s nothing to clean up when the idea turns out to be wrong.

Configuring It Once


Example:

# ~/.pryrc

Pry.config.editor = "code --wait"

Pry.commands.alias_command "c",  "continue"
Pry.commands.alias_command "s",  "step"
Pry.commands.alias_command "n",  "next"
Pry.commands.alias_command "f",  "finish"

if defined?(Rails) && Rails.env
  Pry.config.prompt_name = "#{Rails.application.class.module_parent_name.downcase}(#{Rails.env})"
end

Pry.config.history_file = "~/.pry_history"

The environment in the prompt is not decoration. A red-tinted prompt in production has prevented more accidents than any amount of care.

Conclusion


Pry’s value isn’t that it’s a better REPL — it’s that it makes a running process explorable. ls orients you in an unfamiliar object, cd gives you private-method access without send, show-source reads the method as actually loaded rather than as written in one file, and find-method searches the runtime including generated code. Learn those four and the console stops being a place you evaluate expressions and starts being a place you investigate. Pair it with debug/binding.break for stepping and you’ve got the whole toolkit without a single extra gem beyond Pry itself.

FAQs


Q1: Should I use Pry or the stdlib debug gem?
debug (Ruby 3.1+) is better at stepping and breakpoints and is maintained by the core team. Pry is better at exploration — ls, cd, show-source, find-method have no equivalent. Many people keep both.

Q2: Why does show-source fail on some methods?
C-implemented methods have no Ruby source. Methods created via define_method with a block usually work; those created from an eval’d string do not, unless the caller passed __FILE__ and __LINE__.

Q3: How do I use Pry inside a running Rails server?
binding.pry in a request will halt the worker and take over the terminal running the server, which only works with a single-process, single-threaded setup. For anything else use pry-remote, or better, reproduce in the console.

Q4: Does binding.pry slow down production if I leave one in?
It will hang the request thread indefinitely waiting for input nobody will provide. Add a RuboCop rule or a git pre-commit hook that rejects binding.pry — this is a very common and very embarrassing incident.

Q5: Can I use Pry as my default Rails console?
Yes — add gem "pry-rails" to the development group and rails console uses Pry automatically, with show-models and show-routes commands added on top.

cdrrazan

Rajan Bhattarai

Full Stack Software Developer! 💻 🏡 Grad. Student, MCS. 🎓 Class of '23. GitKraken Ambassador 🇳🇵 2021/22. Works with Ruby / Rails. Photography when no coding. Also tweets a lot at TW / @cdrrazan!

Read More