Initializers aren't run on gem dependency failure

Let's say you've added a gem dependency with config.gem, but your gem isn't installed or Rails doesn't see it. Like the json gem, which one of our unfortunate Windows developers has installed but Rails doesn't see. (I hear it's a Windows issue, but I don't use it.)

When you start your mongrel, you get a warning that you're missing gems. "That's ok. My app has a graceful fallback." Then your app will throw seemingly random errors. After a bit of troubleshooting, you learn Rails is skipping your after_initialize block and config/ initializers/*.rb files if it fails to load any gems.

Unless someone can explain the reasoning, I think this is a bug, and I've made the patch to fix it.

http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/814-initializers-aren-t-run-on-gem-dependency-failure

I couldn't find any tests that check the config/initializers/ files are run, so I've added one.

Looking for feedback.

I don’t see a reason for the old behavior, and I do agree that, if we’re continuing with initialization instead of hard failing, it shouldn’t skip loading initializers and instantiating AR observers.

The question we should be asking ourselves is: do we really want to allow the app to fully initialize if it can’t load gem dependencies? Can we expect it to work this way?

I say no. A Rails app should fail as soon as a requirement that is not met is found: a required file, gem, plugin or similar. Opinions?

Mislav

I agree. If a dependency is not met, exit immediately with an error. If the missing dependency isn't REALLY a problem in some situations, then encode that conditionality using ruby - check the platform, environment, shell out to run a command, whatever you need. Isn't why we are configuring the gems in ruby, to have that flexibility and control?

-- Chad

At least that was the reason for dumping my original config/gems.yml approach :slight_smile:

I vote for yes, because: 1. Not all dependencies are hard dependencies. For example, an app that I'm working on supports syntax highlighting through 'uv', but it'll gracefully fallback if uv is not available (e.g. when being run on JRuby, because uv relies on a native extension). 2. A few people (I think about 3) have reported this problem as a Phusion Passenger bug. So it should be apparent that people don't expect the current behavior.

That said, I think it would be better to allow one to specify required gems and optional gems. A while ago my co-developer pulled the latest version of our application and asked me why it would crash when he visits a certain URI. Turns out that he didn't have a gem installed, and missed the startup warning.

The following situation would be ideal: - Hard gem requirements should fail hard, during startup. - Soft gem requirements should only print a warning. Like: config.gem 'something', :optional => true

Yes, but you get the best of both worlds (dynamic control and decoupling dependencies from app) if you run your YAML through ERB :slight_smile:

Agreed. It always seemed strange to me that you can load your app even if you are missing gems.

Maybe there should be a way to specify optional dependencies, but the default should be to fail fast if a gem is missing.

- Rob

Just from watching the commits fly by, I thought this was already fixed in edge? http://github.com/rails/rails/commit/1edb5c85b5

I've written a new patch that implements this behavior, and includes my old patch.

http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/824-make-gem-dependency-optional

It defaults to optional being false, meaning your app will fail on start. If you set :optional => true, the app will only print a warning if the gem is missing.

Looking for +1's.

-1 on this. This should be thought through more.

First, there is now the standard "development" gem property which is now in RubyGems 1.2.0 (see gem install --help). This is already a way of specifying gems which are only required in development, but not to run the app in production/runtime. How would an "optional" flag relate to this?

Also, what does it mean for a dependency to be "optional"? If it isn't required sometime, somewhere, for something, it should not be a dependency. The when, where, and why is completely dependent on the situation. That's why this should be handled with custom ruby conditional code - based on platform, RAILS_ENV, hostname, whatever. In other words, no dependency is ever "optional" - someone must need it, otherwise it would not be a dependency.

So, I think that this should be handled by leveraging the standard support which is already in Rubygems, and allow the config.gems to pass through the --development option to the underlying gem install command. Anything else that doesn't fall into the current standard development/runtime category should be handled with custom conditionals on a per-app basis.

-- Chad

I've written a new patch that implements this behavior, and includes my old patch.

#824 Make Gem Dependency Optional - Ruby on Rails - rails

-1 on this. This should be thought through more.

First, there is now the standard "development" gem property which is now in RubyGems 1.2.0 (see gem install --help). This is already a way of specifying gems which are only required in development, but not to run the app in production/runtime. How would an "optional" flag relate to this?

In my case the difference between production and development is that development also uses gems like rspec, faker, annotate-models. These are not necessary for running in production. Those are specified separately in config/environments/development.rb. That, however, has nothing to do with gem development dependencies. These dependencies do not interest the app under any condition, it only needs these gems and their runtime dependencies.

Also, what does it mean for a dependency to be "optional"? If it isn't required sometime, somewhere, for something, it should not be a dependency. The when, where, and why is completely dependent on the situation. That's why this should be handled with custom ruby conditional code - based on platform, RAILS_ENV, hostname, whatever. In other words, no dependency is ever "optional" - someone must need it, otherwise it would not be a dependency.

A lot of times optional means "use it if it's there, don't bother me if it's not", which is different from only using a gem in a particular setup. Uv is a good example, it's painful to install and not supported on JRuby, so you would want to disable this dependency when running on JRuby, but also make it optional in other conditions.

Assaf

Being able to specify development gems would be very useful. For example, if you use RSpec as testing framework then you only need RSpec to be installed on the development machine, not the production machine (unless you want to run the tests on the production machine as well).

However, that's not the same thing as optional gems. Here's a concrete example of an optional gem: One of my applications use the 'uv' gem for syntax highlighting code. But it's not a hard requirement: at runtime, my application will check whether Uv is available, and if not, then the application will still work, it just won't do syntax highlighting.

Uv depends on a native extension. If any members in my development team can't compile this native extension for whatever reason, then I still want them to be able to help with development even though syntax highlighting doesn't work.

Being able to specify development gems would be very useful. For example, if you use RSpec as testing framework then you only need RSpec to be installed on the development machine, not the production machine (unless you want to run the tests on the production machine as well).

You *should* be able to achieve this by using config.gem in development.rb. If this doesn't work it's a bug and we should fix it.

However, that's not the same thing as optional gems. Here's a concrete example of an optional gem:

We have cases of this in the rails codebase, but typically they rescue LoadError and set up some stub versions of the relevant libraries. i.e. it's not enough to just rescue the failed load, some values have to be set up so the application knows what to do. Perhaps you require an alternative gem, or you just stub out a few constants and methods or set a global variable like $uv_installed = false.

I'm really not sold on doing this through config.gem as there's just no simple 'else' case here.

I still think this would be better handled via custom application code. For example, never do it if platform is Jruby, or if some other app-specific flag is set.

The app-specific flag could be a constant or environment variable which says to either use or not use the dependency (depending on which is the most common case). If you don't like environment variables, put it as a constant in a file that is attempted to be loaded, but ignored and never checked into source control. A developer-specific config file, sort of like database.yml if you don't check it in.

Cool. Good to know. I never actually use config.gems myself, since there are (cough cough geminstaller) more fully-featured alternatives. Just Kidding :slight_smile:

-- Chad