1---
2title: Ruby
3author: Michael Fellinger
4date: 2019-05-23
5---
6
7# Ruby
8
9## User Guide
10
11### Using Ruby
12
13#### Overview
14
15Several versions of Ruby interpreters are available on Nix, as well as over 250 gems and many applications written in Ruby.
16The attribute `ruby` refers to the default Ruby interpreter, which is currently
17MRI 2.5. It's also possible to refer to specific versions, e.g. `ruby_2_6`, `jruby`, or `mruby`.
18
19In the nixpkgs tree, Ruby packages can be found throughout, depending on what
20they do, and are called from the main package set. Ruby gems, however are
21separate sets, and there's one default set for each interpreter (currently MRI
22only).
23
24There are two main approaches for using Ruby with gems.
25One is to use a specifically locked `Gemfile` for an application that has very strict dependencies.
26The other is to depend on the common gems, which we'll explain further down, and
27rely on them being updated regularly.
28
29The interpreters have common attributes, namely `gems`, and `withPackages`. So
30you can refer to `ruby.gems.nokogiri`, or `ruby_2_5.gems.nokogiri` to get the
31Nokogiri gem already compiled and ready to use.
32
33Since not all gems have executables like `nokogiri`, it's usually more
34convenient to use the `withPackages` function like this:
35`ruby.withPackages (p: with p; [ nokogiri ])`. This will also make sure that the
36Ruby in your environment will be able to find the gem and it can be used in your
37Ruby code (for example via `ruby` or `irb` executables) via `require "nokogiri"`
38as usual.
39
40#### Temporary Ruby environment with `nix-shell`
41
42Rather than having a single Ruby environment shared by all Ruby
43development projects on a system, Nix allows you to create separate
44environments per project. `nix-shell` gives you the possibility to
45temporarily load another environment akin to a combined `chruby` or
46`rvm` and `bundle exec`.
47
48There are two methods for loading a shell with Ruby packages. The first and
49recommended method is to create an environment with `ruby.withPackages` and load
50that.
51
52```shell
53nix-shell -p "ruby.withPackages (ps: with ps; [ nokogiri pry ])"
54```
55
56The other method, which is not recommended, is to create an environment and list
57all the packages directly.
58
59```shell
60nix-shell -p ruby.gems.nokogiri ruby.gems.pry
61```
62
63Again, it's possible to launch the interpreter from the shell. The Ruby
64interpreter has the attribute `gems` which contains all Ruby gems for that
65specific interpreter.
66
67##### Load environment from `.nix` expression
68
69As explained in the Nix manual, `nix-shell` can also load an expression from a
70`.nix` file. Say we want to have Ruby 2.5, `nokogori`, and `pry`. Consider a
71`shell.nix` file with:
72
73```nix
74with import <nixpkgs> {};
75ruby.withPackages (ps: with ps; [ nokogiri pry ])
76```
77
78What's happening here?
79
801. We begin with importing the Nix Packages collections. `import <nixpkgs>`
81 imports the `<nixpkgs>` function, `{}` calls it and the `with` statement
82 brings all attributes of `nixpkgs` in the local scope. These attributes form
83 the main package set.
842. Then we create a Ruby environment with the `withPackages` function.
853. The `withPackages` function expects us to provide a function as an argument
86 that takes the set of all ruby gems and returns a list of packages to include
87 in the environment. Here, we select the packages `nokogiri` and `pry` from
88 the package set.
89
90##### Execute command with `--run`
91
92A convenient flag for `nix-shell` is `--run`. It executes a command in the
93`nix-shell`. We can e.g. directly open a `pry` REPL:
94
95```shell
96nix-shell -p "ruby.withPackages (ps: with ps; [ nokogiri pry ])" --run "pry"
97```
98
99Or immediately require `nokogiri` in pry:
100
101```shell
102nix-shell -p "ruby.withPackages (ps: with ps; [ nokogiri pry ])" --run "pry -rnokogiri"
103```
104
105Or run a script using this environment:
106
107```shell
108nix-shell -p "ruby.withPackages (ps: with ps; [ nokogiri pry ])" --run "ruby example.rb"
109```
110
111##### Using `nix-shell` as shebang
112
113In fact, for the last case, there is a more convenient method. You can add a
114[shebang](https://en.wikipedia.org/wiki/Shebang_(Unix)) to your script
115specifying which dependencies `nix-shell` needs. With the following shebang, you
116can just execute `./example.rb`, and it will run with all dependencies.
117
118```ruby
119#! /usr/bin/env nix-shell
120#! nix-shell -i ruby -p "ruby.withPackages (ps: with ps; [ nokogiri rest-client ])"
121
122require 'nokogiri'
123require 'rest-client'
124
125body = RestClient.get('http://example.com').body
126puts Nokogiri::HTML(body).at('h1').text
127```
128
129### Developing with Ruby
130
131#### Using an existing Gemfile
132
133In most cases, you'll already have a `Gemfile.lock` listing all your dependencies.
134This can be used to generate a `gemset.nix` which is used to fetch the gems and
135combine them into a single environment.
136The reason why you need to have a separate file for this, is that Nix requires
137you to have a checksum for each input to your build.
138Since the `Gemfile.lock` that `bundler` generates doesn't provide us with
139checksums, we have to first download each gem, calculate its SHA256, and store
140it in this separate file.
141
142So the steps from having just a `Gemfile` to a `gemset.nix` are:
143
144```shell
145bundle lock
146bundix
147```
148
149If you already have a `Gemfile.lock`, you can simply run `bundix` and it will
150work the same.
151
152To update the gems in your `Gemfile.lock`, you may use the `bundix -l` flag,
153which will create a new `Gemfile.lock` in case the `Gemfile` has a more recent
154time of modification.
155
156Once the `gemset.nix` is generated, it can be used in a
157`bundlerEnv` derivation. Here is an example you could use for your `shell.nix`:
158
159```nix
160# ...
161let
162 gems = bundlerEnv {
163 name = "gems-for-some-project";
164 gemdir = ./.;
165 };
166in mkShell { buildInputs = [ gems gems.wrappedRuby ]; }
167```
168
169With this file in your directory, you can run `nix-shell` to build and use the gems.
170The important parts here are `bundlerEnv` and `wrappedRuby`.
171
172The `bundlerEnv` is a wrapper over all the gems in your gemset. This means that
173all the `/lib` and `/bin` directories will be available, and the executables of
174all gems (even of indirect dependencies) will end up in your `$PATH`.
175The `wrappedRuby` provides you with all executables that come with Ruby itself,
176but wrapped so they can easily find the gems in your gemset.
177
178One common issue that you might have is that you have Ruby 2.6, but also
179`bundler` in your gemset. That leads to a conflict for `/bin/bundle` and
180`/bin/bundler`. You can resolve this by wrapping either your Ruby or your gems
181in a `lowPrio` call. So in order to give the `bundler` from your gemset
182priority, it would be used like this:
183
184```nix
185# ...
186mkShell { buildInputs = [ gems (lowPrio gems.wrappedRuby) ]; }
187```
188
189
190#### Gem-specific configurations and workarounds
191
192In some cases, especially if the gem has native extensions, you might need to
193modify the way the gem is built.
194
195This is done via a common configuration file that includes all of the
196workarounds for each gem.
197
198This file lives at `/pkgs/development/ruby-modules/gem-config/default.nix`,
199since it already contains a lot of entries, it should be pretty easy to add the
200modifications you need for your needs.
201
202In the meanwhile, or if the modification is for a private gem, you can also add
203the configuration to only your own environment.
204
205Two places that allow this modification are the `ruby` derivation, or `bundlerEnv`.
206
207Here's the `ruby` one:
208
209```nix
210{ pg_version ? "10", pkgs ? import <nixpkgs> { } }:
211let
212 myRuby = pkgs.ruby.override {
213 defaultGemConfig = pkgs.defaultGemConfig // {
214 pg = attrs: {
215 buildFlags =
216 [ "--with-pg-config=${pkgs."postgresql_${pg_version}"}/bin/pg_config" ];
217 };
218 };
219 };
220in myRuby.withPackages (ps: with ps; [ pg ])
221```
222
223And an example with `bundlerEnv`:
224
225```nix
226{ pg_version ? "10", pkgs ? import <nixpkgs> { } }:
227let
228 gems = pkgs.bundlerEnv {
229 name = "gems-for-some-project";
230 gemdir = ./.;
231 gemConfig = pkgs.defaultGemConfig // {
232 pg = attrs: {
233 buildFlags =
234 [ "--with-pg-config=${pkgs."postgresql_${pg_version}"}/bin/pg_config" ];
235 };
236 };
237 };
238in mkShell { buildInputs = [ gems gems.wrappedRuby ]; }
239```
240
241And finally via overlays:
242
243```nix
244{ pg_version ? "10" }:
245let
246 pkgs = import <nixpkgs> {
247 overlays = [
248 (self: super: {
249 defaultGemConfig = super.defaultGemConfig // {
250 pg = attrs: {
251 buildFlags = [
252 "--with-pg-config=${
253 pkgs."postgresql_${pg_version}"
254 }/bin/pg_config"
255 ];
256 };
257 };
258 })
259 ];
260 };
261in pkgs.ruby.withPackages (ps: with ps; [ pg ])
262```
263
264Then we can get whichever postgresql version we desire and the `pg` gem will
265always reference it correctly:
266
267```shell
268$ nix-shell --argstr pg_version 9_4 --run 'ruby -rpg -e "puts PG.library_version"'
26990421
270
271$ nix-shell --run 'ruby -rpg -e "puts PG.library_version"'
272100007
273```
274
275Of course for this use-case one could also use overlays since the configuration
276for `pg` depends on the `postgresql` alias, but for demonstration purposes this
277has to suffice.
278
279#### Adding a gem to the default gemset
280
281Now that you know how to get a working Ruby environment with Nix, it's time to
282go forward and start actually developing with Ruby.
283We will first have a look at how Ruby gems are packaged on Nix. Then, we will
284look at how you can use development mode with your code.
285
286All gems in the standard set are automatically generated from a single
287`Gemfile`. The dependency resolution is done with `bundler` and makes it more
288likely that all gems are compatible to each other.
289
290In order to add a new gem to nixpkgs, you can put it into the
291`/pkgs/development/ruby-modules/with-packages/Gemfile` and run
292`./maintainers/scripts/update-ruby-packages`.
293
294To test that it works, you can then try using the gem with:
295
296```shell
297NIX_PATH=nixpkgs=$PWD nix-shell -p "ruby.withPackages (ps: with ps; [ name-of-your-gem ])"
298```
299
300#### Packaging applications
301
302A common task is to add a ruby executable to nixpkgs, popular examples would be
303`chef`, `jekyll`, or `sass`. A good way to do that is to use the `bundlerApp`
304function, that allows you to make a package that only exposes the listed
305executables, otherwise the package may cause conflicts through common paths like
306`bin/rake` or `bin/bundler` that aren't meant to be used.
307
308The absolute easiest way to do that is to write a
309`Gemfile` along these lines:
310
311```ruby
312source 'https://rubygems.org' do
313 gem 'mdl'
314end
315```
316
317If you want to package a specific version, you can use the standard Gemfile
318syntax for that, e.g. `gem 'mdl', '0.5.0'`, but if you want the latest stable
319version anyway, it's easier to update by simply running the `bundle lock` and
320`bundix` steps again.
321
322Now you can also also make a `default.nix` that looks like this:
323
324```nix
325{ lib, bundlerApp }:
326
327bundlerApp {
328 pname = "mdl";
329 gemdir = ./.;
330 exes = [ "mdl" ];
331}
332```
333
334All that's left to do is to generate the corresponding `Gemfile.lock` and
335`gemset.nix` as described above in the `Using an existing Gemfile` section.
336
337##### Packaging executables that require wrapping
338
339Sometimes your app will depend on other executables at runtime, and tries to
340find it through the `PATH` environment variable.
341
342In this case, you can provide a `postBuild` hook to `bundlerApp` that wraps the
343gem in another script that prefixes the `PATH`.
344
345Of course you could also make a custom `gemConfig` if you know exactly how to
346patch it, but it's usually much easier to maintain with a simple wrapper so the
347patch doesn't have to be adjusted for each version.
348
349Here's another example:
350
351```nix
352{ lib, bundlerApp, makeWrapper, git, gnutar, gzip }:
353
354bundlerApp {
355 pname = "r10k";
356 gemdir = ./.;
357 exes = [ "r10k" ];
358
359 buildInputs = [ makeWrapper ];
360
361 postBuild = ''
362 wrapProgram $out/bin/r10k --prefix PATH : ${lib.makeBinPath [ git gnutar gzip ]}
363 '';
364}
365```