Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #9943 (closed enhancement: fixed)

Opened 1 year ago

Last modified 1 year ago

[PATCH] How about a pre-initialization hook in boot.rb?

Reported by: thewoolleyman Assigned to: bitsweat
Priority: normal Milestone:
Component: Railties Version: edge
Severity: normal Keywords: boot
Cc: thewoolleyman@gmail.com

Description

NOTE: This patch will conflict with [9834], but it's perfectly compatible.

Description taken from rubyonrails-core list post on 10/21/07:

Subject:

Howdy,

Please take a look at Enhancement [xxx] and see what you think. Here's the pitch for this little 5-line patch:

Even though boot.rb has the familiar "Don't change this file..." message, sometimes I want to.

This would allow dynamic control over RAILS_GEM_VERSION and the Version Specification passed to the RubyGems 'gem' method for the rails gem. For example, users could test an app or plugin in a Continuous Integration environment which performs automated regression testing against various old Rails versions.

This patch would also to facilitate my GemInstaller gem, which centrally manages the installation and loading of all gem dependencies and versions, including Rails. Currently, I have to tell people (1) to ignore the dire "Don't change this file..." warning and hack boot.rb if they want to manage the Rails gem version along with the rest of their gems. This is not compatible with Rails upgrades which modify boot.rb, such as changeset [7832] and ticket [9834]; users will have to re-modify boot.rb after upgrading.

So, whaddaya think? I'm happy to rewrite this patch however you would like. I thought the simplest and most flexible approach would be to mirror the config/initializers directory approach, which is done in just 5 lines. If you don't like the extra config/preinitializers directory, it could just look for a single hook file like config/preinitializer.rb. This would be one less default dir in /config, but it would be less flexible. For example, I couldn't just have GemInstaller dump a provided geminstaller_preinitilizer.rb file into config/preinitializers, I'd have to modify the single hook file. If it's OK to have an extra dir in config, though, I think the current approach is best

One other note. This patch is untested, because boot.rb would require some refactoring to be testable, and I didn't want to overengineer this patch. For an example of how boot.rb could be more made more testable, see [9834]. We could even pull out all of the logic from boot.rb into a class, which would be much easier to test in isolation. I'm definitely up for this if you are interested, but I wanted to do The Simplest Thing That Could Possibly Work as the first cut.

Thanks, -- Chad W.

(1) http://geminstaller.rubyforge.org/documentation/tutorials.html#integrating_geminstaller_into_ruby_on_rails

Attachments

rails_preinitializer_hook_ticket_9943.diff (1.2 kB) - added by thewoolleyman on 10/21/07 08:22:23.
rails_preinitializer_hook_ticket_9943.diff
rails_preinitializer_hook_ticket_9943.version2.diff (1.5 kB) - added by thewoolleyman on 10/23/07 06:16:31.
version 2 of this patch, updated after [7998]
rails_preinitializer_hook_ticket_9943.version3_with_tests.diff (2.4 kB) - added by thewoolleyman on 10/31/07 11:06:02.
version 3, now with tests.
ticket_9943_preinitializer_hook_failing_test_scenario.txt (1.0 kB) - added by thewoolleyman on 10/31/07 11:29:13.
description of test scenario which is not currently possible without this patch
rails_preinitializer_hook_ticket_9943.version4.diff (2.0 kB) - added by thewoolleyman on 11/12/07 18:51:23.
version 4, no more config/preinitializers dir, but hardcoded to load config/preinitializer.rb if it exists

Change History

10/21/07 08:20:55 changed by thewoolleyman

Oops, got link wrong above, here's ticket #9834

10/21/07 08:22:23 changed by thewoolleyman

  • attachment rails_preinitializer_hook_ticket_9943.diff added.

rails_preinitializer_hook_ticket_9943.diff

10/23/07 05:42:09 changed by bitsweat

Cool, I extracted and tested a Rails::Boot after working with your tests for #9834: see [7998].

10/23/07 05:47:32 changed by bitsweat

  • keywords set to boot.
  • owner changed from core to bitsweat.

A simple naming convention like config/initializers/preboot.rb is sufficient and feels appropriate.

Could you show an example of what you'd put in this hook?

10/23/07 05:51:26 changed by bitsweat

Another option is an environment variable pointing to a file or directory to load before boot.

The simplest path for your existing users is to edit environment.rb to require 'myboot' instead which does some setup then requires 'boot' itself.

10/23/07 06:16:31 changed by thewoolleyman

  • attachment rails_preinitializer_hook_ticket_9943.version2.diff added.

version 2 of this patch, updated after [7998]

10/23/07 06:48:17 changed by thewoolleyman

Hi,

Thanks for looking at this. I'll try to answer your questions and point out concerns.

1. An example of what I'd put in this hook, and notes:

  GemInstaller.run(...)
  GemInstaller.autogem(...)
  • This would automatically install and load all dependency gems specified in config/geminstaller.yml, including Rails itself. See the detailed GemInstaller docs for more info.
  • It's key that this occurs *BEFORE* Rails::GemBoot#load_rails_gem, in order for the preinitialize hook to control the Rails gem version(s).
  • Note that this approach is very flexible. It could be used to dynamically determine which versions of any of the Rails gems should be loaded - even locally generated and customized gems.

1. Re: "A simple naming convention like config/initializers/preboot.rb is sufficient and feels appropriate." If you can live with the existence of a default 'config/preinitializers' dir, I think it would be better, because:

  1. You don't want the pre-initializer mixed in with the initializers. They do different things - preinit would be doing things like auto-installing and loading gems. You want this done before Initializer, but *NOT* during.
  2. To avoid this problem, you'd need extra logic to ignore 'preboot.rb'. Why not just keep it separate?
  3. A dedicated config/preinitializers directory would allow multiple, unrelated preinitializer hooks to co-exist and auto-update themselves without worry of interfering with each other or clobbering each other.
  4. It would be easier to automate installation of preinit hooks if they can be in a custom named file config/preinitializers/my_hook.rb. Think about the issues faced with installing hook code into a single file. What do I do if it exists? Blow it away? Append?

1. Re: "Another option is an environment variable pointing to a file or directory to load before boot."

  • If you point to a file, this has the same issues as the single-hookfile approach mentioned above.
  • If you point to a dir, it's a little better. However, this is still "external" to the rails app and not completely self-contained like the config/preinitializers hook dir. The user would have to always ensure they set the environment variable correctly in all scenarios - development, testing, production, etc. It's easier on the users to just know that the existence of a hook in config/preinitializers will always work the same in all situations with no need to set an environment var.

1. Re: "The simplest path for your existing users is to edit environment.rb to require 'myboot' instead which does some setup then requires 'boot' itself."

  • No, it should be *BEFORE* Rails::GemBoot#load_rails_gem. environment.rb is not evaluated until the Rails gems and RAILS_GEM_VERSION are already determined and Initializer.run is already underway.

Thx again! -- Chad

10/23/07 06:57:26 changed by thewoolleyman

Oh, I just realized I was wrong about this:

1. Re: "The simplest path for your existing users is to edit environment.rb to require 'myboot' instead which does some setup then requires 'boot' itself."

You are correct, I could have users edit environment.rb. This is a simpler solution, but still requires environment file hacking. The preinitializers directory approach would allow an auto-install script to install a hook without requiring the user to modify their environment file. It is a viable workaround, though.

10/23/07 07:04:34 changed by thewoolleyman

Wait - I take that back. I don't think putting a hook in environment.rb before the require of boot.rb will work.

Look at the server script:

#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'
require 'commands/server'

The first thing it does is load boot.rb, not environment.rb. So, the preinit hook has to be in boot. I'm betting mongrel works the same way. Or am I missing something?

10/25/07 21:00:07 changed by thewoolleyman

copying comment from mailing list:

On 10/25/07, Michael Koziarski <michael@koziarski.com> wrote:

I would love to see this patch (or a similar one) make it in, as we are dealing with this very issue with a tool we built test our apps and plugins against multiple versions of Rails in our CI environment. We went with the rubygem approach because it lets use gems for version management, but we are hitting issues in apps due to the way the boot process works.

What are the other issues that you've hit? If it's just about choosing a different version of rails, then can't you just set RAILS_GEM_VERSION in environment.rb? I still don't buy the need for a pre-initializers rig, but perhaps there's a simpler option hiding somewhere.

Nope, this doesn't work. boot.rb and initilization always hits before environment.rb is interpreted, which means any code in environment.rb is too late to have control over the rails version. See my comments on the patch.

By the way, I am working with this approach and the latest boot.rb code in my projects today, and it's working great. All I have to do is set the RAILS_GEM_VERSION environment variable, and that automatically loads the correct rails gem version.

I also have this hooked into cruisecontrol.rb, so that it automatically sets the rails version based on the name of my project - e.g.: a project name of myproject_rails_1.2.5.7919 automatically uses that gem for rails. This makes is a no-brainer to test my project under multiple rails versions via CI, and requires no code changes at all - they all use the same subversion url.

Thx, -- Chad

10/30/07 18:27:13 changed by david

  • milestone deleted.

10/31/07 11:06:02 changed by thewoolleyman

  • attachment rails_preinitializer_hook_ticket_9943.version3_with_tests.diff added.

version 3, now with tests.

10/31/07 11:29:13 changed by thewoolleyman

  • attachment ticket_9943_preinitializer_hook_failing_test_scenario.txt added.

description of test scenario which is not currently possible without this patch

10/31/07 11:31:06 changed by thewoolleyman

I don't think I've done a good job of explaining the need for this. I've tried to summarize it in ticket_9943_preinitializer_hook_failing_test_scenario.txt:

Here is the scenario which is not currently possible without a preinitializer hook:

1. I want my app to use the stable gem (e.g. 1.2.5) by default, _even_ if I have a beta gem installed (e.g. 1.2.5.7919) 2. I also want to use RubyGems Advanced Versioning [1] in my RAILS_GEM_VERSION environment variable to use the _latest_ beta gem that I have installed, e.g.: RAILS_GEM_VERSION='>1.2.5' or '~>1.2.5.0' 3. I want this to happen automatically without having to modify my app (no freezing rails or modifying environment.rb), because I want my Continuous Integration system to automatically run my app against multiple rails versions for regression and edge-rails testing by simply setting the RAILS_GEM_VERSION env var prior to the build.

With this patch, I can have complete control over the gem version which is loaded, and what is set in the RAILS_GEM_VERSION constant before the normal boot gem-selection process occurs.

[1] http://www.rubygems.org/read/chapter/16#page76

11/12/07 18:51:23 changed by thewoolleyman

  • attachment rails_preinitializer_hook_ticket_9943.version4.diff added.

version 4, no more config/preinitializers dir, but hardcoded to load config/preinitializer.rb if it exists

11/12/07 18:58:42 changed by thewoolleyman

Hi,

I've rewritten this patch to use the approach that bitsweat proposed "A simple naming convention like config/initializers/preboot.rb is sufficient and feels appropriate."

The only difference is that I look for it in config/preinitializer.rb instead of under initializers, so it doesn't get mixed up and parsed with the initializers. I also chose a name that is consistent with the 'initializers' dir.

So, this patch should not be completely transparent to people who do not use it, completely innocuous, harmless, safe, and politically correct, as well as addressing a real need (to have the ability to override normal boot.rb behavior without editing boot.rb).

Others in the past have mentioned the shortcomings of the current regex-parsing approach of boot.rb, and this patch is a way to allow alternative implementations to be used without changing any of the existing behavior. This is in the spirit of "that's a great idea, but lets see if it holds its own as a plugin" that is often taken with other potential enhancements to core rails.

rails_preinitializer_hook_ticket_9943.version4.diff

Thanks, Chad

11/12/07 18:59:51 changed by thewoolleyman

Um, correction: "this patch should NOW be completely transparent blah blah..."

11/12/07 20:31:48 changed by hasmanyjosh

Looks good to me.

+1

11/17/07 01:39:01 changed by bitsweat

  • status changed from new to closed.
  • resolution set to fixed.

(In [8159]) Load config/preinitializer.rb, if present, before loading the environment. Closes #9943.