1

I'm creating a Rails web application which uses a shared library of models (as a Rails Engine), stored in a subrepository (Git subtree.) This shared library contains dependencies on other Ruby gems (in my case, HTTParty and Dalli), of which I want to be automatically referenced by the parent project that includes this shared library.

However, my gem's dependencies don't appear to be resolving in the parent project, and when I start my web application, it has missing references to those gem dependencies in the shared library. (i.e. NameError: uninitialized constant ApiClient::HTTParty) If I explicitly add those references to my web app's Gemfile (as in uncomment the Gemfile lines below), everything works fine.

How do I get these dependencies to 'chain', and have the parent project automatically resolve these references?

Here's what my project looks like:

[MyRailsApp]
 -- ...
 -- [app]
 -- [config]
 -- [lib]
   -- [MyLib]
     -- ...
     -- [app]
     -- [config]
     -- [lib]
       -- [MyLib]
         -- version.rb
         -- engine.rb
     -- MyLib.gemspec
     -- Gemfile
 -- Gemfile

MyRailsApp/Gemfile:

source 'https://rubygems.org'

gem 'activesupport', '3.2.13', :require => 'active_support'
gem 'actionpack',    '3.2.13', :require => 'action_pack'
gem 'actionmailer',  '3.2.13', :require => 'action_mailer'
gem 'railties',      '3.2.13', :require => 'rails'
...
# gem 'dalli'
# gem 'httparty'

gem 'MyLib', :path => 'lib/MyLib'

MyLib/MyLib.gemspec:

$:.push File.expand_path("../lib", __FILE__)

# Maintain your gem's version:
require "mylib/version"

# Describe your gem and declare its dependencies:
Gem::Specification.new do |s|
  s.name        = "MyLib"
  s.version     = MyLib::VERSION
  s.authors     = ["David"]
  s.email       = ["ops@myemail.com"]
  s.homepage    = "http://www.mysite.com"
  s.summary     = "Shared Library"
  s.description = "Shared Library"

  s.files = Dir["{app,config,db,lib}/**/*"] + ["MIT-LICENSE", "Rakefile", "README.rdoc"]

  s.add_dependency "rails", "~> 3.2.13"
  s.add_dependency "dalli", ">= 2.6.4"
  s.add_dependency "httparty", ">= 0.11.0"
end
David Elner
  • 5,091
  • 6
  • 33
  • 49

2 Answers2

3

Figured it out. I had the wrong line of thinking... I thought Gemspecs, used for installing gem dependencies (which was working just fine), would also be used by Rails to determine what dependencies need to be loaded into memory when the application starts. This is not the case, at least, not when using Rails engines.

For the average gem, it appears that a typical Rails web app has a line in the boot.rb file which loads all gems and dependencies in the application Gemfile. However, this autoloading does not appear to extend to Rails engines listed in the Gemfile. In this case, you must load your dependencies into application memory manually, by finding the engine.rb file (in your Rails engine) and adding require 'yourgem' at the beginning of the file. This will load the dependency when the engine loads.

A friend found and linked me this relevant question/answer, if this explanation isn't sufficient: https://stackoverflow.com/a/5850503

Community
  • 1
  • 1
David Elner
  • 5,091
  • 6
  • 33
  • 49
1

If you want rubygems to understand your dependencies, package each of them with a proper .gemspec file. You don't have to publish your gem, it can be private and referenced via a git:// type URL.

The thing is, generally your .gemspec needs to be at the root level. You can't bury it in your project as Rubygems does not go out of its way to look for these.

In your use case, MyLib should be a separate thing.

tadman
  • 208,517
  • 23
  • 234
  • 262
  • Aye, I understand the gem could work as it's own repository, but I wanted to be able to embed the source code directly into the parent (web) project itself. I'm afraid having to manage two totally independent repositories would be a pain for development, especially for models which can change often. – David Elner Jun 24 '13 at 16:13
  • @Dave Breaking it out into separate modules introduces a bit of pain up front, but being able to unit test your dependencies independently is a huge win. Once you get used to working in a split environment you'll be fine. – tadman Jun 24 '13 at 18:52
  • Thanks, but for my purpose that's infeasible. Although I'd like to treat the library as an independent module, I cannot afford to commit/push/pull changes in my shared library just to see 'if it worked' in my development environment. Can you imagine having to commit/push/pull every-time you change any file while developing a Rails app? It'd be madness. And it turns out mylib/gemspec is being respected by the parent web application, and installs the dependencies I need. Instead, I was on the wrong train of thought to solve my problem. See my answer for more details. – David Elner Jun 26 '13 at 13:53
  • It's not as mad as you think. You can specify a local `path` in the `Gemfile` while you're doing testing, then switch that back to the proper `git://` path when you're ready to commit. Any changes made to gems will require a `touch tmp/restart.txt` to force an application reload, but that's not usually a big deal. There's actually a lot of options usable within [`Gemfile`](http://gembundler.com/v1.3/gemfile.html). – tadman Jun 26 '13 at 17:32
  • Sure, I could change my Gemfile every single time I want to develop locally (which would be every-time I ever code), but that sounds like a pain. And more importantly, your diagnosis `The thing is, generally your .gemspec needs to be at the root level. You can't bury it in your project as Rubygems does not go out of its way to look for these` is incorrect: rubygems actually does find and use my gemspec and its dependencies, so this proposed solution about submodules is on a tangent. – David Elner Jun 27 '13 at 14:56
  • It's Bundler that makes use of `Gemfile`, so if it's set up correctly then it seems anything's possible. I know the `path` option can be relative to the project root, so if you have a submodule you can check in a `Gemfile` with that path. Anything listed in your `Gemfile` needs a valid `.gemspec`, though, as far as I know. Rubygems is not the one finding the buried `.gemspec`, it's Bundler coaching Rubygems on where to find it. – tadman Jun 27 '13 at 15:04