views:

392

answers:

2

I'm trying to write a plugin that will extend InheritedResources.

Specifically I want to rewrite some default helpers.

And I'd like it to "just work" once installed, w/o any changes to application code.

The functionality is provided in a module which needs to be included in a right place. The question is where? :)

The first attempt was to do it in my plugin's init.rb:

InheritedResources::Base.send :include, MyModule

It works in production, but fails miserably in development since InheritedResource::Base declared as unloadable and so its code is reloaded on each request. So my module is there for the first request, and then its gone.

InheritedResource::Base is 'pulled' in again by any controller that uses it:

Class SomeController < InheritedResource::Base

But no code is 'pulling in' my extension module since it is not referenced anywhere except init.rb which is not re-loaded on each request

So right now I'm just including the module manually in every controller that needs it which sucks. I can't even include it once in ApplicationController because InheritedResources inherites from it and so it will override any changes back.

update

I'm not looking for advice on how to 'monkey patch'. The extension is working in production just great. my problem is how to catch moment exactly after InheritedResources loaded to stick my extension into it :)

update2

another attempt at clarification:

the sequence of events is

  • a) rails loads plugins. my plugin loads after inherited_resources and patches it.
  • b) a development mode request is served and works
  • c) rails unloads all the 'unloadable' code which includes all application code and also inherited_resources
  • d) another request comes in
  • e) rails loads controller, which inherites from inherited resources
  • f) rails loads inherited resources which inherit from application_controller
  • g) rails loads application_contrller (or may be its already loaded at this stage, not sure)
  • g) request fails as no-one loaded my plugin to patch inherited_resources. plugin init.rb files are not reloaded

I need to catch the point in time between g and h

A: 

What you are attempting to do is usually called "MonkeyPatch" - changing the way one module or class is working by "overriding" methods.

It is a common practice in Rails, but it doesn't mean it is the best way to do things - when possible, it is better to use common inheritance (it is more explicit about the changes you make).

Regarding your questions about "where to put the files": it is usually the lib/ directory. This can mean the lib of the rails app, or a lib directory inside a gem or plugin, if you are into that sort of thing.

For example, if the file you want to change is lib/generators/rails/templates/controller.rb of inherited resources, the first thing you have to do is replicate that directory structure inside your lib/ folder ('lib/generators/rails/templates/controller.rb')

Inside that new file of yours, (empty at the beginning) you can override methods. However, you must also the modules/classes hierarchy. So if the original gem had this:

module foo
  module bar
    def f1
    ...
    end
    def f2
    ...
    end
  end
  def f3
  ...
  end
end

And you wanted to modify f1, you would have to respect the foo-bar modules.

module foo
  module bar
    def f1
    ... # your code here
    end
  end
end

Now the last thing you need is to make sure this code is executed at the right time. If you are using the application's lib/ folder, you will need to create an entry on the initializers/ folder and require your new file. If you are developing a gem/plugin, you will have a init.rb file on the "root" folder of that plugin. Put the 'require' there.

I'm not very familiar with this unloadable stuff; maybe I'm asking something obvious but- have you tried making your extension module unloadable, too? (You shouldn't need this if you monkeypatched the module instead of creating a new one)

egarcia
Please read the question before you answer :)a) I didn't ask for "how to monkey patch". My extension is working.b) I was actually asking about the "make sure this code is executed at the right time" which you left for the end w/o actually answering (see below)c) no, I can't put code in initializers, I'm creating a plugin, and "stick your initializer there on install.rb is not an answer I'm looking ford) unloadable makes sure it unloads on each request in development.e) monkey-patching or including your own modules is completely equivalent regarding 'surviving the reload' - both don't :)
Vitaly Kushner
Sorry for having wasted your time. I can't help you then, good luck.
egarcia
+2  A: 

The Rails::Configuration, config in the environment files, allows registering a callback on the dispatcher that runs before each request in development mode, or once when in production mode.

config.to_prepare do
   # do something here
end

The problem is, I don't think your plugin has access to config when the init.rb file is run. Here is a way to register your callback directly in the dispatcher. Just put this in the init.rb file.

require 'dispatcher'
::Dispatcher.to_prepare do
    puts "hi there from a plugin"
end

Warning: I don't know what side effects this may have. If possible, try to get access to config and register the callback tha right way.

Kevin
this probably ins't too dangerous. i found google cites here http://theadmin.org/articles/2009/04/13/how-to-modify-core-redmine-classes-from-a-plugin/ and here http://www.oliyiptong.com/blog/2008/08/22/rails-to_prepare-executing-code-before-each-request/
Kevin
just what I needed.
Vitaly Kushner