How do Ruby Gems Load in Rails? - ruby-on-rails

Say I have a ruby gem that I want to use, but it's use is only needed in one model or controller.
Is there a way to load that gem exclusively for that one model or controller?
Would it be a waste of resources for it to be available systemwide (callable from any controller)

The answer posted is incorrect, it's perfectly possible to only load a gem for a particular model or controller. Your Gemfile allows you to define groups that Bundler can use to require certain gems. By default, any ungrouped gems along with the gems for the environment you are currently running in are required, and any named groups are not required. So if you had the following Gemfile:
gem 'always_used_gem', '1.0.0'
group :rarely_used do
gem 'rarely_used_gem', '1.0.0'
end
Then rarely_used_gem would not be required on initial application load. If you had a particular method that needed that functionality, you could do this:
def do_stuff
Bundler.require :rarely_used
# use stuff from rarely_used_gem
end
One note: make sure the Bundler.require is inside a method call or something like that, or else the group will be required when the file is parsed (i.e. application boot)
In terms of whether you want to do this, it should really only be used for exceptional circumstances. You're trading boot speed for execution speed by doing this, which might make sense in development but probably doesn't in production. Also, you can use this method if you have some incompatibilities between two gems (we use this to resolve issues between a couple of AWS gems, for instance.)

No, it's not possible. Once a gem has been loaded it will be available for all controllers / models / everything. This should not be a primary concern for you. RAM and CPU time is cheap, programmer's time is not.

Bundler autoloads/-requires all gems, unless
#Gemfile
gem :somegem, :require => false
Then you can do require it when you need it
#some_ruby_file.rb
#within_some_context
require 'somegem'
How useful it is and when to do this (except for when the load order is important, as for the mocha gem (needed to be loaded last)), I don't know. I leave this to your own judgement.

Related

Conditionally require gems in Gemfile based on Rails or Sinatra framework?

I am writing a gem that can be used with both Sinatra and Rails, however, the gem dependencies are different based on which framework the developer is using the gem on.
If it's a rails app, we need 'sass-rails' and 'coffee-rails'
If it's a sinatra app, we need 'sass' and 'coffee-script'
Ideally bundler would just install the necessary gems based on which framework this gem is being loaded into, but I can't seem to figure out how to conditionally specify dependencies.
Any suggestions would be much appreciated.
I would suggest you not to do that. It would be hackish and unreliable.
What you can do however is divide and conquer! Build a generic version of your gem that is framework agnostic and only handles the logic, let's call it yourgem-core, then you can build two other gems based on that first one, called yourgem-rails and yourgems-sinatra.
It's much better, only logic and logic test in yourgem-core, only rails integration tests in yourgem-rails, only sinatra integration tests in yourgem-sinatra
You can use :group option in bundler.
Reference: http://bundler.io/v1.5/groups.html

Is there a way to package all of the gem's my gem depends on WITH my gem?

I'm working on writing a gem that has several gem dependencies, one of which depends on a gem that broke backwards compatibility in a newer version. This got me to thinking - I don't want the gem that I'm building to become "that gem" that is making it difficult for people to update their application. I also don't want to force people using my gem to have to use specific versions of the gems it depends on in the rest of their application.
On one hand, I could just rewrite all of the code from those dependencies to bundle them with my gem and remove the dependency all together, but that seems a little tedious. Is there any way for me to simply include the gem dependencies directly in my gem, then wrap them in a module so my packaged versions don't conflict with the versions used by the rest of the application?
I don't think what you want is possible via existing Ruby tools. However, if backwards-compatibility for you when relying on your dependency is purely a syntax/usage issue, and not a low-level difference between versions, then you don't have to import and maintain the old gem's code. You have another option: Create a "shim" layer in your gem that provides the functions you need from either new or old interface of the dependency.
In practice it might look like this, assuming by way of example that it was the constructor to the Thingy class that has changed:
module DependencyShim
def new_Thingy( new_style_args )
if thingy_is_new
Thingy.new( new_style_args )
else
Thingy.new( convert_args_to_old_style( new_style_args ) )
end
end
# convert_args_to_old_style() not shown
private
def thingy_is_old
Thingy::VERSION < '1.3.4'
end
def thingy_is_new
Thingy::VERSION >= '1.3.4'
end
end
Better abstractions are very likely possible, but are difficult to predict as I don't know the nature of difference between old and new, and how tightly bound your code is to your dependency.
This is obviously painful if there are radical changes between versions, and your own gem uses the dependency heavily. But even then it may still be less painful than re-implementing and maintaining the dependency inside your own gem.
I would recommend looking at making your gem the most compatible with latest versions of your dependencies, and over the long term moving to deprecate support for the old dependencies, based on what you know of your user base. There may be good reasons for all involved to move away from the old dependency.
Assuming you are using bundler, you can specify versions for dependencies in your Gemfile like this:
gem "my_dependency", "0.6.1"
There's a whole load of other options too such as 'version greater than X' etc. Read the bundler docs for more info.

Rails Engines: Where to define gems only used in testing

I'm building an engine, and I want to use VCR and Webmock for testing.
The documentation within the Gemfile generated when an engine is created, seems to suggest that all an engine's gems should be loaded via gemspec, but the only options for this are add_dependency and add_development_dependency. If I use the latter, VCR and Webmock get loaded into my development environment, and I then have to explicitly disable Webmock in the development environment. I'd rather not do that as a host app may want these gems to work in development, and my engine disabling them may be unexpected.
The obvious solution would appear to be to use the engine's Gemfile:
group :test do
gem 'vcr'
gem 'webmock'
end
Is this the right way to load gems that are only used when testing an engine?
Are there any gotchas doing this?
One of the well known rails engines, rails_admin (https://github.com/sferik/rails_admin) uses that approach, so I believe it can be considered a good practice.
What's the load order for Rails app and Rails app's Rails Engines?
My guess is that the Rails app Gemfile is the determining factor for a gem being loaded or not loaded. This might be worth a try in a Rails test app.
I believe the answer is that there is nothing wrong with declaring in an engine's Gemfile, gems only used for testing and debugging the engine code. Further, I think the Gemfile template should be made less ambiguous, and have submitted a pull request to this effect:
https://github.com/rails/rails/pull/11881

Rails + Gems (in general): How do gems work?

I've been using Rails for a while and have always used gems in my gemfile, but I never really understood how the functionality of gems that I install actually become available. Say I use the has_permalinks gem (http://haspermalink.org/). It provides a .generate_permalink! method for my Model. Where does this method get defined? How come just I can use this method all of a sudden just by installing the gem? Is there some sort of include/require/load to initialize the gem's code so that it becomes accessible to the rest of the application? Also, where is this code stored when I install the gem?
I answered your questions separately, and out of order, but I think it actually might make it easier to understand the answers in this order.
Also, where is this code stored when I install the gem?
If you're using Bundler, you can do bundle show has_permalink and it will show you where that gem is installed. Here's an example of me doing it with the pg gem:
✗ bundle show pg
/Users/jasonswett/.rvm/gems/ruby-1.9.2-p320#jason/gems/pg-0.11.0
Where does this method get defined?
If you do the bundle show thing, it returns a path - the method is defined somewhere in there. (You can use grep -r 'def generate_permalink' /gem/path to find exactly where if you want.)
How come just I can use this method all of a sudden just by installing
the gem? Is there some sort of include/require/load to initialize the
gem's code so that it becomes accessible to the rest of the
application?
Look at this part of the doc about the Rails initialization process:
http://guides.rubyonrails.org/initialization.html#config-boot-rb
In a standard Rails application, there’s a Gemfile which declares all
dependencies of the application. config/boot.rb sets
ENV["BUNDLE_GEMFILE"] to the location of this file, then requires
Bundler and calls Bundler.setup which adds the dependencies of the
application (including all the Rails parts) to the load path, making
them available for the application to load.
It looks like, fairly early on in the process, Rails looks at your Gemfile and loads all your gems via Bundler. So there's your include.

Problem creating Rails 3 Engine

I'm working on creating my first Rails 3 engine and I'm currently getting the following error message
is a Railtie/Engine and cannot be installed as plugin (RuntimeError)
To give you a bit more background I'm not doing the Engine as a gem, but I've place the Engine files in the vendor/plugins folder. I know there is an issue with loading when in the plugins folder, but I'm not clear on how to solve the problem.
My rails/init.rb file in the Engine has the following code
require 'rails'
module RailsApp
module MyEngine
class Engine < Rails::Engine
config.after_initialize do
RailsApp::GameType.register do |game_type|
game_type.name = "TreasureIsland"
game_type.version = "0.1"
game_type.thumbnail = "teasure_island.jpg"
end
end
end
end
end
Suggestions?
I think I remember reading that Railties would not work in plugins directory, because they are intended to be loaded at a different point in the application boot process. I would recommend creating a gem using something like Jeweler, which does alot of the work for you. The purpose of the Railtie/Engine is to have a reusable component that can be included in multiple rails apps. Once you have a gem created, you can point to the local gem path within your Gemfile. This allows you to see changes in your engine code inside your rails app without having to build and reinstall the gem every time you make a change to the engine code. Of course you would want to point bundler to the installed gem in production. I would recommend putting it on github and using that URL in your Gemfile in production.
Bundler local gem example:
#Gemfile
gem "my_engine", :require => "my_engine", :path => "/my_engines/my_engine"
Check out the Modern Rubyist's website. He has a good series on creating Railties and Engines. There may have been some changes to Rails since this was written, but I think most of it is still relevant. It helped me out a good bit when I was learning how to write Engines with Rails 3.
http://www.themodestrubyist.com/2010/03/01/rails-3-plugins---part-1---the-big-picture/
http://www.themodestrubyist.com/2010/03/05/rails-3-plugins---part-2---writing-an-engine/
http://www.themodestrubyist.com/2010/03/16/rails-3-plugins---part-3---rake-tasks-generators-initializers-oh-my/
http://www.themodestrubyist.com/2010/03/22/rails-3-plugins---part-4---more-on-generators/
John, I believe engines (which are typically gems) vs plugins (which live in vendor) are loaded at different points in the rails initialization process.
Engines actually have a bit more flexibility, they can hook deeper into rails. In addition, packaging as a gem has a lot of advantages: easier to share across apps, easier to maintain in a separate code repo, easier version control.
I'm creating my first rails engine right now and created a useful starting point and walk-through for getting started:
http://keithschacht.com/creating-a-rails-3-engine-plugin-gem/

Resources