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

Howdy,

Please take a look at Enhancement #9943 (2) 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 Woolley

(1) http://geminstaller.rubyforge.org/documentation/tutorials.html#integrating_geminstaller_into_ruby_on_rails (2) http://dev.rubyonrails.org/ticket/9943

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.

I'm just not sure that there are a whole lot of use cases that need this kind of flexibility before environment.rb gets hit.

When I'm testing my apps against multiple rails releases I just use symlinks in /vendor/rails pointing to various subversion checkouts.

Are there any other common cases you have in mind? You could always automate the patching of boot.rb for your gem installer app.

> 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.

I'm just not sure that there are a whole lot of use cases that need this kind of flexibility before environment.rb gets hit.

Hi Koz,

Thanks for the feedback! I'll respond to your points inline below.

When I'm testing my apps against multiple rails releases I just use symlinks in /vendor/rails pointing to various subversion checkouts.

Symlinks aren't cross-platform. They are also harder to automate, especially in a Continuous Integration environment. With a hook to pre-initialize RAILS_GEM_PATH and load the gem on the path, you can control everything with an environment variable and not modify the filesystem/working copy at all - everything is in memory and the env var goes away after the build. The symlink approach is harder to automate and more intrusive - it would require the CI tool to manage the physical creation of the symlinks (and ensuring the existence of wherever they point to), and reseting them to the proper defaults (but what if the build fails? Hope your Ci tool has a good after-build-failure hook).

IMHO, automating the use of multiple releases is easier with gems than modifying the filesystem under vendor, if only because of RubyGems built-in support for selecting any installed version on the fly.

Are there any other common cases you have in mind? You could always automate the patching of boot.rb for your gem installer app.

Well, that would be ignoring the warning to not modify boot.rb, and I don't want to do that!

Also, that would involve some ugly parsing and hacking of people's boot.rb (the hook needs to be after RAILS_ROOT is defined). What if boot.rb changes with a new Rails release (or has been customized by the user) and my regexp no longer matches? What if I have a bug and hose their boot.rb?

Again, I think it's cleaner, safer and less intrusive to have preinitializer hook(s). No need to ignore warnings and modify boot.rb, and if I screw something up or users don't want my hook anymore, there's a well-defined place to find and delete it.

Even if there's not a ton of use cases for this patch, there's not really any alternatives that address the problem well, and it's completely innocuous AFAIK, so I hope it can be considered in that light.

-- Cheers

Koz

Thanks again for the response, -- Chad

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.

thanks, Rob

http://streamlinedframework.org

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

I just saw this was taken off the 2.0 milestone list. This is a simple patch with tests which addresses a real limitation of the current Rails. I don't think I've done a good job explaining it so far, so I'll try again in the form of a failing test scenario.

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.

I'm flexible, if you want me to rewrite it to not have the 'preinitializers' directory and instead just have a magic hook filename, I'm glad to rewrite the patch. Just let me know.

Thx, --Chad

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

If all you want to do is change RAILS_GEM_VERSION to test different versions of Rails, why don't you just change boot.rb to default to ENV['RAILS_GEM_VERSION']:

Index: config/boot.rb

The boot.rb has been completely refactored on trunk (and improved, thanks bitsweat!). Your patch is against an old version.

Second, the version specification in the 'gem' method in boot.rb is hardcoded to "equal", even in the new boot.rb. This doesn't meet my second requirement - being able to pass a RAILS_GEM_VERSION like '>1.2.5' in the env which automatically uses the latest beta gem without hardcoding the version.

Thanks for the reply, though! -- Chad

This would also mean hacking boot.rb, which is not a very nice solution and means additional work merging everytime you upgrade rails.

- Rob

I'm flexible, if you want me to rewrite it to not have the 'preinitializers' directory and instead just have a magic hook filename, I'm glad to rewrite the patch. Just let me know.

My problem is that to add an entire pre-initializer feature when the sole use case is version twiddling seems hugely overkill. No one's come up with a case that can't be solved by moving things into and out of vendor/rails...

If all you want to do is have more control over the version of rails that gets required, let's do that instead of some generic thing that we'll have to support for an entire release cycle.

This would also mean hacking boot.rb, which is not a very nice solution and means additional work merging everytime you upgrade rails.

Obviously any change to boot.rb that isn't included in Rails will require merging on upgrade. In case it wasn't clear, I was suggesting the change (or a similar one) be included with Rails. Rails uses ENV['RAILS_ENV'] for the environment if available, so using ENV['RAILS_GEM_VERSION'] for the gem version is similar in that regard. It solves this particular problem and I can't see any downsides.

Jeremy

> I'm flexible, if you want me to rewrite it to not have the > 'preinitializers' directory and instead just have a magic hook > filename, I'm glad to rewrite the patch. Just let me know.

My problem is that to add an entire pre-initializer feature when the sole use case is version twiddling seems hugely overkill. No one's come up with a case that can't be solved by moving things into and out of vendor/rails...

The use case is selecting the version _without_ modifying the app. I want _easily_ test my app against multiple rails versions under Continuous Integration. Having my CI tool simply set an environment var is easy. Having it install plugins or hack externals is much harder, relatively.

If all you want to do is have more control over the version of rails that gets required, let's do that

Sure, we could have the RAILS_GEM_VERSION allow a version spec (>) instead of just a numeric version. However, this still wouldn't support even cooler preinit hooks that I haven't even mentioned to avoid confusion (like automatically installing the required gem if it's missing)

... instead of some generic thing that we'll have to support for an entire release cycle.

What's to support? It will be completely transparent to people who don't use it. If it's the existence of the directory that's a problem, I'm fine to go with the single magic hook file (e.g., config/preinitialize.rb)

-- Cheers

Koz

Thanks for the feedback,

-- Chad

The use case is selecting the version _without_ modifying the app. I want _easily_ test my app against multiple rails versions under Continuous Integration. Having my CI tool simply set an environment var is easy. Having it install plugins or hack externals is much harder, relatively.

Again, you're completely missing the point of what I'm objecting to. If all you want to do is change the gem version, lets change boot.rb to check an env var first. There's yet to be a single use case for anything else.

Jeremy's suggestion looks like the right way forward, so lets stop wasting time talking past one another or trying to design some generic 'pre boot' hooks, and instead apply a patch that lets you solve the problem you have.

The latest boot.rb in trunk does allow override by the env var. However, this still only supports a specific version, not an advanced version spec (e.g. floating on latest beta gem if env exists).

Also, as I said in the other thread, even if it the env var did take a version spec instead of a specific version, it still wouldn't allow cooler advanced hooks like auto-installation of missing gem versions on app startup.

We are an agile shop that works on a lot of different rails projects, so we switch pairs, machines and projects often. These are often on different rails releases, which may or may not be installed on a given machine. We also want to have our CI systems automate testing of all our projects against multiple rails versions, including the latest edge gems, without having to even think about it.

It's all about the automation :slight_smile:

Thanks, Chad

> Obviously any change to boot.rb that isn't included in Rails will > require merging on upgrade. In case it wasn't clear, I was suggesting > the change (or a similar one) be included with Rails. Rails uses > ENV['RAILS_ENV'] for the environment if available, so using > ENV['RAILS_GEM_VERSION'] for the gem version is similar in that > regard. It solves this particular problem and I can't see any > downsides. > > Jeremy

The latest boot.rb in trunk does allow override by the env var. However, this still only supports a specific version, not an advanced version spec (e.g. floating on latest beta gem if env exists).

I'm not sure how easy it is to specify a advanced version spec (I've never needed to), but assuming it can be specified as a string, it's doable from the environment.

Also, as I said in the other thread, even if it the env var did take a version spec instead of a specific version, it still wouldn't allow cooler advanced hooks like auto-installation of missing gem versions on app startup.

We are an agile shop that works on a lot of different rails projects, so we switch pairs, machines and projects often. These are often on different rails releases, which may or may not be installed on a given machine. We also want to have our CI systems automate testing of all our projects against multiple rails versions, including the latest edge gems, without having to even think about it.

Given your scenario, my advice would be to wrap whatever command you use to start Rails (mongrel_rails, style, etc.) in a script that checks the gem version in the environment and downloads the gems before starting rails if they are not currently installed.

Jeremy

I'm not sure how easy it is to specify a advanced version spec (I've never needed to), but assuming it can be specified as a string, it's doable from the environment.

It's just the version prefixed with (in)equality or ~, see the link to rubygems docs earlier in thread for more info.

The problem is that the current boot.rb hardcodes the prefix to "=" and assumes RAILS_GEM_VERSION. Now that we've discussed this, I realize that it should really be more flexible. Given bitsweat's excellent refactoring of boot.rb to make it testable and isolate Rails::GemBoot.gem_version, we should go ahead and allow any rubygems version spec in RAILS_GEM_VERSION. I think the default is equality, so it may be as easy as removing the "=" (but should still be well-tested anyway).

I'll go ahead and try to code up a separate patch to implement this behavior. Even though it addresses some of my issues, it still doesn't allow the flexibility that a preinit hook would. It will prune my pitch for the preinit patch, but I'll be unselfish and hopefully code and karma will prevail.

Given your scenario, my advice would be to wrap whatever command you use to start Rails (mongrel_rails, style, etc.) in a script that checks the gem version in the environment and downloads the gems before starting rails if they are not currently installed.

That's a great idea, and what we currently do as part of our cap scripts. As well as the beginning of our CI rake tasks. As for mongrel, I suppose I could write a plugin, but that's more duplication. Not to mention when we occasionally test under webrick (handles PUTs differently), which doesn't have plugin support.

The point is, I want everything dependency-related (gem installation and load path management) to be self-contained, be in one place, and happen automatically whenever I run an app - whether that is during a deploy, during local tests, during CI, or when I'm just firing up the app via webrick or mongrel. Call me a pie-in-the-sky dreamer, but I know it's possible. An _official_ pre-init hook is one of the key requirements, though. Yes, I can continue to hack boot.rb, which would probably be easier than repeatedly explaining the justification for a preinit hook, but I started this thread and patch based on the principle that I shouldn't be editing a file that tells me not to edit it :slight_smile:

Thanks again for discussing, it's helping me to clarify things and address the parts of the issue separately.

-- Chad

This is done, with tests, in [10057].