Ruby - Getting Started
I tried REPL with Kotlin and Java, both sucked: REPL with Kotlin is deprecated, unsupported and getting removed; REPL with Java is just bloated. Gave up. I’m becoming a huge fan of the “No tools” movement - use as few tools as possible. I believe this approach leads to simplicity. Tools lead you to the vicious cycle of complexity: tools hide the complexity away from you, which means you’re getting away with complexity, which means it creeps in, and you solve that with more tools. That also means that Jetbrains has an incentive to make Kotlin as complex and feature-rich as they can get away with, since their business model is to sell you tools. I’m starting to get disinterested in that, I’m fed up with Merchants With Complexity. No-tools also simplifies the ramp-up for newbies: imagine a newbie having to study Maven/Gradle, Intellij just to run Hello World app and then do something just a tiny bit more complex (like parsing YAML/JSON/XML or such).
Anyways, REPL in Ruby is so simple. To remove blank lines and comments from a file, you just need to run:
$ ruby -ne 'puts $_ unless $_.strip.empty? or $_.lstrip.start_with? "#"' <file
Autocompletion? Works! Run irb, type in "foo". and press TAB to cycle through stuff.
The simplest way to get Ruby is from your distro:
$ sudo apt install ruby ruby-bundler
It’s not the newest Ruby, but it’s good enough, and it gets patched and updated along with everything else on the machine.
Additionally, I recommend to set up the build toolchain, which is required
to install gems not via apt, but via gem command:
$ sudo apt install ruby-dev make gcc libffi-dev libssl-dev libyaml-dev
ruby-devbrings the headers and themkmfmachinery that native gems need in order to compile. Without it, every gem with a C extension fails to build.makeandgcccompile native gem parts
Fast
Ruby VM is so fast to start, it’s not even funny. Sure, Java programs start fast too, unless you’re running them from Gradle - and then Gradle either takes 2 seconds to run, or takes shitload of RAM for Gradle Daemon. This has also huge advantage with GitHub Actions: Ruby springs to life and Rake builds your project so fast, the entire thing is done in 10 seconds. Compared to that, Gradle builds always take at least 1 minute.
No Tooling Required
irb auto-completion is brilliant, and it comes straight with Ruby - no need to install tools or anything else.
You only need a text editor and irb to get started. Excellent. rake is also baked in, so you can quickly start
writing build scripts.
REPL is supported out-of-the-box. Not just that - Ruby is perfect for small scripts, possibly replacing bash.
gems
At some point you’ll need gems, for example when generating documentation via YARD. This exposes the fact that many Ruby gems are half-implemented in C and require some tooling to update. I don’t mind: I won’t be studying their sources, and I’m not forced to write C, so I’m good.
Where the gems go
Run bundle install in a fresh project and it blows up:
Bundler::PermissionError: There was an error while trying to write to
`/var/lib/gems/3.3.0/cache`. It is likely that you need to grant write
permissions for that path.
By default Bundler installs gems into the system-wide gem dir, /var/lib/gems/3.3.0, which is owned by root.
Don’t reach for sudo here: that would dump your project’s dependencies into the directory apt manages.
Instead point Bundler at your HOME, once, globally:
$ bundle config set --global path ~/.gem
That writes BUNDLE_PATH: "/home/you/.gem" into ~/.bundle/config and applies to every project from now on;
gems end up in ~/.gem/ruby/3.3.0/. bundle install now runs happily as a plain user.
ruby-lsp, and which folder goes on the PATH
bundle config path only governs Bundler; gem install has its own idea of where things go. It won’t fail
though - as a non-root user it defaults to the per-user gem dir:
$ gem install ruby-lsp
Defaulting to user installation because default installation directory (/var/lib/gems/3.3.0) is not writable.
WARNING: You don't have /home/mavi/.gem/ruby/3.3.0/bin in your PATH,
gem executables (ruby-lsp, ruby-lsp-check, ruby-lsp-launcher, ruby-lsp-test-exec) will not run.
So ~/.gem/ruby/3.3.0/bin is the folder to add. Two things not to hardcode there: 3.3.0 tracks your Ruby
version, and the ~/.gem part isn’t even fixed. RubyGems picks the user dir like this
(rubygems/defaults.rb):
gem_dir = File.join(Gem.user_home, ".gem")
gem_dir = File.join(Gem.data_home, "gem") unless File.exist?(gem_dir)
~/.gem wins if it already exists, otherwise you get the XDG location, ~/.local/share/gem/ruby/3.3.0.
Which means the bundle config step above quietly decides this for you: pointing Bundler at ~/.gem creates
that folder, and from then on gem install lands in the very same tree. Handy - one dir, one PATH entry.
Just ask RubyGems instead of guessing. In ~/.bashrc:
export PATH="$(gem env user_gemhome)/bin:$PATH"
or, for fish, in ~/.config/fish/config.fish:
command -q gem; and fish_add_path (gem env user_gemhome)/bin
The command -q gem guard is there only to keep the startup quiet if Ruby ever isn’t installed - otherwise
fish greets you with Unknown command: gem on every new shell. Asking RubyGems costs some 70ms of Ruby VM
startup per shell; my fish starts in under 100ms with this in place, so I can live with that.
You may be tempted to skip the Ruby startup and simply glob the thing:
fish_add_path ~/.gem/ruby/*/bin # tempting, but no
That does work - as long as it matches. Two ways it bites you:
- An unmatched wildcard is a hard error in fish, not a silent skip. Before you install your first gem,
every new shell opens with
No matches for wildcard '~/.gem/ruby/*/bin'; fish skips that line and carries on, but you get the noise.fish_add_path (path filter -d ~/.gem/ruby/*/bin)is the quiet form - thepathbuiltin tolerates unmatched globs, andfish_add_pathwith zero arguments does nothing at all. fish_add_pathpersists what it’s given into the universalfish_user_pathsvariable, and never prunes it. So the glob isn’t really re-evaluated on every startup, the way the config line makes it look: bump Ruby to 3.4 and you keep a dead~/.gem/ruby/3.3.0/binon your PATH forever.gem env user_gemhomealways names the one dir that’s actually live.
Updating built-in gems (NOT NECESSARY)
This is not really necessary: Ruby projects use Bundler, and Bundler can install all necessary gems itself. Many gems aren’t half-implemented in C, and therefore do not require any additional tooling installed.
To update default gems:
$ gem update
Gem updates will be installed into your per-user gem folder (see above) - the system dir is left alone.
Toy project
Check out koans-ruby which also demoes rake, bundler, YARD and also shows how to run all that from GitHub Actions.