Project

General

Profile

Hooks » History » Version 4

Holger Just, 2010-05-16 23:03

1 1 Holger Just
h1. HowTo Use Hooks
2
3 2 Holger Just
{{>toc}}
4 1 Holger Just
5
Redmine supports the concept of Hooks. It is an API to allow external code to extend the core Redmine functionality in a clean way. Hooks allow the plugin author to register callback functions which are executed one after another when the Redmine code reaches specific points in the code.
6
7 2 Holger Just
There is a list of [[Hooks|valid hooks]]. But the best way to find them is to just have a look into the code to find the place you would like to extend and search for a call to a hook nearby.
8
9 1 Holger Just
Additional methods to extend or replace Redmine code are:
10 2 Holger Just
* [[Plugin_Internals#Adding-a-new-method|Adding new methods]]
11 1 Holger Just
* [[Plugin_Internals#Using-Rails-callbacks-in-Redmine-plugins|Using rails callbacks]]
12
* [[Plugin_Internals#Wrapping-an-existing-method|Wrapping an existing method]] using @alias_method_chain@
13
14 2 Holger Just
h2. Fundamentals
15
16
As said above, when a hook is called, it executes the previously registered callback functions. These functions must accept exactly one parameter: a hash providing some context. This context hash will always contain data necessary to do something useful in your callback function. In the case of controller of view hooks (see below), it contains at least the following information:
17
* :controller => a reference to the current controller instance
18
* :project => the current project (if set by the controller),
19
* :request => the current "request object":http://api.rubyonrails.org/classes/ActionController/Request.html with much information about the current web request
20
21
Additionally, the hash will contain some data specific to the respective hook. This data is directly passed in the @call_hook@ call you will find in Redmine's code.
22
23
Model hooks will not contain the default data as it does not apply here. These hooks will only contain the data passed in the @call_hook@ call.
24
25
h2. Types of hooks
26
27
Basically, there are currently two types of hooks:
28
* View hooks
29
* Controller hooks
30
* Model hooks
31
32
While both types use exactly the same API, there is a fundamentally different use-case for these.
33
34
h3. View hooks
35
36
View hooks are executed while rendering the HTML code of a view. This allows the plugin author to insert some custom HTML code into some sensible places of the view. The return value of the callback function transformed to a string and included into the view. There exists a shortcut for rendering a single partial. See below for an example.
37
38
h3. Controller hooks
39
40
Controller hooks are fewer in number than the view hooks. Often it is sufficient to use "additional filters":http://apidock.com/rails/ActionController/Filters/ClassMethods or to extend the model classes, as the controller actions should (and are most of the time) be very short and don't do much. There are however, some more lengthy action which use hooks. To properly use those, one has to understand, that the objects in the context hash are only referenced. This, if you change a object in-place, the changes will be available in the actual controller (and later in the view). Consider the following simplified example:
41
42
Assume the following function registered to the @do_something@ hook. See below for how to achieve that.
43
44
<pre><code class="ruby">
45
def do_something(context={ })
46
  context[:issue].subject = "Nothing to fix"
47
end
48
</code></pre>
49
50
Now consider a controller action with the following code:
51
52
<pre><code class="ruby">
53
issue = Issue.find(1)
54
# issue.subject is "Fix me"
55
call_hook(:do_something, :issue => issue)
56
# issue.subject is now "Nothing to fix"
57
</code></pre>
58
59
As you can see, the hook function can change the @issue@ object in-place. It is however not possible to completely replace an object as this would break the object references.
60
61
h3. Model hooks
62
63
There are very few model hooks in Redmine. Most extensions in model code can be done by adding new methods or encapsulating existing ones by creatively applying the [[Plugin_Internals#Wrapping-an-existing-method|alias_method_chain]] pattern. The hooks can be used in the same way as the controller hooks.
64
65 1 Holger Just
h2. Register functions to hooks
66 2 Holger Just
67
h3. View hooks
68
69
The following example is going to hook a function into the hook @view_issues_form_details_bottom@. This can be used to add some additional fields to the issue edit form.
70
71
# In your plugin (assumed to be named @my_plugin@), create the following class in @lib/my_plugin/hooks.rb@. You can register to multiple hooks in the same class.
72
<pre><code class="ruby">
73
module MyPlugin
74
  class Hooks < Redmine::Hook::ViewListener
75 3 Holger Just
    # This just renders the partial in
76
    # app/views/hooks/my_plugin/_view_issues_form_details_bottom.rhtml
77 2 Holger Just
    # The contents of the context hash is made available as local variables to the partial.
78
    #
79
    # Additional context fields
80
    #   :issue  => the issue this is edited
81
    #   :f      => the form object to create additional fields
82
    render_on :view_issues_form_details_bottom,
83
              :partial => 'hooks/my_plugin/view_issues_form_details_bottom'
84
  end
85
end
86
</code></pre> The following class does exactly the same as the above but uses a method instead of the shorter @render_on@ helper. The name of the method decides which callback it registers itself to.
87
<pre><code class="ruby">
88
module MyPlugin
89
  class Hooks < Redmine::Hook::ViewListener
90
    def view_issues_form_details_bottom(context={ })
91
      # the controller parameter is part of the current params object
92
      # This will render the partial into a string and return it.
93
      context[:controller].send(:render_to_string, {
94
        :partial => "hooks/my_plugin/view_issues_form_details_bottom"
95
        :locals => context
96
      })
97
      
98
      # Instead of the above statement, you could return any string generated
99
      # by your code. That string will be included into the view
100
    end
101
  end
102
end
103
</code></pre>
104
105
2. In your @init.rb@ make sure to require the file with the hooks. It should look like this:
106
<pre><code class="ruby">
107
require 'redmine'
108
109
# This is the important line.
110
# It requires the file in lib/my_plugin/hooks.rb
111 4 Holger Just
require_dependency 'my_plugin/hooks'
112 2 Holger Just
113
Redmine::Plugin.register :my_plugin do
114
  [...]
115
end
116
</code></pre>
117
118
h3. Controller and Model hooks
119
120
You can register methods to controller and model hooks the same way as the view hooks. Always remember to require the hook class in your @init.rb@. See an example below:
121
122
<pre><code class="ruby">
123
module MyPlugin
124
  class Hooks < Redmine::Hook::ViewListener
125
    def controller_issues_bulk_edit_before_save(context={ })
126
      # set my_attribute on the issue to a default value if not set explictly
127
      context[:issue].my_attribute ||= "default"
128
    end
129
  end
130
end
131
</code></pre>
132
133
h2. Additional examples
134
135
Some additional real-life examples can be found at
136
137
* http://github.com/edavis10/redmine-budget-plugin/blob/master/lib/budget_issue_hook.rb
138
139
h2. TODO
140
141
* HowTo add filters to existing controllers?
142
* HowTo overwrite methods using alias_method_chain
143
** instance methods
144
** class methods
145
** initialize
146
** modules