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.

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

-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