Selecting Ruby gems at run-time based on configuration - ruby-on-rails

We're writing a Rails app that we want to be able to talk to any of several external data repositories via a uniform adapter interface, and we'd like to be able to add more later just by writing new implementations of that adapter interface (cf. ActiveRecord adapters).
Any one installation of the app will only need one adapter running, and we don't want to have to rev the code or even the Gemfile whenever we introduce a new adapter. Assuming we write each adapter as a standalone gem, what's the proper way to incorporate just one adapter gem at runtime, based on configuration?

Normally in Rails, you can only access gems that are listed in the Gemfile; Bundler enforces this. However that doesn't stop you from loading gems manually.
If your adapter gems are installed in a well-known location, then loading an adapter gem could be as simple as this:
$LOAD_PATH << "/path/to/adapters/my_adapter/lib"
require "my_adapter"
Where "my_adapter" is something you discover at runtime via configuration.

Put your configuration in the environment, as you're probably already doing.
For example, I use the dotenv gem.
Set an environment variable to your adapter gem name.
The result is an ENV var that Rails can access, such as:
ENV["adapter"] #=> "my_custom_gem_name"
Use the environment to pick the adapter, e.g. in your Rails config:
require ENV['adapter']
This presumes your gem name is the same as the require name. If your gem name is different, use the require name.
When you deploy a new adapter, you can put it anywhere on the Rails load path. Or you can adjust the Rails load path as you like e.g. adding a path to $LOAD_PATH.
An example of how to adjust the load path:
# /config/application.rb
module MyApp
class Application < Rails::Application
$LOAD_PATH << "/path/to/custom/gem"
require("my_custom_gem_name")
…
For example, put the gem code in ./lib, or install the gem into ./vendor or systemwide, using any tool you like such as scp or rsync or ansible, thus bypassing the typical bundle command.
You won't need to update the Gemfile.

I don't think there's a good way to do quite exactly what you ask. Part of the bundler system (that's what uses Gemfiles) is that only gems mentioned in your gemfile (and their dependencies) are available to your app, it is isolated to only them. If this sounds like a bad thing, we could have a long conversation about the pre-bundler dependency management problems that this system has successfully solved.
Option 1 -- what are you worried about anyway? There is probably no downside to including all the possible gems in your Gemfile, even if a given installation will only use one. If using Rails, you will want to make sure these gems are not require'd on launch. (If not using Rails, this might not be neccesary, as other environments don't necessarily ask bundler to require all gems on boot (which is wise), but won't hurt).
So all the gems will get installed in every installation, yes, but only the one you want to use will be loaded, when you issue the require at runtime -- you'll still want to do this on application load, perhaps in response to an ENV variable, to avoid any concurrency or load time weirdness. Anyway, the unused gems will just be sitting there on disk un-loaded. How big a downside is having them installed but not used? How much are you willing to increase the confusingness of your setup to get rid of this downside?
Option 1 is what I'd do.
Option 2 -- separate Gemfiles. Another option is preparing a separate Gemfile for each type of setup. Gemfiles are just ruby code, so you could have one 'base' gemfile including common gems, and then a separate Gemfile for each type of setup, which uses ruby to load/include the base gemfile and then adds the setup-specific gems. You'd give each Gemfile a separate name, Gemfile_adapter1 or whatever.
You're going to have to commit something to your source when you add support for a new adapter type, aren't you? Where does this adapter come from? I don't understand how you could do it without revising any code. Anyway, adding a new Gemfile for that adapter type when you add a new adapter doesn't seem like a huge barrier, but I dunno.
You can launch Rails specifying which of these gemfiles to use with the BUNDLE_GEMFILE env variable: BUNDLE_GEMFILE=./Gemfile_one rails server. And in every other command you do that will use the Gemfile. BUNDLE_GEMFILE=./SOME_GEMFILE bundle install. BUNDLE_GEMFILE=./SOME_GEMFILE RAILS_ENV=production bundle exec rake assets:precompile. If you use capistrano that's doing some of these things for you, figure out how to make sure cap uses the right BUNDLE_GEMFILE when it executes it. Figure out how to make your app server do that when it launches your app. Etc.
This will work out fine -- but is going to end up being a pain to keep track of and make sure it's working right in your entire devops stack.
I suppose you could even generate the Gemfiles at install time, instead of having them in your source repo, for even more confusing situation and another thing that can go wrong and be confusing to debug! (I wouldn't).
I'd consider this Option 2, but would prefer option 1 unless there were really good reasons it wasn't going to work.
Option 3 -- Don't use Bundler. You could abandon bundler and gemfiles entirely. Maybe, if you can convince Rails to do this somehow. It might be hard to convince Rails to do this.
If you could, you are in a situation where you have to install all your gems on your deploy system by hand, making sure they are the right versions and figuring out what versions are compatible with what other versions, and that no more recent versions you don't want are installed.
Then, at runtime, you just need to require all the gems you want, and the app will get the most recent version of that gem installed on the system.
I would never, ever, ever, do this. I don't know if you can get Rails to do it, but even if you could, I remember the dependency hell from before Bundler existed, and would never want to go back.

Related

How to find unused gems and cleanup gemfile

I'm looking for simple, but good way to cleanup gemfile and make rails startup faster.
How can I get a list of all required gems vs all loaded gems.
bundle clean --force will remove old gems (or older versions of currently-used gems) previously installed, but not currently being used in your current Gemfile.lock manifest.
First, if you want to check what are the gems used by your project, I invite you to run gem server in your project folder root, then go to http://0.0.0.0:8808/
You will be able to understand the dependencies of all the gems your project is using. It will also show you all versions of the same gem.
To remove old versions of gems you can run as #changingrainbows mention
bundle clean --force
After this step run your gem server again and watch the result, a clean and understandable gem list with all dependencies.
It depends what you're after here.
If you're looking to remove old, unused gem versions, then bundle clean.
If you've been adding gems as you develop and have lost track of the ones you actually use, and have good test coverage, then try this answer.
If you want to reduce the number of gems rails pulls in at startup to the bare minimum, try gem_bench.
I think it is impossible. When your APP starts it loads gems from Gemfile.lock but it does not know if they (gems) are needed in your code or not. The APP inform you by raising an exception When something calls a class or method that is undefined if some needed gem is missed (if you remove it from Gemfile), but this can happen at any moment (not during starting your APP).
So if you are looking the way to clean up your gem list I think the best way to do it manually (I know it is not easy way). Analyse each gem to find out what functionality it provides and decide (or find in your code) if it is needed or not. Additionally tests (if you have them) should help you a lot.

Gems in application build without bundler

I know that if application uses bundler, I can easily find all the gems installed by looking at the Gemfile.
Say, I am looking at the Rails 3 application that doesn't use bundler, how do I know what gems it uses?
Thanks
If it's not using Bundler, I don't know of a definitive way to identify every gem being used. You could search the entire app tree for require statements to start with, but that's not going to show most of them. Gems also require other gems internally, and will install their own dependencies, but those gems won't be referenced directly from your app's require statements.
If the app works and the tests pass (meaning you've at least got all the required gems installed), you could approach the problem by creating a Gemfile, listing the gems you know are needed, and then running your tests (or the app itself) via bundle exec, which will ensure that only the gems listed in the Gemfile are visible. Then you'll get failures related to missing gems, and can add them to the Gemfile until it all works. Once it's working via bundle exec, you'll know that you've captured all the requirements there.
If you're using RVM, you'll probably find it helpful to create a gemset for your app, along with a .rvmrc file in the app root, to take advantage of RVM's automatic gemset switching and Bundler integration. It'll make it easier to maintain the gem state going forward.
In any case, running gem list with the app in a working state will show you all the gems that it might be using, but without being scoped to a gemset or wrapped in bundle exec, you'll also see gems that were installed for other reasons that potentially have nothing to do with your app's dependencies.

Should one bundle gems with the app?

Having just got a new machine I have taken the opportunity to try something new.
RVM is great and I have been using gemsets but after reading a few blog posts I have decided use switch to rbenv and use bundler to manage my gems exclusively as outlined in Ruby Rogues episode 45.
I do not collaborate all that much and if I do it is usually with one or two other people.
The bundler documentation details the ability to package up gems in to the vendor/cache directory by running:
$ bundle package
$ bundle install --local
Great private gems that can be checked in to source control; I guess making deployment to a clean server or collaborating easier?
However if you check your Gemfile and Gemfile.lock into source control then what is the need for bundle package?
Ryan Mcgeary advocates for this approach in this blog post form early 2011 and in another blog post from 2010 Yehuda Katz says that:
You might want to install your bundled gems to a different location, such as a directory in the application itself. This will ensure that each application has its own copies of the gems, and provides an extra level of isolation.
This isolation is quite like gemsets I suppose and I can imagine when you have a huge list of system gems it would be difficult to know which ones are actually being used by your applications.
So packaging gems with the application?
Is anyone doing this? Is this practice obsolete?
What's the best practice and what are the advantages / disadvantages of bundling gems within the app?
I think bundle package has a narrow set of usage cases.
Gemfile and Gemfile.lock deal with version locking, but in the end you still go out to rubygems.org and download the gem when you do bundle install.
The three scenarios I can see bundle package being useful:
1) I have a clean production environment, and I don't want to touch rubygems.org at all. This might be due to a security protocol, limited internet access, etc. In the end I have a entirely self-contained application package with all the gems lined up and ready to go, without really touching the server environment, or the internet.
2) I want to download the gem, unpack it, screw around with it, and use that particular one. Especially if I don't wanna deal with things like git, or forking, or any of that stuff.
3) From a development team point of view, you can say that all gems being used must be packaged and local to the app. You wouldn't have to worry about developers installing different versions, touching their environments, etc. This reason is kinda weak in my eyes, but I've seen this logic once or twice.
In the end, unless you have a use case where you're actively hunting for this ability, I'd stay away from it.

Rails requires RubyGems >= 0.9.4. Please install RubyGems

Having the same problems as this post, except I don't think the solution is the same unfortunately.
I'm getting this error message:
Rails requires RubyGems >= 0.9.4. Please install RubyGems
when I run a script/runner job in Cron, but it works perfectly fine when I run it in a terminal on the same server.
The rails server also runs fine. I only have trouble running script/runner's in cron. It seems to want to have a terminal attached...
Try which ruby and which gem from your cron job and also from the terminal. Are you accessing different binaries? You didn't mention which OS and which shell, but you may have a different $PATH when running headless.
The selected answer is completely correct, but something I'd suggest is to NOT use script/runner. The resources required to load the entire stack of your rails application is pretty intense for something to run regularly.
In my past experience, any cron jobs generally only have to deal with data (as opposed to say, generate static files, etc). In this case, you can very simply just load your models up, and since you've written your models the correct way (fat models), you can easily do your data processing with a few model methods.
Of course, all the above depends completely on your task, so take it with a grain of salt :)
I suppose this was a sort of answer to a problem that may not exist, and wasn't asked here, but just thought I'd throw my two cents in ;)
You wouldn't happen to be working on OSX? You should be installing a newer ruby rails and gems off macports instead. Google for the instructions...
As indicated by earlier posts, your Cron is using a different gem command source — also try which gem and gem -v. You may have a library set in your .bash_profile (or similar shell configuration or perhaps by other means, setting proper Ruby/Rails environment (i.e., Locomotive or other all-in-one environment).
To update your gem setup:
gem update --system
More information here - http://www.rubygems.org/read/book/1

Build a Plugin or Gem?

Typically I create a plugin when I have a module that I know I'm going to need over again in my other projects, however, they could also be packaged as gems.
When should I be building a gem over creating a plugin? Is there any criteria for making the call?
Plugins are becoming obsolete now that you can manage gems via the "config.gem" statement in environment.rb. Gems are available system-wide (not just in one app), and are versioned unlike plugins.
I've converted all of my plugins to gems recently. Easy to do and well worth it.
Rails seems to be moving towards the gem direction. I have converted most of my plugins to gems now. Gems are easier to manage and fit better in the Ruby eco-system. Why do we need two separate systems anyway?
There is still a problem with gems however: it is not possible to add rake tasks to a Rails application from a gem. Probably the same holds for generators, although I'm not sure. If you use these in your plugin, migrating to a gem is not yet possible. Hopefully this gets fixed soon.
you can add generators to rails via gems. it's actually pretty easy, you can just add a rails_generators directory to your gem. (i think other directory names will work - i'm not sure what rails searches for). example: http://github.com/remi/rackbox/tree/a21c21667c68d5fd51357e28f0742171e9161e9b/rails_generators
as for adding rake tasks ... i have yet to figure out howto do that :/
for now, i'm having my generators add require 'myproject/rails/tasks' (or something) to the project's Rakefile as a way to add rake tasks to rails from a gem.
a lot of gems ask you to 'bootstrap' them into your rails project, eg.
sudo gem install cucumber
cd rails_app
./script/generate cucumber # bootstrap cucumber into your app

Resources