When I was first learning Ruby, I was amazed at the power of Pry. In fact, Pry is what let me comprehend object-oriented programming. It helped me see how any point-in-code understood the rest of the world. It helped me explore what was possible by making available methods, variables, documentation and source code immediately present.

In addition to being an explorative learning tool, Pry is also a potent “get-stuff-done” tool. Ruby’s design makes it a killer tool at the command line. The APIs are robust, the syntax can be terse and flexible, and it’s designed to be chainable, which makes it easy to iterate against.

Often times when I need to do data work, I’ll hop into a Pry or IRB shell. I’ll connect to our SQL Server DB with Sequel, or make a series of requests with HTTParty or Ferrum. Ruby’s potent Enumerable module makes whipping data into shape a delight.

Recently I was introducing a colleague to Ruby for a data extraction task. While fluent in Python, he was generally new to Ruby syntax and the style of hold-onto-your-butts we’re-doin’-it-live exploration and development tools like Pry can provide. Asking him to run pry and set up the environment to hack felt like a burden. Lots of things to remember for someone not used to the syntax.

Enter: bin/console

I realized that I could set up a comfortable playground to explore our problem by creating a helpful entrypoint. Instead of starting from scratch with pry or irb each time, I could provide a richer experience with information and helpers. Just like a good-citizen in the CLI world will provide the helpful --help option, bin/console can prepare you for hacking. It can provide information and examples to set you up for success, and set you up in your environment with all of your favorite tools.

Here’s the file I added as bin/console:

#!/usr/bin/env ruby
require_relative '../lib/proxy'
require_relative '../lib/browser'
require_relative '../lib/somesite'
require_relative '../lib/somesite/page/search'


def info
  puts "[Development Console] [DEBUG: #{!!ENV['DEBUG']}]"
  puts <<~TXT

  By default, the browser is headless.
  To show the browser for debugging, call `debug!` or launch with `DEBUG=true bin/console` instead.
    (Requires a new browser session, i.e. `browser = Browser.new`)

  Some interesting things you might want to do:
    - Play with the browser:

      ```
      browser = Browser.new
      browser.goto 'https://www.httpbin.org/headers'
      puts browser.body
      ```

    - Play with the SomeSite wrapper for the browser:

      ```
      client = SomeSite.new
      client.set_location_by_zip_code 55802
      client.search(205, 50, 16)
      ```

      browser = client.browser

    - Skip the browser, and load sample data straight into a page object:

      ```
      page = SomeSite::Page::Search.new(File.read('test/data/somesite-205-50-16.html'))

      listing = page.results.first

      listing.to_h
      ```
  
  Some helpful `pry` commands:
    - ls: Show methods and objects available in this scope
    - ls browser: Show methods and objects available to the given object, `browser`
    - ? browser: Show the docs for the given object, browser
    - ? SomeSite#search: Show the docs for the given method, .search
                         Also accepts methods on instantiated objects, such as `browser.goto`
    - $ SomeSite#search: Show the source code for the given method, the instance method `search` on `SomeSite
    - $ SomeSite: Show the source code for the given class or module, SomeSite
    - $ browser: Show the source code for the given instance of an object, browser

  You can see this information again by calling `info`.
  TXT
end

def debug!
  ENV['DEBUG'] = 'true'
  puts "[DEBUG: #{!!ENV['DEBUG']}]"
end

info

require 'pry'
Pry.start

# Or: 
# require 'irb'
# IRB.start

Now, common tools, helpful hints, and examples of what you might be interested in doing are all presented up front. You can custom tailor this experience to the intended audience. Perhaps puts File.read('../readme.md')? No, we don’t read those!