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.
Navigating Execution With binding.pry
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:
cdinto an object and runls— then do the same thing on a class rather than an instance.cd Orderfollowed byls -mshows class methods including those inherited from concerns and gem modules, and becauseselfis 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.
Check viewARU - Brand Newsletter!
Newsletter to DEVs by DEVs - boost your Personal Brand & career! 🚀