how to control gems vs plugins loading order in Rails - ruby-on-rails

I have a plugin that must be loaded before resource_controller. The reason is that Resourcecontroller tries to load ApplicationController and it depends on the said plugin (and will fail to load if plugin's init.rb was not loaded yet).
The problem is that ResourceController comes from a gem and not a plugin.
Is there a way to load plugins before the gems (from environment.rb's "config.gem ...")?

There's no present way to load plugins before gems if you depend exclusively on config.gem to load them, but that doesn't mean that you can't take the loading of the resource_controller gem into your own hands.
As a very brutal solution, you can remove the relevant config.gem line, and then explicitly 'require' it at the bottom of environment.rb.

For rails2.3x in environment.rb set your gem to lib => false and then require the gem in the after_initialize block
config.gem 'some_gem', :lib => false
config.after_initialize do
require 'some_gem'
end
That'll do it.

a quick glance at the Initializer.rb code shows:
load_gems
load_plugins
# pick up any gems that plugins depend on
add_gem_load_paths
load_gems
check_gem_dependencies
If I understand it correctly, gems always comes before plugins... then some gems that the plugins require.
why not use resource_controller as a plugin, also?
just don't use the "config.gem ..." and put it in the plugins directory.

Related

how to set gem dependencies without having to declare them on the Gemfile that uses it?

I'm building a gem locally, suppose it name is "MyGem".
Now suppose that MyGem depends on other already built gem, for example "cancan".
So, i've added in my mygem.gemspec the line:
add_runtime_dependency("cancan")
Here is the problem: if I installl MyGem into a new project by adding it into my gemfile like this:
gem "mygem", :path => "path/to/my/local/gem"
then this new project is not being able to use cancan methods, and I have to explicity declare cancan on the new project gemfile in order to use it.
I tried also using gemspec method, but didn't solve my problem either.
Any ideas?
Update
I just wanted to add that when i only have myGem declared in my new application gemfile, after I run bundle install all the dependencies are installed.
That is, if i run gem list the "cancan" gem is displayed, but I still can't access it methods from the application level.
Thanks for the help.
Ok, i've solved this. I'm not sure if this is the best solution but it did work.
Making the application level developer to explicity add the dependencies in his gemfile didn't make any sense. So, as the gems did were being installed, i just required all the gem dependencies in my my_engine.rb file inside my gem.
Following the example, in my my_engine.rb I added the following line
require "cancan"
And that'it...
Even better you could do:
autoload :CanCan, "cancan"
So the module would be loaded only when it is called.
And even better than that, you could load only the file from cancan that you are using (maybe you don't need to load all of it).
You can add that line in you my_gem.rb file or your engine.rb file if you are using engines.
That worked for me, I hope this help someone.
Use add_dependency instead of add_runtime_dependency (this may help with cancan) then run bundle update on your new project.
Does the "cancan" gem name actually match the name to be require'd?
Example for yajl-ruby gem:
Gem::Specification.new do |s|
s.name = %q{yajl-ruby}
...
But when require'ing the library, you'd use a different string:
require 'yajl'
That means in your Gemfile, you have to explicitly require the dependency (which you said you wanted to avoid).
gem 'yajl-ruby', '>=1.0', :require => 'yajl'
To avoid needing to do this, and if you're the author of the "cancan" dependency, you should make the gem name matches the require name.
The only alternative I can think of is require'ing the dependency directly in one of your source files (like you did in your solution).

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

Rails optional gem config

What do you do when you want to use a gem for development/testing that you don't want to force other devs to use? Right now I have
begin
require 'redgreen'
rescue LoadError
end
in test_helper.rb and no gem config, but that seems like a clumsy approach, albeit a functional one. I'd like to do something like the following:
config.gem "redgreen", :optional => true
Any other suggestions? Or should I just vendor those pretty superficial gems...?
EDIT
To be clear, I am only talking about those specific gems, like redgreen, which aren't actually used in the functional code, but only in the coding process. There is no need to vendor these at all, except to avoid the conditional require.
Gems that are specific to your development environment should be installed in your gemset or local gems, but not in the Gemfile.
A classic example is the ruby-debug-base19x which Rubymine needs for debugging. This is installed in your local gemset, but not in the Gemfile because not all coders use Rubymine.
[EDIT]
Indeed, everything is run in the context of the bundle, and outside gems are not reachable. There do exist some workarounds indeed. Most of them are dirty :)
I found a lot of good solutions in this bundler issue.
The nicest solution was to add this to your .irbrc :
# Add all gems in the global gemset to the $LOAD_PATH so they can be used even
# in places like 'rails console'.
if defined?(::Bundler)
global_gemset = ENV['GEM_PATH'].split(':').grep(/ruby.*#global/).first
if global_gemset
all_global_gem_paths = Dir.glob("#{global_gemset}/gems/*")
all_global_gem_paths.each do |p|
gem_path = "#{p}/lib"
$LOAD_PATH << gem_path
end
end
end
require 'irb/completion'
require 'rubygems'
require 'wirble'
Wirble.init
Wirble.colorize
If you then install wirble to the global gemset, it can then be found.
Original source: https://gist.github.com/794915
Hope this helps.
I answered a similar question of my own here
User-level bundler Gemfile
One way to do this is to create different environments:
group :scott do
end
Then
bundle --with-env=scott
Ok, I think I've come up with something. Basically, the idea is to only execute a secondary Gemfile when a Rails app is executing. To do this we add two things:
First, we alter the rails script a little:
# in ./script/rails
Kernel::IN_RAILS_APP = true
APP_PATH = File.expand_path('../../config/application', __FILE__)
require File.expand_path('../../config/boot', __FILE__)
require 'rails/commands'
Second, we tell bundler to pull in the secondary Gemfile if we're in a rails app and a secondary file exists:
# Gemfile
if Kernel.const_defined?(:IN_RAILS_APP)
local_gemfile = File.dirname(__FILE__) + "/Gemfile.local"
if File.exists?(local_gemfile)
puts 'using local gemfile'
self.instance_eval(Bundler.read_file(local_gemfile))
end
end
Now you can add a Gemfile.local to your project and run specific gems on a per-machine basis. bundle install works normally since the IN_RAILS_APP constant doesn't exist.
** Make sure to add Gemfile.local to your .gitignore.
In my opinions this is what environments are for. Fortunately there is also a way provided to do it with what is in your Gemfile, this is also how rails use it: groups
Pretty much use the environments the same way rails use it. Here is what you could find in your Gemfile:
group :test do
# Pretty printed test output
gem 'turn', :require => false
end
And here is what you can find in your config/application.rb
Bundler.require(:default, Rails.env) if defined?(Bundler)
All you would need to do is to change your local environment settings and the others working with you won't be affected unless they decide to. Everything gets committed and nothing gets lost.
Here some links :
http://yehudakatz.com/2010/05/09/the-how-and-why-of-bundler-groups/
http://gembundler.com/groups.html
If you want it to be optional, it's better to freeze the gem as a plugin. However, it's not a good idea to use different gems than the rest of a development team, as it creates some inconsistencies in the codebase that can be hard to track down later. I would say add it to config.gem, and just tell the other developers to do:
rake gems:install
And you're done.
This is how I tackled the same problem under Rails 3.1. In my Gemfile:
if File.exists? './tmp/eric_dev_gems'
gem 'redgreen'
gem 'awesome_print'
gem 'wirble'
gem 'wirb'
gem 'hirb'
end
Create a file in ./tmp/ (or in some folder which is in your .gitignore) of your choosing. I used eric_dev_gems. This should be ignored by git, and will only exist on your system unless one of your teammates decides he wants to create that file too.
I solved it by putting this in my gem file:
$gem_names ||= ENV['GEM_PATH'].split(':').map{|g| Dir.glob("#{g}/gems/*").map{|p|p.split('/gems/').last}}.flatten
gem 'redgreen' if $gem_names.any?{|n| n=~/redgreen/ }
That way the gem will only be used if you manually installed it on your system.
This works well but has the downside that it puts the gem name in the Gemfile.lock. This is of little consequence because the gem does not get installed with bundle install but it does make your lock file a bit messy and can cause the lock file to change a bit from one developer to the next.
If that is an issue for you another option is to keep the gemfile clean and require the gem by its full path, or you can add the path for just that gem. Like this:
$gem_paths ||= ENV['GEM_PATH'].split(':').map{|g| Dir.glob("#{g}/gems/*")}.flatten
$gem_paths.grep(/redgreen/).each {|p|$LOAD_PATH << p+'/lib'}
require 'redgreen' rescue nil

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.

How can I modify Rails' config within an engine plugin?

I'm working on an engine for Rails as a plugin. I'd like it to be able to make necessary changes to Rails' configuration when it's loaded so it can specify its Gem dependencies as well as add some load paths.
The plugin's init.rb file has access to the config object but this is effectively read-only, you can specify a gem but it makes no difference, the initializer must have run already at this point.
I've got around this for now by requiring a file with a new Rails::Initializer block like so:
Rails::Initializer.run do |config|
config.gem "authlogic", :version => ">= 2.0.9"
# etc
end
This works but wipes out any existing configuration in the main application's environment.rb.
Maybe I can solve this by having a generator in the engine that adds something to environment.rb that loads the plugin's config at the right stage, or maybe there is a way of adding a file to config/initializers to do this job. Not sure how best to go about this though.
I would go with the config/initializers route. That is the standard folder to put plugin-specific config code and it will get loaded at the correct time.
For implementation, I would try my hardest to choose sensible defaults for everything that allowed me to not have a config file. (I understand this is not always possible.)
Next I would create a generator with the plugin that would automatically create the config file in config/initializers using:
./script/generate plugin MyPlugin --with-generator
Finally, I would put something in install.rb of my plugin to run the generator script when the plugin is installed. This way the config file is generated automatically with the install, and the user still has an easy way to regenerate if he wants to restore the default configuration.
Are you sure you want to distribute this as a plugin rather than a gem? If you package your engine as a gem then you can specify gem dependencies as part of your gem build process. For example, if you use Jeweler to create your gem you just add a single line:
s.add_dependency 'authlogic'
When your gem is installed it will make sure all dependencies are installed. Google 'jeweler gem dependency' for a full Jeweler config example.
Also, I've been doing a lot of work on my own rails engine and recently extracted a lot of useful base functionality. You may find this helpful for other engine issues:
http://keithschacht.com/creating-a-rails-3-engine-plugin-gem/
You can easily add this line to init.rb (under your plugin directory)
config.gem 'quick_magick'
I tried it with rails 2.3.5 and it worked like magic.

Resources