Spork.trap_method Jujitsu

bbenezech edited this page Apr 2, 2013 · 7 revisions

You can use Spork.trap_method (for instance methods) and Spork.trap_class_method (for class methods) as aggressive jujitsu to prevent portions of your code from being preloaded.

Some of these workarounds may make it into Spork core and be activated by default… but since it’s an aggressive monkey patch on what in many cases is a moving target, it seems best to keep this out until the target settles… or a better way to prevent preloading is worked out with that specific component (coordinating with the gem authors to give controls over when to load project deps?).

What does Spork.trap_{,class_}method do? It makes it so all calls to the method are captured via a lambda and put into a list for later execution (after the process is forked). Since the trapped method isn’t actually executed when it is called, it’s return value is nil. This can, obviously, cause problems if a return value is expected.

Spork.trap_method is provided by spork, but many of the below examples are written Rails 3 context.

Mongoid

Mongoid likes to preload all of your models in rails, making Spork a near worthless experience. It can be defeated with this code, in your Spork.prefork block, before loading config/environment.rb

# Rails 3
Spork.prefork do
  ...
  require "rails/mongoid"
  Spork.trap_class_method(Rails::Mongoid, :load_models)

  # Prevent main application to eager_load in the prefork block (do not load files in autoload_paths)
  Spork.trap_method(Rails::Application, :eager_load!)

  require File.dirname(__FILE__) + "/../config/environment.rb" # <- see this.  Your hackery goes above this.  After this line is too late.

  # Load all railties files
  Rails.application.railties.all { |r| r.eager_load! }
  ...

Devise / Routes / etc

Devise likes to load the User model. We want to avoid this. It does so in the routes file, when calling devise_for :users, :path => "my_account". The solution? Delay route loading.

# Rails 3
Spork.prefork do
  ...
  require "rails/application"
  Spork.trap_method(Rails::Application, :reload_routes!)
  
  require File.dirname(__FILE__) + "/../config/environment.rb"
  ...

Rails 3.1

Routes reloading changed in Rails 3.1 (rc5 as of this writing). Therefore you will need the following instead of the previous Spork.trap_method:

Spork.trap_method(Rails::Application::RoutesReloader, :reload!)

Machinist

Move the blueprint loading to Spork.each_run:

# Rails 3
Spork.each_run do
  Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
end

Factory Girl

Factory Girl 2

Factory Girl 2 does not have the auto-loading issues of previous versions, so you do not need to do anything to get Spork to work. However, if you want changes to factories to take effect on each run, you can reload them like this (in Factory Girl 2.1 or higher).

Spork.each_run do
  FactoryGirl.reload
end

Another thing to watch out for with Factory Girl is when specifying a class for a factory, using a class constant will cause the model to be preloaded in prefork preventing reloading, whereas using a string will not.

Factory.define :user, class: 'MyUserClass' do |f|
  ...
end

Factory Girl 1

Factory Girl 1.x auto-loads the models, so it’s necessary to delay the loading of Factory Girl until each run. To do this, modify your Gemfile so that it doesn’t require factory_girl_rails:

gem 'factory_girl_rails', :require => false

Put the require in the each_run block:

Spork.each_run do
  require 'factory_girl_rails'
end

If this doesn’t work for you, try reloading all of the models after requiring Factory Girl:

Spork.each_run do
  require 'factory_girl_rails'
  Factory.factories.clear 
  # reload all the models
  Dir["#{Rails.root}/app/models/**/*.rb"].each do |model|
    load model
  end
end

I18n

To reload config/locales/*

Spork.each_run do
  I18n.backend.reload!
end

Issue#96

Fabrication

To reload your fabricators:

Spork.each_run do
  Fabrication.clear_definitions
end

Shoulda Matchers

# Rails 3
Spork.prefork do
  ...
  require 'shoulda/matchers/integrations/rspec' # after require 'rspec/rails'
  ...
end

RailsAdmin

Same as Factory Girl, use string references instead of class references in rails_admin.rb to not load your models:

RailsAdmin.config do |config|  
  config.model "Post" do     # not config.model Post
   ...
  end
end

ActiveAdmin

ActiveAdmin loads all the registered models when the routes are loaded. This prevents Spork from loading changes before each run.

To fix this, you can delay route loading:

Spork.prefork do
  ...
  require "rails/application"
  Spork.trap_method(Rails::Application, :reload_routes!) # Rails 3.0
  Spork.trap_method(Rails::Application::RoutesReloader, :reload!) # Rails 3.1

  require File.dirname(__FILE__) + "/../config/environment.rb"
  ...

Working spec_helper.rb examples

Check out working examples here:

Examples