Rails: Chaining Ruby gem dependencies? - ruby-on-rails

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

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

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.

Related

rails engine gemfile.lock not being included rails engine

I am trying to build a rails engine and I have some gems that I want to isolate and bundle only in the rails engine Gemfile. I put these gems in my Gemfile:
source 'https://rubygems.org'
gemspec
gem "pg", "0.15"
In my insurance.gemspec:
$:.push File.expand_path("../lib", __FILE__)
# Maintain your gem's version:
require "insurance/version"
# Describe your gem and declare its dependencies:
Gem::Specification.new do |spec|
spec.name = "insurance"
spec.version = Insurance::VERSION
spec.authors = ["Engine Sample"]
spec.email = ["engine#engine.com"]
spec.homepage = "https://github.com/engine/engine_plugin"
spec.summary = "Engine Sample."
spec.description = "Engine Sample."
spec.license = "MIT"
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
# to allow pushing to a single host or delete this section to allow pushing to any host.
if spec.respond_to?(:metadata)
spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
else
raise "RubyGems 2.0 or newer is required to protect against " \
"public gem pushes."
end
spec.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"]
spec.add_dependency "rails", "~> 5.0.1"
spec.add_development_dependency "route_translator"
end
Then I go to my engine on terminal and run bundle install which generates a Gemfile.lock in my engine folder.
I then go to main application and add in main application Gemfile this:
gem 'insurance', path: '/home/Documents/Projects/insurance'
I also created some routes in my engine folder config/routes.rb
but when I run my application, I get this error
Gem::LoadError - pg is not part of the bundle. Add it to your Gemfile.:
Rails engines are actually gems, they are supposed to rely on parent app's Gemfile.lock and only specify dependencies in their gemspec.
If you want some gem to be a dependency of the engine - then add it to gemspec, and if you want to lock exact version - do so in dependency declaration (but in fact, it's better to specify a more loose version):
spec.add_dependency "pg", "0.15" # better use something like "~>0.15" here

When do you need a require in a rails Gemfile?

In my gemfile I have things like:
gem 'net-sftp', '2.1.1', :require => 'net/sftp'
gem 'backup', '3.0.27'
gem 'watu_table_builder', :require => 'table_builder'
gem 'browser', '0.1.6'
When in a Gemfile do you need a require? I've also found things like :require => false. Help?
If you omit the :require option, by default Bundler will attempt to require the gem by using the standard name-to-file conversion rule:
dashes are considered namespace separators and underscore classname separators
It means that the following gem statements
gem 'net-sftp'
gem 'backup'
gem 'foo_bar'
are equivalent to
gem 'net-sftp', require: 'net/sftp'
gem 'backup', require: 'backup'
gem 'foo_bar', require: 'foo_bar'
This works well if the gem author has followed the standard conventions. But in some cases, for a variety of reasons, this doesn't happen.
For instance, there are gems called foo-bar where the main filename is /foo_bar.rb or even /foo.rb. In this case you provide the :require option to tell Bundler which file you want to require.
Finally, require: false is used when you want a gem to be part of the bundle, but you don't want Bundler to load it by default.
This is useful, for instance, to lazy-load a gem in case it is used only in certain circumstances. Think about a rake task that includes a heavy gem. You don't want your application to load it on boot, but it needs to be part of the bundle or it will not be found.
In this case you pass the option require: false. Then, in your rake task you will require it manually as usual
require 'library'
The library will be loaded only when the task is invoked, not in the normal application execution.
A good example is whenever. The library must be part of the bundler because it must be bundled when you deploy the app, but it is intended to be run as a command line script. For this reason, you don't want Bundler to require it when the Rails application is started.
There are cases where you use groups instead of require: false.
See also the official Bundler documentation for require.
You need a :require if the name of the file that's required to activate the gem can't be inferred from the gem's name. Ruby convention is to name your gem the same thing as the "require" statement you should use, but not everything follows the convention.
:require => false disables automatic requiring by Bundler, so you'd need to use require 'foo' manually to use the code in foo. This is mainly useful if a gem is large or expensive to activate and only used in certain cases.
This is often used if the name of the library to require is different than the name of the gem. In your case it would download the gem 'watu_table_builder', but would call 'table_builder'.
require specifies other files that would be required on requiring this particular gem.
Documentation says
Each gem MAY specify files that should be used when autorequiring via
Bundler.require. You may pass an array with multiple files, or false
to prevent any file from being autorequired.
Refer to this for whole documentation

Loading gem at runtime in Rails 3

I have Rails 3.0.x application. I would like to load gems on runtime, without using Gemfile.
What I would like to accomplish is to load my application as usual, with regular gems being loaded by Bundler. After that I would like to load all gems (Rails Engines) located in a specific directory (but until runtime, I don't know what gems will that be).
Does anybody know if this is possible in Rails, maybe using Bundler API?
What you're trying to do is dangerous. If each of your Rails Engines are also gems - then they would also have Gemfiles with other dependencies, and those would in turn have other dependencies, etc. If you allow Bundler to resolve those, then you would have lesser problems at runtime.
Here's how you would do it without any hacks. Remember that your Gemfile is just Ruby code, and you can have gems which are not loaded by default.
# In your Gemfile, add at the end:
Dir[YOUR_RAILS_ENGINES_SUBFOLDER + "/*/*.gemspec"].each do |gemspec_file|
dir_name = File.dirname(gemspec_file)
gem_name = File.basename(gemspec_file, File.extname(gemspec_file))
# sometimes "-" and "_" are used interchangeably in gems
# for e.g. gemspec_file is "engines/my-engine/my_engine.gemspec"
# dir_name will be engines/my-engine
# gem_name will be my_engine
# Register that engine as a dependency, *without* being required
gem gem_name, :path => dir_name, :require => false
# e.g. this is similar to saying
# gem 'my_engine', :path => 'engines/my-engine', :require => false
end
Now you have all your dynamic Rails engines registered as gem dependencies. Bundler will resolve them, and all their sub-dependencies, so you don't have to worry about anything. Just run bundle install once before running the application, or whenever you add/remove any engine in that folder.
The good thing is, these gems will just be registered, and not loaded. So in your production code, you can now load whatever gem that you choose at runtime simply by saying require <your-engine-name>
Edit: Extra code comments
Try this:
Bundler.with_clean_env do
# require gems...
end

How to require the forked gem lib file ? Name conflicts?

I had to fork the gem Thor, coz my cli has one command run which is reserved in the Thor lib itself, changed its name to millisami-thor just in the .gemspec as follows:
Gem::Specification.new do |s|
...
s.name = 'millisami-thor'
...
end
and to use it, I pushed the gem under the name millisami-thor to rubygems.org and in the Gemfile of my cli project, I put gem 'millisami-thor', :require => 'thor'
Now while testing bundle exec cucumber features or to try out the executable, just did ./bin/executable --params and it worked out.
Now, I build the gem with gem build gemname.gemspec that generates gemname.gem and installed with gem install gemname.gem and it gets installed as well as the binary too. Fine, till here.
Now, when I use the binary cmd like executable --params, it looks for the original thor library instead of the forked one.
I figured out that this was due to the require ... in the executable.
require 'thor'
require 'fileutils'
require 'gemname/cli'
Cf::CLI.start
coz in there I explicitly required the original thor.
Now, when I change it to require 'millisami-thor, it cannot find and says:
...
custom_require.rb:36:in `require': no such file to load -- millisami-thor (LoadError)
...
In the Gemfile, I could have done gem 'millisami-thor, :require => 'thor' so that it loads the forked gem.
But how can I do the same if its just the require 'millisami-thor' ?
The only option I can think of is to change all the class names to 'MillisamiThor' instead of 'Thor' and the file names too. But this will be too messy and ugly.
I could have spotted this if I had installed my gem and test it before. But I did just in the test environment, in which the bundler requires the millisami-thor's thor file, so I didn't have this problem till today.
Is there any other way out to achieve this without any messy hacks?
You can keep the original gem setup and point Bundler to your git repository fork:
gem 'thor', :git => 'git://github.com/yourname/thor.git', :require => 'thor'
or even a local path
gem 'thor', :path => '/path/to/thor.git', :require => 'thor'

Bundler.require does not work for ActiveRecord in my gem

I just created a new gem (using bundler) and want to add Active Record support. So I added s.add_dependency "activerecord", "~> 3.0" to my gemspec. Then I use Bundler.setup and Bundler.require and thought that I have access to Active Record now, but I haven't. I have to explicitly use require "active_record". Any idea why Bundler.require does not work for me in that case?
Firstly, if you're packaging a gem, do not use Bundler.require. Bundler.require is for apps not gems.
In .gemspec, specify the dependencies of your deployed gem.
In your Gemfile, include the line gemspec to automatically include the dependencies listed in your .gemspec in your Gemfile.
You may also optionally create gem groups for dev and test.
In your code, explicitly require any libraries you need.
I lost a couple of hours on this today so I hope this helps.
(Sources 1, 2)
Secondly, though the ActiveRecord gem is called "activerecord", the lib is called "active_record". This is what you would need in Gemfile.
gem 'activerecord', :require => "active_record"
Unless you include the :require option, ActiveRecord won't be loaded correctly and you won't know about it until you try to use it.
If you want use Bundler you need define your Gemfile with Activerecord
gem 'activerecord', "~> 3.0.0"
Or you need define bundler to use your gemspec with adding gemspec in your Gemfile
gemspec
See http://gembundler.com/rubygems.html
I had this problem, and the issue in my case was that I was naming a directory in my gem active record, as in:
lib ->
active_record ->
base.rb <- containing some monkey patches to base
This was causing mass confusion including sweet error messages like:
Gem Load Error is: uninitialized constant ActiveRecord::Base
Did you mean? ActiveRecord::Base
Simply moving changing the file from lib/active_record/base.rb to lib/active_record_base.rb fixed it for me.

Resources