Ruby tooling and tricks of the trade

15 minute read

Ruby tooling and tricks of the trade

This is a blog post version of a presentation that I gave at my company Netlify for a Ruby learning group. The material is slightly modified and compacted and to not mention the internal code base. In this presentation I share some of my favourite tooling and tricks for Ruby development.

General

Ruby version management

  • You probably use something for Ruby already like RVM, rbenv, chruby
    • And nvm for Node, gvm Golang, pyenv for Python
    • Many tools to remember, different command syntax
  • asdf

Using the right version

  • With multiple rubies and gem versions installed, how do you know that $ rake in your project is the right version?
    • You don’t!
  • Do:
    • $ bundle exec rake, or
    • create a binstub *
      $ bundle binstubs rake
      $ git add bin/rake && git commit...
      $ bin/rake
      

Self-contained scripts

  • Need to distribute an minimal example of something with test input data
    • Demonstrating a bug maybe.
  • You can bundle a ruby program + data at the end after __END__ *
    DATA.each_line.map(&:chomp).each do |url|
      puts url
    end
    __END__
    https://erikw.me/
    https://erikw.me/blog/
    https://erikw.me/projects/
    

Gems for Development

Rake - the Ruby Make

  • Rake is a tool for task management and build automation. Ruby files -> programmatically interact with other gems, very powerful.
  • But there are no complex header inclusion and linking etc. like in C/C++, why would I need a Make-like too?

Example tasks

# run as $ rake hello
desc 'hello example task'
task :hello do
	puts "Hello there"
end

Rake::TestTask.new do |task|
	task.name = "test_internal"
	task.description = "Run internal tests"
	task.libs << "test"
	task.test_files = FileList['test/**/*_test.rb'].reject { |t| t == 'test/official_test.rb' }
end
  • While you can use it with automatic rules like in a Makefile (*.o <- *.c & *.h), it find it most useful to
    • Standard build instructions across projects. If I see a Rakefile in a project, I can assume the default task is going to be similar to make all or that there’s a rake test task.
    • Task dependencies: e.g. run tests before bundle up and publish as a Gem.
    • Simplify complex command invocation e.g. setting right environment variables.
  • E.g. in my personal website, I have these rake tasks: *
      $ bundle exec rake -T
      rake build                  # Build website
      rake ci                     # Build steps to be used by ci runner
      rake clean                  # Clean up generated files
      rake htmlproof              # Validates HTML files wit htmlproofer..
      rake is_website_vulnerable  # Check deployed website for JavaScript vulnerabilities with...
      rake lighthouse             # Generate and open a lighthouse report against deployed site
      rake notify                 # Notify various services about new content
      rake pingomatic             # Ping pingomatic
      rake pingpubsubhubbub       # Notify pubsubhubbub server
      rake serve                  # Build and serve website and watch for changes on filesystem
      rake sitemapbing            # Notify Bing of the new sitemap
      rake sitemapgoogle          # Notify Google of the new sitemap
      rake test                   # Run automated tests
    

Guard - a gem for (automatically) running things

Guard automates various tasks by running custom rules whenever file or directories are modified. It’s frequently used by software developers, web designers, writers and other specialists to avoid mundane, repetitive actions and commands such as “relaunching” tools after changing source files or configurations.

  • Enables fast development.
    • I like to develop fast! Switching around in tmux between multiple sessions, windows and panes, executing commands, back to editor, re-run test, wamp! Brings back memories of old LAN-party coding nights in University, with a bit too many energy drinks and coding all night.
  • guard-minitest is a Guard module that automatically runs corresponding tests when a source file changes. For example changing the app/models/user.rb model should run the corresponding unit, functional and integration tests for the model (like test/unit/models/user_test.rb, test/integration/api/v1/users_controller_test.rb, test/integration/api/hero/users_controller_test.rb etc.)
  • Exist 300+ plugins for many task:
    • Auto install gems when update Gemfile
    • Auto-run rubocop
    • Auto-regen documentation
    • Auto-restart local webserver
  • Why good?
    • Quickly detect bad changes
    • The tool knows more which tests to run than you do (maybe)
    • Too long time to run all tests locally
    • Still quicker than wait for running all tests in CircleCI
  • Why not?
    • Don’t rely on it, will not know now all tests to run.

Solargraph + LSP client in editor/IDE

  • LSP - Language Server Protocol
  • solargraph is a great LSP server for Ruby.
  • Get code completion, jump to definition, find usages, show function documentation
  • I use NeoVim + ALE

Rubocop - code style checker and formatter

  • https://rubocop.org/
  • On CLI (warn or autocorrect -a safe changes)
  • CI build (fail build)
  • PRs (exist integrations that post warnings in PR)
  • In editor/IDE for auto format code
    • Like gofmt tool
  • Exist extensions for gemspecs, Rails etc.
  • You can create own style checks. We have netlify/rubocop-netlify

Yard - easy and pretty docs

It’s easy to auto generate good looking docs for Ruby.

  1. Write code comments above methods as usual, using e.g. rDoc syntax.
  2. Chose a doc generation tool like yard or rdoc. I found yard easier and more flexible to work with.
    • Optionally configure with file to exclude, custom title etc.
  3. Use a Rake task to auto generate on a normal build -> dir with HTML
    • Use Guard to live preview
  4. Public repo? Get it auto build and published to [rubydoc.info]https://www.rubydoc.info/

REPL / Debugging tooling

PRY - developer console

  • Pry: IRB on steroids
  • Syntax highlighting, doc browser, source inspect
  • In the same way ipython is more pleasing than the plain python REPL.

Bye bye bugs

  • Byebug
    • Adds debugging commands familiar from GDB-like programs:
      • break
      • step
      • next
      • continue
      • frame
      • threads
  • byebug in source code will break and drop to REPL shell.
    • Also known as debugger.

Pry + Byebug

  • pry-byebug: Debug with Byebug but with power of Pry
    • extends Pry with debugger commands like step, next, continue.
    • See this blog post for a good explanation.

minitest-byebug

  • minitest-byebug
  • Drops to REPL shell where a minitest testcase fails
  • In my projects I put this in my test_helper.rb to easily enable when needed. Note; it’s not practical to drop to REPL when multiple tests are running (and failing), thus I enable this usually when running only one test. *
    # Start as: $ DEBUG=1 rake test
    if ENV['DEBUG'] || ENV['D']
      val = ENV['DEBUG'] || ENV['D']
      require 'minitest/byebug' if %w[yes true 1].include? val
    end
    

Pretty printing

  • > pp someObject is good, but we can do better, with better indentation & colors!
  • amazing_print
    • > ap someObject

What record is this?

  • Get a sense of how an ActiveRecord (ruby on rails) looks; just type the class name.
  • If you don’t know the class name, retrieve it:
    • > rec.class

Finding that object’s method

  • Finding methods on an object, when you know roughly what you look for.
  • Say you have an ActiveRecord instance and you know there is some sort of Kubernetes method in there but not the exact name.
  • ActiveRecords have many (!!) methods and even > pp rec.public_methods can be hard to read.
  • My standard method is to find it like this:
    • > rec.public_methods(false).grep(/kubernetes/)
    • false ignores inherited methods.

Shell

Quickly invoke specific test(s)

  • Did I already say that I love working fast in the CLI?
    • The default rails test invocation is not supporting this:
    • $ bin/rails test test/unit/model/users_test.rb -n test_should_not_be_valid_without_email
  • My t test shell function for rails testing
    • quickest (?) way to run a specific test
    • t path/to/some_test.rb some_method
    • t path/to/some_test.rb /pattern/

Still there are long file pathsto type, we can fix that, with …

fzf - fuzzy finding is a game-changer for CLI

  • fzf - Quickly find src/test to edit or run
    • $ t test/**<tab>
  • Search/complete shell history to re-run tests
    • I want to rerun that test named something with “config” that I had yesterday, what was it now again? - fzf it!
  • Exist many fzf plugins to editors and other tools like vim and tmux

cd(1) to gem source code

  • My bundlecd shell function - cd to the version of a gem used in current project
  • Useful to see what that gem is really doing, or even adding a debug breakpoint…
$ cd my-project/
$ bundle list
[...]
* somegem (1.0.0 518bb08)
$ bundlecd somegem
$ pwd
~/[...]/lib/ruby/gems/3.2.0/bundler/gems/somegem-518bb081f351

Editor / IDE

Invest some time in your editor tooling

There’s a great ruby config or plugin out there for your favourite editor or IDE – find it and leverage for your productivity and convenience!

src<>test navigator

  • Get an extension that lets you navigate between
    • implementation<>test
    • implementation<>related file (e.g. model → controller, serializer→model a.s.o.)
  • (neo)Vim:

Test coverage integration

  • Simplecov is maybe the most known code coverage tool for ruby?
    • It outputs a coverage report files that can be read by other tools.
  • nvim-coverage is one such tool, showing directly in the neovim editor which lines are covered and not.

Snippets

Create helpful snippets e.g. I have d to set to expand to debugger breakpoint (to support my fast workflow).

Collaboration - git provider plugins

  • Extension to get link to source on GitHub to share in PR/Slack

Closing

The Ruby ecosystem have it all, and it’s a real pleasure to work in and with it.

Leave a comment

Your email address will not be published. Required fields are marked *

Loading...