[activerecord] Better loading pattern for connection adapters that play nicely with bundler --standalone

Hi,

I initially sent this idea as a github issue: Better loading pattern for activerecord connection adapters that play nicely with bundler --standalone · Issue #7753 · rails/rails · GitHub (closed)

Steve Klabnik suggested I should post here first (better late than never).

I've since created a PR: Loading pattern for connection adapters that plays nicely with bundler --standalone by saimonmoore · Pull Request #7755 · rails/rails · GitHub

Original discussion:

I'm using the relatively new --standalone feature/option of bundler so as to avoid extracting a part of my rails project into a separate project. My only requirement is to not have to load rails entirely:

By creating a custom gemfile (subset of your typical gemfile) and executing:

    bundle install --gemfile=Gemfile.my_subset --standalone

bundler will create a ruby script which requires all the gems in your gemfile subset using the original gems locations.

e.g. :

    path = File.expand_path('..', __FILE__)     $:.unshift File.expand_path("#{path}/../ruby/1.9.1/gems/rake-0.9.2.2/lib")     $:.unshift File.expand_path("#{path}/../ruby/1.9.1/gems/multi_json-1.0.4/lib")     $:.unshift File.expand_path("#{path}/../ruby/1.9.1/gems/activesupport-3.1.6/lib")     $:.unshift File.expand_path("#{path}/../ruby/1.9.1/gems/builder-3.0.3/lib")     $:.unshift File.expand_path("#{path}/../ruby/1.9.1/gems/i18n-0.6.0/lib")     $:.unshift File.expand_path("#{path}/../ruby/1.9.1/gems/activemodel-3.1.6/lib")     $:.unshift File.expand_path("#{path}/../ruby/1.9.1/gems/arel-2.2.3/lib")     $:.unshift File.expand_path("#{path}/../ruby/1.9.1/gems/tzinfo-0.3.33/lib")     $:.unshift File.expand_path("#{path}/../ruby/1.9.1/gems/activerecord-3.1.6/lib")     ...

This allows scripts to e.g. load just activerecord without setting up a separate gemfile.

The issue here is that with this setup script, you aren't using bundler (to avoid loading rails and the gems in your typical gemfile.). Additionally, the gems the script requires are `expanded` gems and have not been installed via `gem install`.

Activerecord connection adapters have this code:

    gem 'mysql2', '~> 0.3.10'     require 'mysql2'

This code assumes:

  * You're running the code within bundler (recommended)   * You have the mysql2 gem installed as a gem in your system

When using bundler standalone neither of these cases are true so the app breaks with:

   Please install the mysql2 adapter: `gem install activerecord-mysql2-adapter` (no such file to load -- active_record/connection_adapters/mysql2_adapter) because it hits the 'gem' call before it attempts to require 'mysql2'

IMHO a better approach would be:

    begin       require 'mysql2'     rescue LoadError       gem 'mysql2', '~> 0.3.10'       require 'mysql2'     end

This code assumes you already have mysql2 on your load path somehow and only if it raises an exception do we attempt to issue the `gem` call.

If you have mysql2 on your system, the `gem` call will never be made (defeating the purpose of the `gem` call in the first place).

Is this reasonable?

If we don't use the `gem` command, we can't enforce a particular version of mysql2 in the adapter code. Since AR has "soft dependencies" (dependencies that are not declared in the gemspec), we need some way to ensure that we've loaded a connection handling gem that has the version we actually support. Doing a `require 'mysql2'` could pick up *any* version of mysql2, including one that won't actually work with the AR adapter code.

Hi Aaron,

Hi,

I initially sent this idea as a github issue: Better loading pattern for activerecord connection adapters that play nicely with bundler --standalone · Issue #7753 · rails/rails · GitHub (closed)

Steve Klabnik suggested I should post here first (better late than never).

I've since created a PR: Loading pattern for connection adapters that plays nicely with bundler --standalone by saimonmoore · Pull Request #7755 · rails/rails · GitHub

Original discussion:

I'm using the relatively new --standalone feature/option of bundler so as to avoid extracting a part of my rails project into a separate project. My only requirement is to not have to load rails entirely:

By creating a custom gemfile (subset of your typical gemfile) and executing:

   bundle install --gemfile=Gemfile.my_subset --standalone

bundler will create a ruby script which requires all the gems in your gemfile subset using the original gems locations.

e.g. :

   path = File.expand_path('..', __FILE__)    $:.unshift File.expand_path("#{path}/../ruby/1.9.1/gems/rake-0.9.2.2/lib")    $:.unshift File.expand_path("#{path}/../ruby/1.9.1/gems/multi_json-1.0.4/lib")    $:.unshift File.expand_path("#{path}/../ruby/1.9.1/gems/activesupport-3.1.6/lib")    $:.unshift File.expand_path("#{path}/../ruby/1.9.1/gems/builder-3.0.3/lib")    $:.unshift File.expand_path("#{path}/../ruby/1.9.1/gems/i18n-0.6.0/lib")    $:.unshift File.expand_path("#{path}/../ruby/1.9.1/gems/activemodel-3.1.6/lib")    $:.unshift File.expand_path("#{path}/../ruby/1.9.1/gems/arel-2.2.3/lib")    $:.unshift File.expand_path("#{path}/../ruby/1.9.1/gems/tzinfo-0.3.33/lib")    $:.unshift File.expand_path("#{path}/../ruby/1.9.1/gems/activerecord-3.1.6/lib")    ...

This allows scripts to e.g. load just activerecord without setting up a separate gemfile.

The issue here is that with this setup script, you aren't using bundler (to avoid loading rails and the gems in your typical gemfile.). Additionally, the gems the script requires are `expanded` gems and have not been installed via `gem install`.

Activerecord connection adapters have this code:

   gem 'mysql2', '~> 0.3.10'    require 'mysql2'

This code assumes:

* You're running the code within bundler (recommended) * You have the mysql2 gem installed as a gem in your system

When using bundler standalone neither of these cases are true so the app breaks with:

  Please install the mysql2 adapter: `gem install activerecord-mysql2-adapter` (no such file to load -- active_record/connection_adapters/mysql2_adapter) because it hits the 'gem' call before it attempts to require 'mysql2'

IMHO a better approach would be:

   begin      require 'mysql2'    rescue LoadError      gem 'mysql2', '~> 0.3.10'      require 'mysql2'    end

This code assumes you already have mysql2 on your load path somehow and only if it raises an exception do we attempt to issue the `gem` call.

If you have mysql2 on your system, the `gem` call will never be made (defeating the purpose of the `gem` call in the first place).

Is this reasonable?

If we don't use the `gem` command, we can't enforce a particular version of mysql2 in the adapter code. Since AR has "soft dependencies" (dependencies that are not declared in the gemspec), we need some way to ensure that we've loaded a connection handling gem that has the version we actually support. Doing a `require 'mysql2'` could pick up *any* version of mysql2, including one that won't actually work with the AR adapter code.

I understand though I think it sucks…

What about something like:

    begin        gem 'mysql2', '~> 0.3.11'        require 'mysql2'     rescue LoadError       puts "You do not appear to have mysql2 (~> 0.3.11) installed. AR requires this to function correctly."

      begin         require 'mysql2'         puts "Found some version of mysql2. Proceeding..."       rescue LoadError => err         raise err       end     end

Then you leave the responsibility up to the developer…

-- Aaron Patterson http://tenderlovemaking.com/

Saimon