Living in sin (with Spring)
Tags: ruby, rails
Published: 2018-06-19 09:33:14

---
title: Living in sin (with Spring)
published: true
canonical_url: https://evilmartians.com/chronicles/living-in-sin-with-spring
description: A couple of tricks on using Spring in Ruby development
tags: ruby,rails
---

[Spring](https://github.com/rails/spring) is a very popular tool in Rails application development (especially since it [has been added](https://github.com/rails/rails/pull/12958) to the Rails default Gemfile).

Your codebase and the number of dependencies grow the application boot time increases as well. It could take a dozen seconds before `rails c` becomes responsive.

Spring tries to solve this problem by loading application code once and reloading it when necessary. Why "tries"? Well, because sometimes it fails to reflect the changes.

For example, Spring couldn't detect anything except the code changes: environmental variables, randomization seeds, etc..

Let me share some tricks on working with Spring.

## Spring vs. external dependencies

Suppose that you store some configuration in a file called `config/my_config.txt`.

Spring won't reload the application if you change this file unless you explicitly tell it to do that:

```ruby
# config/spring.rb
Spring.watch "config/my_config.txt"
```

Easy.

What if your configuration depends on something _unwatchable_? Consider an example:

```ruby
# development.rb
Rails.application.configure do
  # We want to dump the structure.sql only if there are any
  # uncommitted changes migrations (i.e. do not re-generate the dump when
  # you're running migrations written by someone else)  
  res = `git status db/migrate/ --porcelain`
  ActiveRecord::Base.dump_schema_after_migration = res.nil? || res.present?
end
```

The value of `ActiveRecord::Base.dump_schema_after_migration` is populated once on the initial load. What if you add a migration later? The dump won't be generated.

To handle this situation, we must use `Spring.after_fork` callback:

```ruby
Spring.after_fork do
  res = `git status db/migrate/ --porcelain`
  ActiveRecord::Base.dump_schema_after_migration = res.nil? || res.present?
end
```

Cool! That works!

But what if someone in your team not using Spring? We should take care of it:

```ruby
if defined?(::Spring::Application)
  Spring.after_fork do
    res = `git status db/migrate/ --porcelain`
    ActiveRecord::Base.dump_schema_after_migration = res.nil? || res.present?
  end
else
  res = `git status db/migrate/ --porcelain`
  ActiveRecord::Base.dump_schema_after_migration = res.nil? || res.present?
end
```

**NOTE:** you've probably noticed that we're checking for the presence of `::Spring::Application` constant and not just `::Spring`. It [turned out](https://github.com/palkan/test-prof/issues/47#issuecomment-358310037) that some gems also initiate the `::Spring` constant even without `spring` itself.


## Spring vs. RSpec

[RSpec](http://rspec.info) has a feature of running tests in a random order.

How does it work?

It generates a random seed **on load** and [uses it later]
(https://github.com/rspec/rspec-core/blob/v3.7.1/lib/rspec/core/ordering.rb#L25-L30) to order the tests.

When running tests with Spring, this seed value is not reloading–your tests order is always the same (note: while Spring server is running)!

Hopefully, we already know how to fix this:


```ruby
if defined?(::Spring::Application)
  Spring.after_fork do
    RSpec.configure do |config|
      # Make sure that seed value is randomized for each test run
      config.seed = rand(0xFFFF)
    end
  end
end
```

That snippet should be probably included into the [default `rails_helper.rb`](https://github.com/rspec/rspec-rails/blob/master/lib/generators/rspec/install/templates/spec/rails_helper.rb) by default.


## .offspring


Unfortunately, it's almost impossible to avoid such caveats when using Spring.

<img width="70%" src="https://thepracticaldev.s3.amazonaws.com/i/hs93c4yrksfk08d9dtsl.png">

*["Run Test Run" at RubyConfBy 2017](https://www.youtube.com/watch?v=q52n4p0wkIs)

Nowadays I prefer to disable it entirely (and rely on [`bootsnap`](https://github.com/Shopify/bootsnap) instead).

What if other developers on your team are still in love with Spring? We need a way to disable it _locally_.

Spring provides a way to do that out-of-the-box: set `DISABLE_SPRING=1` environment variable and you're done.

This approach has some disadvantages:
- you cannot disable Spring per-project
- it's not clear whether is's disabled or not; you have to run a command to see the env var value
- it doesn't play well with containers.

I found a much better (IMO) way to do that: toggle the Spring state depending on the presence of `.offspring` file in your project root.

All you need is to add a single line to your `bin/spring`:

```ruby
!/usr/bin/env ruby

ENV['DISABLE_SPRING'] = '1' if File.exists?(File.join(__dir__, '../.offspring'))

# ...
```

Now you can enable/disable Spring with the following commands:

```
$ touch .offspring #=> disables Spring
$ rm .offspring #=> enables Spring
```

That's it!

-----

Read more dev articles on https://evilmartians.com/chronicles!