Wrapping class method via alias_method_chain in plugin for Redmine - ruby-on-rails

I am not sure if this problem is a general Rails problem or Redmine specific.
There is a class User which has a class method try_to_login. I wrote a module containing a method_alias_chain to wrap that method and provide additional functionality. This works fine if I go into the console and call try_to_login. My wrapper will be executed and everything is fine. However, when I run this on the server just the vanilla method is called. The wrapper is never touched. I added a logger command to the vanilla method to be sure and it is in deed being called.
Here is a simplified version of the code:
require_dependency 'principal'
require_dependency 'user'
require 'login_attempt_count'
module UserLoginAttemptLimiterPatch
def self.included(base)
base.extend ClassMethods
base.class_eval do
class << self
alias_method_chain :try_to_login, :attempt_limit
end
end
end
module ClassMethods
def try_to_login_with_attempt_limit(login, password)
user = try_to_login_without_attempt_limit login, password
#stuff here gets called via console but not via browser
user
end
def authentication_failed(login)
#important code here
end
end
end
User.send(:include, UserLoginAttemptLimiterPatch)
In addition this module is required when the plugin is loaded.

How are you requiring the module? If you are running in development mode, the User class could be reloaded after the first request which would clear out your patch and alias_method_chain.
You can get around it by doing the patch inside of a Dispatcher (which runs with every code reload):
require 'dispatcher'
Dispatcher.to_prepare do
Issue.send(:include, MyMooPatch)
end
Reference: http://theadmin.org/articles/2009/04/13/how-to-modify-core-redmine-classes-from-a-plugin/

Related

Rails: trouble including modules via a plugin

I'm trying to create a rails plugin and the problem I'm facing is that the app won't include my modules when migrating the plugin.
Here's what I have so far:
1. A file lib/patch/settings_helper_patch.rb with extension code
2. An init.rb file with require_dependency 'patch/settings_helper_patch'
3. Some code in settings_helper_patch.rb which is as follows:
module ValidateIssuePatch
module Patch
module SettingsHelperPatch
def self.included(base)
base.send(:include, InstanceMethods)
end
module InstanceMethods
def issue_options
#some code here
end
end
end
end
end
unless SettingsHelper.included_modules.include?(ValidateIssuePatch::Patch::SettingsHelperPatch)
SettingsHelper.send(:include, ValidateIssuePatch::Patch::SettingsHelperPatch)
end
After I migrate the plugin, I wish to use the issue_options method, but I get undefined local variable or method error.
If I run SettingsHelper.included_modules.include?(ValidateIssuePatch::Patch::SettingsHelperPatch) from the console, I get uninitialized constant Patch::SettingsHelperPatch.
However, if I call ValidateIssuePatch from the console, I get => ValidateIssuePatch in response.
Can anyone tell me what is the magic I'm missing here?
Firstly, if your module is only going to have instance methods, I would recommend using the following easy-to-follow syntax:
module ValidateIssuePatch
module Patch
module SettingsHelperPatch
def issue_options
# code
end
end
end
end
SettingsHelper.include(ValidateIssuePatch::Patch::SettingsHelperPatch)
Secondly, the reason why ValidateIssuePatch might be defined is that some other file has it which is being required properly. This file isn't being executed in any way. I would raise an error somewhere that, when raised, will verify that the code is / isn't being executed. Something like the following:
module ValidateIssuePatch
module Patch
module SettingsHelperPatch
raise "All good" # remove this afterwards
def issue_options
# code
end
end
end
end
SettingsHelper.include(ValidateIssuePatch::Patch::SettingsHelperPatch)
Chances are that the error won't be raised and it'll confirm that your file isn't being required - either not at all or not in the right order.
To further verify this, simply open up your console and do the following with your existing code:
ValidateIssuePatch::Patch::SettingsHelperPatch #=> error
require path_of_file
ValidateIssuePatch::Patch::SettingsHelperPatch #=> no more error
Finally, why do you check for the module already being included in SettingsHelper? (referring to the unless condition) Your code should be including the module only once, not "maybe only once".

Including methods to a controller from a plugin

Using Rails 2.3.11, I'm creating a plugin for Redmine that add methods to ApplicationController.
I've created the following module, in the plugin :
module ApplicationControllerPatch
def self.included(base) # :nodoc:
base.class_eval do
rescue_from AnException, :with => :rescue_method
def rescue_method(exception)
...
end
end
end
end
Now, if I include this module directly into the application_controller.rb file, like this:
class ApplicationController < ActionController::Base
include ApplicationControllerPatch
...
end
Everything works just fine, however I would like to avoid editing the core source by including this module from the plugin itself.
So far, if I do:
ApplicationController.send(:include, ApplicationControllerPatch)
directly from this module file (located in the plugin folder). This will load properly for the request and then it gets overwritten by the controller (I guess).
What's is the way to accomplish this ?
A common pattern is to use Dispatcher.to_prepare inside your plugin's init.rb. This is required because in development mode (or generally if config.cache_classes = false) Rails reloads all classes right before each request to pick up changes without the need to completely restart the application server each time.
This however means the you have to apply your patch again after the class got reloaded as Rails can't know which modules got injected later on. Using Dispatcher.to_prepare you can achieve exactly that. The code defined in the block is executed once in production mode and before each request in development mode which makes it the premier place to monkey patch core classes.
The upside of this approach is that you can have your plugins self-contained and do not need to change anything in the surrounding application.
Put this inside your init.rb, e.g. vendor/plugins/my_plugin/init.rb
require 'redmine'
# Patches to the Redmine core.
require 'dispatcher'
Dispatcher.to_prepare do
ApplicationController.send(:include, MyPlugin::ApplicationControllerPatch) unless ApplicationController.include?(RedmineSpentTimeColumn::Patches::IssuePatch)
end
Redmine::Plugin.register :my_plugin do
name 'My Plugin'
[...]
end
Your patch should always be namespaced inside a module named after your plugin to not run into issues with multiple plugins defining the same module names. Then put the patch into lib/my_plugin/application_controller_patch.rb. That way, it will be picked up automatically by the Rails Autoloader.
Put this into vendor/plugins/my_plugin/lib/my_plugin/application_controller_patch.rb
module MyPlugin
module ApplicationControllerPatch
def self.included(base) # :nodoc:
base.class_eval do
rescue_from AnException, :with => :rescue_method
def rescue_method(exception)
[...]
end
end
end
end
end
This kind of problem occurs only in dev because the classes are reloaded but not gems.
So add your send method in a config.to_prepare block within config/environments/development.rb
Read Rails doc concerning initialization process for further details.

Can't access model from inside ActionController method added by a Rails engine

I am developing a Rails engine to be packaged as a gem. In my engine's main module file, I have:
module Auditor
require 'engine' if defined?(Rails) && Rails::VERSION::MAJOR == 3
require 'application_controller'
end
module ActionController
module Auditor
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def is_audited
include ActionController::Auditor::InstanceMethods
before_filter :audit_request
end
end
module InstanceMethods
def audit_request
a = AuditorLog.new
a.save!
end
end
end
end
ActionController::Base.send(:include, ActionController::Auditor)
where AuditorLog is a model also provided by the engine. (My intent is to have "is_audited" added to the controllers in an application using this engine which will cause audit logging of the details of the request.)
The problem I have is that when this code gets called from an application where the engine is being used, the AuditorLog model isn't accessible. It looks like Ruby thinks it should be a class in ActionController:
NameError (uninitialized constant
ActionController::Auditor::InstanceMethods::AuditorLog)
rather than a model from my engine.
Can anyone point me in the right direction? This is my first time creating an engine and attempting to package it as a gem; I've searched for examples of this and haven't had much luck. My approach to adding this capability to the ActionController class was based on what mobile_fu does, so please let me know if I'm going about this all wrong.
Use ::AuditorLog to access the ActiveRecord class (unless you have it in a module or namespace, in which case you'll need to include the module name).

Can I monkey-patch an existing ActiveRecord Observer?

I'm trying to monkey patch an existing ActiveRecord Observer; specifically, IssueObserver from the code base of Redmine 1.0.4.
In a plugin's init.rb, I have include the *patch.rb file:
require File.join(File.dirname(__FILE__), 'lib/issue_observer_patch.rb')
IssueObserver.send :include, IssueObserverPatch
This is done outside the Redmine::Plugin.register block.
The module does something like:
module IssueObserverPatch
def self.included(base)
base.send :alias_method_chain, :after_create, :audit
end
def after_create_with_audit(issue)
after_create_without_audit(issue)
issue.logger.info('***'*50)
# Insert a new Audit instance.
end
end
However, when I run the server (using script/server, which is using Mongrel), the patch seems to do nothing. The ***..*** string does not get logged when I create a new issue.
By changing the IssueObserver and including a sentence to log the after_create event, and then running the server and creating an issue; I can see this log, but not the log on after_create_with_audit method.
Is there any proper way to patch an ActiveRecord Observer?
It seems that Rails VM are loaded and unloaded on demand. So, changing the code on the init.rb to the following solves the problem:
config.to_prepare do
IssueObserver.send :include, IssueObserverPatch
end
This makes this block of code to be execute per VM on production and per request on development.

Trying to understand how a Controller uses a Plugin/Module

Ok so I am using some module/lib/plugin (not sure of the exact name), let's say its a authentication/authorization plugin like: http://github.com/technoweenie/restful-authentication
class HomeController < ApplicationController
some_module_name_here
end
Now just from adding the code above 'some_module_name_here', I can access to methods from that module.
What is that line of code doing that gives me access to methods/objects from the module?
Is that declaring a variable like in say java/c#:
public SomeModule _someModule;
I know that plugins/modules basically extend the class under the covers, but how does it do this with a single line of code?
Is it called in the constructor somehow?
When you create a ruby plugin, and load it into the rails app via environment.rb, bundler, or a require call, the methods are loaded as "modules" that can be called. The ones that act like you're talking about will have an extra method called acts_as_list or something similar. All that method does is include the methods of the module into the class where that line was called.
Here's an example, which you could include in your app's lib folder and play with:
module Bellmyer
module Pointless
def self.included(base)
base.extend PointlessMethods
end
module PointlessMethods
def acts_as_pointless
unless included_modules.include? InstanceMethods
extend ClassMethods
include InstanceMethods
end
end
end
module ClassMethods
def pointless_class?
true
end
end
module InstanceMethods
def pointless_instance?
true
end
end
end
end
The module is available to any ruby class in your app, but the methods don't actually get loaded until you call acts_as_pointless, which then includes and extends your class with the methods listed. Only the acts_as_pointless method is immediately available to the model. This is the standard pattern for an ActiveRecord plugin.
That's not how it works.
When the plugin or gem is loaded it adds a class method to, in this case, ApplicationController named some_module_name. When you call that methods, a bunch of other class and instance methods are included.
Check out your favourite gem or plugin to see how they do it exactly.

Resources