Loading local gems through Bundler and mounted apps - ruby-on-rails

I'm creating a gem (let's call it mygem) that is essentially a Sinatra server intended to be mounted within Rack based apps.
Inside my gem's gemspec file, I have the following:
gem.add_dependency 'kss'
And Inside my gem's Gemfile, I have the following
source 'https://rubygems.org'
gemspec
gem "kss", :path => "/Users/me/code/kss"
Now when running the server from within mygem's folder, this works exactly as expected: instead of fetching out for the kss dependency, it will look on my local drive and load that version.
The problem comes in when I add mygem to a Rails test app Gemfile. In my Rails test app Gemfile, I have the following line:
gem "mygem", :path => "/Users/me/code/mygem"
I would expect, upon a bundle install, that Bundler would load mygem and its dependencies; but for the kss dependency, instead of loading the local dependency, Bundler actually does fetches out to rubygems to find and load it. I'm assuming because in this case, it's only reading from the gemspec line and not including my dependency override.
Is there anything I can do to fix this behavior? I'd very much like to be able to run and test this stuff locally, but Bundler doesn't seem to recognize dependency overrides from a higher level app.
I'm completely open to any suggestions or changes if I'm going about this the wrong way.

Dependencies listed in your gemspec will become dependencies of the implementing application, while dependencies defined in the Gemfile will not become dependencies of the implementing application. I think you should be able to simply adjust the Rails test app Gemfile to be:
gem "kss", :path => "/Users/me/code/kss"
gem "mygem", :path => "/Users/me/code/mygem"

Related

I'm developing a Ruby gem, how can I try it out locally with my Rails app?

I am developing a gem meant to be used with Rails projects and want to try it out locally with another Rails app of mine.
I built the gem with bundle exec rake release which put a .gem file in the /pkg directory.
Then, in my Rails app, I added the following to my gemfile
gem 'mygem', '0.1.1', path: '/Users/me/projects/mygem/ruby/pkg'
I then ran bundle install which said it installed the gem. When I do this, it removes the gem from the path. IDK where it went.
When I start the Rails app, it's like the gem isn't included at all.
Interestingly, if I add a version that doesn't even exist, it still says bundle install works fine. (Example: gem 'mygem', '0.1.2345', path: '/Users/me/projects/mygem/ruby/pkg')
What am I supposed to do to try out my Gem locally with a Rails app?
This question is different from How can I specify a local gem in my Gemfile? because I explicitly tell bundle in my Gemfile to use the local gem, with the path given, and it still doesn't work. When I run bundle install, it says
Using mygem 0.1.1 from source at /Users/me/projects/mygem/pkg
So you'd think it works right, but it still doesn't.
Interestly, if I try it with a version number that doesn't exist, like mygem 1.2.3, it still runs bundle install successfully, which is really weird and seems like a bug:
Using mygem 1.2.3 (was 0.1.1) from source at /Users/me/projects/mygem/pkg
I prefer to use the following when working on a local gem side-by-side with a Rails project:
gem 'foo',
:git => '/path/to/local/git/repo',
:branch => 'my-fancy-feature-branch'

Have Bundler parse and load dependencies from Gemfile/s in gem/s loaded with :path

here's my current situation:
I am working on a rails engine ( "awesome_engine" ). I do all development via a host application, i.e. a rails app that specifies this engine as a dependency in its Gemfile with the :path parameter
gem "awesome_engine", :path => "awesome_engine"
This works well- the engine gem folder is under "awesome_engine" relative to the root of the host application. However "awesome_engine" references a gem that is also in development, "awesome_core". awesome_engine/Gemfile therefore reads:
gem "awesome_common", :path => "../awesome_core"
THe problem with this setup seems to be that Bundler does not look for a Gemfile in awesome_engine/ when it creates the application bundle for the host rails application, it only reads dependencies from the gemspec of "awesome_engine", which is fine for any single gem you'd want to have loaded in its "unpackaged" state, not so great for when you want to work on a graph of related gems at the same time
Am i
a) trying to bite off more than i can chew and should just dump "awesome_core" in the Gemfile of the host app while it is under development and be happy with it ( i won't be :-) )
b) missing something trivial on how to have Bundler do this
c) none of the above ( please specify: ________ )
thanks!
Andras
ps: i know Bundler does not parse "awesome_engine/Gemfile" because a) if i put crap in it bundle install in the host app still runs fine b) none of the other dependencies from that file end up in the host applications app bundle
So when you specify a gem through your (current project's) Gemfile, it is assumed that you are referencing something with well defined dependencies. This means that it is expected that there is a valid .gemspec file that names those dependencies. But this you already know.
My suggestion for c) would be to create your gems with bundle gem which sets up the gem in a way that information for it's Gemfile is drawn from the .gemspec file. Have a look at http://gembundler.com/v1.2/bundle_gem.html and just generate a test gem and read its sources, it's quite clear.
I hope that solves your problem.

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

What does config.gem (in environment.rb) do?

I've been told that doing:
config.gem 'tzinfo'
doesn't obviate the need to require 'tzinfo'. Is this true of all gems? If yes, what exactly does adding config.gem WHATEVER do?
config.gem should cause the gem to be automatically required. You should not need to do a manual 'require' call.
config.gem
Tells Rails to load this gem automatically
Tells Rails that this gem is needed for the application, so that rake gems:install will install it
The :source option can tell rails to get it from a nonstandard repository
The :lib option can tell rails to load a non-standard file from the gem (i.e. something not named after the gem itself)
If i'm correct, during the environment initialization 'config.gem' allows your app to setup and require GEM dependencies from within the app, without the need to have to install them manually. (As we did before) By calling "config.gem tzinfo" as you did above, it automagically requires the gem across the app. This helps when you deploy to an external server and need to prepare the app along with necessary gems, etc. You can then run RAKE GEMS:INSTALL and rails will pull in all your gems and require them.
A thing to note though is that if you DO NOT want a gem to be required across your app. Then add ":lib => false" after config.gem i.e (config.gem 'tzinfo' :lib => false).
In some cases, (I followed your link) if you're getting an uninitialized gem, and you've manually installed it. Make sure that the config.gem ":lib" directory matches with the correct :lib directory of the gem. I.E a gem may be packaged and installed as "nlewis-supergem", however I may need to point the lib at "supergem". i.e "config.gem "nlewis-supergem" :lib=>"supergem". It all depends on how some people package their gem and the corresponding libraries.
A quick tip is instead of installing manually always install the gem via "config.gem" and then rake GEMS:INSTALL to catch any wierd errors before deployment.
Hope this helps.

config.gem in environment.rb

Let's say in a Rails app you have some gems that you use in your app (we'll call them "primary gems") and you have vendored them for portability.
Let's say that those "primary gems" also require gems of their own - we'll call these "secondary gems".
When you are setting up your environment.rb, you have to say:
config.gem 'primary-gem'
for any of the gems you are using directly.
But, do you also need to say . . .
config.gem 'secondary-gem'
even if you are not using that gem explicitly in your app?
Or is it simply enough to include the gem in your vendor/gems directory for it to get picked up by your app?
At deploy time rails knows about your dependencies, so if you want to freeze your gems then you can run
rake gems:unpack:dependencies
to freeze them into the vendor directory.
At runtime however it's the gems job to load it's dependencies, and usually the gems do this, so a config.gem 'primary' should work.
No, you don't or at least you shouldn't. Each GEM specification should include it's own list of dependencies. When primary gem is installed, RubyGems automatically will install each gem dependency on cascade.
In other words, if A requires B that requires C+D, you only need to write
config.gem 'A'
When the command
gem install A
is run, RubyGems will resolve all the dependencies and install them.
You can view all A dependencies running (from a Rails project)
rake gems
Sometimes, a GEM author may forget to include some GEM dependencies in the specification. In this case you should specify them in your environment.rb to force the application to install them. Off course, it's also a good idea to contact the GEM maintainer so that it can fix the problem.

Resources