Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make Rails load a plugin *after* the application code?

I'm trying to write a plugin that defines a MongoMapper model. The problem is that when I run script/console, I get this error:

/home/helder/.rvm/gems/ruby-1.8.7-p249/gems/mongo_mapper-0.8.2/lib/mongo_mapper/connection.rb:29:in ``database':NameError: uninitialized class variable @@database_name in MongoMapper::Connection`

which leads me to think that it's trying to load my plugin model before setting up the database connection. How do I make it load the plugin after the rest of my application code?

like image 570
agentofuser Avatar asked Jun 25 '10 07:06

agentofuser


1 Answers

I will try to address both the error I encountered as well as the general question as stated in the title.

The specific error

I figured out what the problem was. As I said in a comment above, the problem is that Rails (2.3.8), when using ActiveRecord, first sets up the database connection, then loads gems, then plugins (in this order). So if you have any plugins that need to access the database during their initialization (that is, inside the plugin's init.rb or some other file required by this one), everything works fine.

But when using MongoMapper, Rails loads MongoMapper's classes together with all other gems/plugins, but doesn't set up its connection (Rails doesn't do it, and the plugin doesn't trigger that itself either). The way currently recommended on MongoDB's documentation is to create an initializer like this:

MongoMapper.connection = Mongo::Connection.new('localhost', 27017)
MongoMapper.database = "#myapp-#{Rails.env}"

if defined?(PhusionPassenger)
   PhusionPassenger.on_event(:starting_worker_process) do |forked|
     MongoMapper.connection.connect_to_master if forked
   end
end

But since plugins are initialized before files in config/initializers are run, if you try to define a MongoMapper model, as soon as it gets to a call that access the database (like a call to the key class method), BOOM. You get the error quoted in the question.

One way to solve this is to not require in init.rb the files that need the database, but rather only add them to the load path (if they're not in lib/ or app/models, which Rails automatically adds to the load path). That way, the models will only be required by Rails' auto class loader when they are referenced, which will usually be inside your application code. By then, the db connection will already be set up. Of course, this only works if you indeed don't need to reference these classes during your plugin's initialization. Otherwise, read on.

The general question

How to make Rails load a plugin after the initialization code?

On your plugin's init.rb, throw anything that needs waiting inside this block:

config.after_initialize do
  # require my models
  # do this
  # do that
end

That config variable is the same passed to the block which is passed to Rails::Initializer.run inside your config/environment.rb file, and is made available to your plugin's init.rb by Rails at no extra cost.

Anything you put inside that block will be executed (by Rails::Initializer#after_initialize) after everything is finished loading and getting initialized, but before any requests come in. Enjoy.

like image 172
agentofuser Avatar answered Sep 29 '22 08:09

agentofuser