Writing plugins compatible with both Redmine 1.x and 2.x - some tips
Added by Vitaly Klimov about 12 years ago
- Table of contents
- Writing plugins compatible with both Redmine 1.x and 2.x
- Routing
- Overriding models, controllers and helpers
- Extending default helpers and controllers
- Miscellaneous
- Conclusion
Writing plugins compatible with both Redmine 1.x and 2.x¶
This guide is not intended to be complete and fool proof. It simply contains bits and scraps i gathered while adding Redmine 2.x (and Rails 3) compatibility to my plugins. I believe tips i give here will help create cross-version plugins.
Routing¶
Theory¶
Most simple part so far. Although routing syntax completely changed in Rails 3, it is straightforward and easy to learn. For complete guide please see http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/ link.
Code sample¶
Below is sample routing.rb file which is compatible with both Rails' versions.
if Rails::VERSION::MAJOR >= 3
RedmineApp::Application.routes.draw do
match 'projects/:project_id/boards/:board_id/manage', :to => 'boards_watchers#manage', :via => [:get, :post]
match 'projects/:project_id/boards/:board_id/manage_topic', :to => 'boards_watchers#manage_topic', :via => [:get, :post]
end
else
ActionController::Routing::Routes.draw do |map|
map.with_options :controller => 'boards_watchers' do |bw_routes|
bw_routes.with_options :conditions => {:method => :get} do |bw_views|
bw_views.connect 'projects/:project_id/boards/:board_id/manage', :action => 'manage'
bw_views.connect 'projects/:project_id/boards/:board_id/manage_topic', :action => 'manage_topic'
end
bw_routes.with_options :conditions => {:method => :post} do |bw_views|
bw_views.connect 'projects/:project_id/boards/:board_id/manage', :action => 'manage'
bw_views.connect 'projects/:project_id/boards/:board_id/manage_topic', :action => 'manage_topic'
end
end
end
end
Overriding models, controllers and helpers¶
Theory¶
Since patching mechanism is changed in Rails 3, in order to support both Redmine version in the plugin we have to check two different code branches – one should use Dispatcher functionality (Rails 2.x) and another – new to_prepare functionality from Rails 3.
Code¶
To make things simple and clear, i moved all similar parts of the code to the separate file in lib folder, and left only Rails specific code in the init.rb file. So usual inclusion code would look like following:
init.rb:
# Including dispatcher.rb in case of Rails 2.x
require 'dispatcher' unless Rails::VERSION::MAJOR >= 3
if Rails::VERSION::MAJOR >= 3
ActionDispatch::Callbacks.to_prepare do
# use require_dependency if you plan to utilize development mode
require 'boards_watchers_patches'
end
else
Dispatcher.to_prepare BW_AssetHelpers::PLUGIN_NAME do
# use require_dependency if you plan to utilize development mode
require 'boards_watchers_patches'
end
end
boards_watchers_patches.rb:
# use require_dependency if you plan to utilize development mode
require 'application_helper'
module BoardsWatchers
module Patches
module ApplicationHelperPatch
def self.included(base) # :nodoc:
# sending instance methods to module
base.send(:include, InstanceMethods)
base.class_eval do
unloadable
# aliasing methods if needed
alias_method_chain :render_page_hierarchy, :watchers
end
end
# Instance methods are here
module InstanceMethods
def render_page_hierarchy_with_watchers(pages, node=nil, options={})
# our code is here
end
end
end
end
end
# now we should include this module in ApplicationHelper module
unless ApplicationHelper.included_modules.include? BoardsWatchers::Patches::ApplicationHelperPatch
ApplicationHelper.send(:include, BoardsWatchers::Patches::ApplicationHelperPatch)
end
Views and form tags¶
This is the part of common problem, when it is not possible to patch different parts of views files and in order to add/change couple of strings in any .erb file plugin authors have to replace whole file. I filed a ticket on redmine.org (http://www.redmine.org/issues/11104) proposing possible solution to this issue. Meanwhile i would like to ask anyone who interested in cross-version compatibility to take a look at my plugin Plugin views revisions This plugin solves this issue.
Anyway, if we are about to make existing plugin with its own views compatible with Rails 3.x, there are some things that are changed in view syntax and code.
First of all, it is the form_tag and form_for methods, In Rails 2.x we had to use <% form_for > without equal sign and in Rails 3.x we need to use equal sign: <= form_for ... %>
Another thing that is related to the views is that in Rails 3.x all strings that displayed by views are escaped by default, so you have to add manually .html_safe method if you want to return actual HTML code in your helpers.
Extending default helpers and controllers¶
In Redmine versions prior to 2.0.0 all plugins were in vendor/plugins folder and because of that Rails automatically loaded and added all files inside app/controllers, app/helpers, app/models, etc folders.
This approach allowed to extend existing controllers and helpers by simply adding appropriate files and methods to the plugin' folders. For example, if we have to add method get_list_of_projects to SettingsHelper we can simply create file named settings_helper.rb in app/helpers folder of the plugin with the following content:
app/helpers/settings_helper.rb
module SettingsHelper
def get_list_of_projects
# do what we need here
end
end
Redmine 2.x moved plugins to plugins folder in application's root and because of that method described above does not work at all. So we have to use patching and override mechanisms described above to make things work.
Miscellaneous¶
Links and paths to plugin assets¶
Sometimes you need to reference some assets from plugin assets folder. If you are using Redmine 1.x it was possible to do it by using extended image_path function with extra parameter (:plugin) to get full asset path, for example:
image_path("../flash/some_flash.swf",:plugin => 'my_plugin')
Unfortunately, Redmine 2.x removed support for this function, so i wrote my own version of function which will return path to the assets. Please put file with this function into any subfolder of the plugin folder, otherwise plugin name retrieval would not work
module MyPluginAssetHelpers
PLUGIN_NAME = File.expand_path('../../*', __FILE__).match(/.*\/(.*)\/\*$/)[1].to_sym
def self.plugin_asset_link(asset_name,options={})
plugin_name=(options[:plugin] ? options[:plugin] : PLUGIN_NAME)
File.join(Redmine::Utils.relative_url_root,'plugin_assets',plugin_name.to_s,asset_name)
end
end
Relative url root¶
If you installed Redmine to the sub-URI you should set Redmine::Utils.relative_url_root to the
value of sub-URI in config/additional_environment.rb:
Redmine::Utils::relative_url_root = "/redmine"
Unfortunately this method does not work for Redmine 2.x and you should set ActionController root directly:
config.action_controller.relative_url_root = '/redmine'
Conclusion¶
This is all so far. Please do comment and correct this post if you would like to.
I think it would be great if something based on this post will be added to the plugins related wiki pages
Replies (10)
RE: Writing plugins compatible with both Redmine 1.x and 2.x - some tips
-
Added by William Roush about 12 years ago
This should be part of official docs for plugin development, extremely useful!
Thanks for the write up. :)
RE: Writing plugins compatible with both Redmine 1.x and 2.x - some tips
-
Added by Mischa The Evil about 12 years ago
Stickied and relocated toc
to the right.
Reference: #11151.
Thanks for writing this useful information.
RE: Writing plugins compatible with both Redmine 1.x and 2.x - some tips
-
Added by Jim McAleer about 12 years ago
Great job very, very useful!!! Thanks!!!!
RE: Writing plugins compatible with both Redmine 1.x and 2.x - some tips
-
Added by Jiří Křivánek about 12 years ago
Thank You for this great guide. It helped me a lot when porting my plugin from 1.3 to 2.0. But I still have one big problem:
There is NO redmine around my plugin view!
No HTML headers, no styles, no menu...
Just a content of my plugin - which works!
Any idea which little detail of too complex Ruby/Rails/Redmine conglomerat I am missing?
RE: Writing plugins compatible with both Redmine 1.x and 2.x - some tips
-
Added by Jiří Křivánek about 12 years ago
In a different thread someone adviced me: The problem was in the constructor of my controller class.
At the old Redmine, there was no need to call the ancestors constructor from within the overriden one.
I know, most of plugins do not do this but I stuck on it many hours and it should have been written in the porting guide.
Please add it!
RE: Writing plugins compatible with both Redmine 1.x and 2.x - some tips
-
Added by Eric Boudaillier almost 12 years ago
Hi,
I am migrating from redmine 1.4 to redmine 2.1.
My redmine sites are in sub URIs. I have never done specific configuration in 1.4, but now, the redmine graphs plugin (the one ported for 2.x - https://github.com/dmp/redmine-graphs-plugin) shows incorrect links.
I have tried to add in additonnal_environement.rb:
config.action_controller.relative_url_root = '/redmine'
but without success. And adding the following line raises an error:
Redmine::Utils::relative_url_root = "/redmine"
Has someone solved this issue?
RE: Writing plugins compatible with both Redmine 1.x and 2.x - some tips
-
Added by Andriy Lesyuk almost 12 years ago
RE: Writing plugins compatible with both Redmine 1.x and 2.x - some tips
-
Added by Marco Nobler almost 12 years ago
First of all, thanks for this, it's very useful.
I'm trying to port a plugin built for 1.3 redmine (rails 2) to redmine 2.1.2.
I have an error running migration (see "redmine_tlcit_migration.out" file attached):
undefined method `validate' for class `Issue'
attached you can also find the "issue_patch.rb".
Could you give me some hint to solve this?
thanks in advance
Marco Nober
redmine_tlcit_migration.out (6.41 KB) redmine_tlcit_migration.out | the migrate ouput | ||
issue_patch.rb (1.35 KB) issue_patch.rb | the source |
RE: Writing plugins compatible with both Redmine 1.x and 2.x - some tips
-
Added by Marco Nobler almost 12 years ago
Hi,
i'm triyng to port a redmine 1.x plugin to redmine 2.1.2.
I'm facing with a problem with patching modules:
I need to patch application helper and some other modules but probably i'm making some mistakes, attached can find the code:
plugins/redmine_tlcit/init.rb
plugins/redmine_tlcit/lib/redmine_tlcit/patches/application_helper_patch.rb
but i have this run-time error, it seems have not loaded the patch definitions:
ActionView::Template::Error (undefined method `prompt_to_remote' for #<#<Class:0x7f7b8d368610>:0x7f7b8d0d5498>):
13: <% end >
14: < unless @project.issue_categories.empty? >
15: <p><= f.select :category_id, (@project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true >
16: <= prompt_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'),
17: l(:label_issue_category_new),
18: 'category[name]',
19: {:controller => 'issue_categories', :action => 'new', :project_id => Herve Harster},
app/views/issues/_form.html.erb:41:in `_app_views_issues__form_html_erb___1076854482_70084312332980'
app/helpers/application_helper.rb:985:in `labelled_fields_for'
app/views/issues/_form.html.erb:1:in `_app_views_issues__form_html_erb___1076854482_70084312332980'
app/views/issues/_edit.html.erb:8:in `_app_views_issues__edit_html_erb__1983334900_70084312901780'
app/helpers/application_helper.rb:978:in `labelled_form_for'
app/views/issues/_edit.html.erb:1:in `_app_views_issues__edit_html_erb__1983334900_70084312901780'
app/controllers/issues_controller.rb:117:in `show'
app/controllers/issues_controller.rb:114:in `show'
please, can you all help me?
Thanks in advance
init.rb (1.7 KB) init.rb | |||
application_helper_patch.rb (688 Bytes) application_helper_patch.rb |
RE: Writing plugins compatible with both Redmine 1.x and 2.x - some tips
-
Added by Arun M V about 11 years ago
Hi,
Currently I have Redmine 2.3.0 installed and I am trying to make the Rate Plugin compatible with it.But I am facing some issues.Please find the below error log.
ActionView::Template::Error (undefined method `project_options_for_select_with_selected' for #<#<Class:0xb5adc540>:0xb587f748>):
15: <%= # TODO: move to controller once a hook is in place for the Admin panel
16: projects = Project.find(:all, :conditions => { :status => Project::STATUS_ACTIVE})
17:
18: select_tag("rate[project_id]", project_options_for_select_with_selected(projects, @rate.project))
19: %>
20: </td>
21: <td align="right">
app/views/common/_tabs.html.erb:24:in `_app_views_common__tabs_html_erb__225850422__623011288'
app/views/common/_tabs.html.erb:23:in `each'
app/views/common/_tabs.html.erb:23:in `_app_views_common__tabs_html_erb__225850422__623011288'
app/helpers/application_helper.rb:271:in `render_tabs'
app/views/users/edit.html.erb:9:in `_app_views_users_edit_html_erb___547050721__623564338'
Please find the attached files init.rb and rate_users_helper_patch.rb.
Please help.
Thanks,
Arun
init.rb (1.61 KB) init.rb | |||
rate_users_helper_patch.rb (1.53 KB) rate_users_helper_patch.rb |