Project

General

Profile

Plugin Internals » History » Revision 3

Revision 2 (Mischa The Evil, 2009-04-01 00:05) → Revision 3/24 (Mischa The Evil, 2009-07-28 00:40)

h1. Plugin Internals 

 {{>toc}} 

 This page will be used as a central place to store information about plugin-development in Redmine. 

 h2. Require a certain Redmine version 

 Sometimes plugins require a specific feature implemented in the Redmine core or the plugin overrides a specific view which requires you to control on which (specific) versions of Redmine the plugin can be installed to assure that the required core is available. Such prevents a lot of issues regarding plugin-compatibility. 

 The above can be accomplished by utilizing the @requires_redmine@-method (see issue #2162 for the implementation dicussion    and it's actual implementation in r2042). Utilisation of the method provides an easy, reliable way to create plugins that require a specific version of Redmine and which are setup to stop Redmine with a message about a non-supported version if the version-requirement is not met. 

 h2. Overriding the Redmine Core 

 You can override views but not controllers or models in Redmine. Here's how Redmine/Rails works if you try to override a controller (or model) and a view for a fictional plugin @MyPlugin@: 

 h3. Controllers (or models) 

 # Rails bootstraps and loads all it's framework 
 # Rails starts to load code in the plugins 
 # Rails finds @IssueController@ in MyPlugin and see it defines a @show@ action 
 # Rails loads all the other plugins 
 # Rails then loads the application from _../app_ 
 # Rails finds @IssueController@ again and see it also defines a @show@ action 
 # Rails (or rather Ruby) overwrites the @show@ action from the plugin with the one from _../app_ 
 # Rails finishes loading and serves up requests 

 h3. Views 

 View loading is very similar but with one small difference (because of Redmine's patch to Engines) 

 # Rails bootstraps and loads all it's framework 
 # Rails starts to load code in the plugins 
 # Rails finds a views directory in _../vendor/plugins/my_plugin/app/views_ and *pre-pends* it to the views path 
 # Rails loads all the other plugins 
 # Rails then loads the application from _../app_ 
 # Rails finishes loading and serves up requests 
 # Request comes in, and a view needs to be rendered 
 # Rails looks for a matching template and loads the plugin's template since it was *pre-pended* to the views path 
 # Rails renders the plugins'view 

 Due to the fact that it is so easy to extend models and controllers the Ruby way (via including modules), Redmine shouldn't (and doesn't) maintain an API for overriding the core's models and/or controllers. Views on the other hand are tricky (because of Rails magic) so an API for overriding them is way more useful (and thus implemented in Redmine). 

 To override an existing Redmine Core view just create a view file named exactly after the one in _../app/views/_ and Redmine will use it. For example to override the project index page add a file to _../vendor/plugins/my_plugin/app/views/projects/index.rhtml_. 

 h2. Extending the Redmine Core 

 As explained above: you rarely want to override a model/controller. Instead you should either: 
 * add new methods to a model/controller or  
 * wrap an existing method. 

 h3. Adding a new method 

 A quick example of *adding a new method* can be found on Eric Davis' "Budget plugin":http://github.com/edavis10/redmine-budget-plugin/blob/5076b1c88b57c2068aa92cdf694769dbd22d061a/lib/issue_patch.rb. Here he added a new method to Issue called @deliverable_subject@ and also declared a relationship. 

 h3. Wrapping an existing method 

 A quick example of *wrapping an existing method* can be found on Eric Davis' "Rate plugin":http://github.com/edavis10/redmine_rate/blob/4666ddb10e1061ca3ef362735d0d264676b99024/lib/rate_users_helper_patch.rb. Here he uses the @alias_method_chain@ to hook into the UsersHelper and wrap the @user_settings_tabs@ method. So when the Redmine Core calls @user_settings_tabs@ the codepath looks like: 

 # Redmine Core calls @UsersHelper#user_settings_tabs@  
 # @UsersHelper#user_settings_tabs@ runs (which is actually @UsersHelper#user_settings_tabs_with_rate_tab@) 
 # @UsersHelper#user_settings_tabs_with_rate_tab@ calls the original @UsersHelper#user_settings_tabs@ (renamed to @UsersHelper#user_settings_tabs_without_rate_tab@) 
 # The result then has a new Hash added to it 
 # @UsersHelper#user_settings_tabs_with_rate_tab@ returns the combined result to the Redmine core, which is then rendered 

 "@alias_method_chain@":http://api.rubyonrails.org/classes/ActiveSupport/CoreExtensions/Module.html#M001188 is a pretty advanced method but it's also really powerful. 

 h2. References 

 * http://www.redmine.org/boards/3/topics/show/5121 (Which version of Redmine I need to use your plugin?) 
 * http://www.redmine.org/boards/3/topics/show/4283 (Can a plugin modify the view of the projects page?) 
 * http://www.redmine.org/boards/3/topics/show/4095 (Rails Engines and extending the issue model) 

 h2. To Do 

 This is an abstract of an IRC-discussion between @chantra__@ and @edavis10@ about using hooks and call-backs within a plugin. This needs to be rewritten obviously. 

 <pre> 
 [16:06:48] <chantra__> hi all, I am playing with hooks.... 
 [16:06:58] <chantra__> is there a way to detect a new logged issue? 
 [16:07:20] <chantra__> the only hooks I find are: http://pastebin.com/m779d73b0 
 [16:08:11] <chantra__> and :controller_issues_edit_before_save is not triggered when a new issue is logged 
 [16:09:21] <chantra__> any ideas? 
 ... 
 [17:11:45] <+edavis10> chantra__: http://www.redmine.org/wiki/redmine/Hooks 
 [17:12:02] <chantra__> edavis10: cheers 
 [17:12:10] <chantra__> I found out that there is no hook for new issues 
 [17:12:16] <+edavis10> chantra__: if you want to hook into when every issue is saved/created, I'd recommend using Rails callbacks 
 [17:12:19] <chantra__> and no way to override issuescontroller 
 [17:12:51] <chantra__> so far, only trick is to patch isses_controller.rb and add a hook call when issue is saved Ls 
 [17:13:26] <+edavis10> chantra__: http://github.com/edavis10/redmine_kanban/blob/000cf175795c18033caa43082c4e4d0a9f989623/init.rb#L10 
 [17:13:41] <+edavis10> chantra__: http://github.com/edavis10/redmine_kanban/blob/000cf175795c18033caa43082c4e4d0a9f989623/lib/redmine_kanban/issue_patch.rb 
 [17:13:52] <+edavis10> Line 13 of the second url 
 [17:14:16] <+edavis10> chantra__: that will run `issue.update_kanban_from_issue` every time an issue is saved (new or updated) 
 [17:14:19] <chantra__> edavis10: thanks a bunch, I will dig this out 
 [17:14:40] <+edavis10> chantra__: here's the Rails documentation on callbacks http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html 
 [17:14:42] <chantra__> then I can compared created_on and updated_on to see if it is new ;) 
 [17:15:02] <chantra__> splendid 
 ... 
 [17:15:07] <+edavis10> chantra__: or you can use the `before_create` callback.    It's only run on new records 
 [17:15:20] <chantra__> \o/ :) 
 [17:15:32] <+edavis10> (or after_create if you want to make sure the record was saved) 
 [17:15:52] <chantra__> thanks, you are saving me loads of time here :) 
 [17:16:05] <chantra__> coz I was heading the wrong way :p 
 [17:16:21] <+edavis10> you're welcome. My Github account as all of my code (~20+ Redmine plugins) 
 [17:16:39] <+edavis10> the kanban plugin is my latest, so it's the best code at the moment 
 [17:17:04] <chantra__> yeah, I checked a lot of them already... but being a ruby noob, it took me time to assimilate 
 ... 
 [17:17:36] <+edavis10> chantra__: if they have a lib/ directory, that is where I put my hooks or where I patch Redmine's behavior 
 ... 
 [17:18:29] <chantra__> yeah, found that out, but I was hitting a wall on hooking new issue, so I was going to patch the core redmine.... which is not really nifty when doing a plugin :) 
 [17:19:29] <+edavis10> chantra__: some plugins have overrode the IssuesController from a plugin.    It will work but the second the core code changes, the plugin will break 
 ... 
 [17:56:45] <chantra__> edavis10: yet again thanks a bunch, I start to get my stuff working without patching around 
 [17:56:47] <chantra__> gtg 
 </pre>