Hooks » History » Version 4

Holger Just, 2010-05-16 23:03

1 1 Holger Just
h1. HowTo Use Hooks
2 1 Holger Just
3 2 Holger Just
{{>toc}}
4 1 Holger Just
5 1 Holger Just
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 1 Holger Just
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 2 Holger Just
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 1 Holger Just
* [[Plugin_Internals#Wrapping-an-existing-method|Wrapping an existing method]] using @alias_method_chain@
13 1 Holger Just
14 2 Holger Just
h2. Fundamentals
15 2 Holger Just
16 2 Holger Just
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 2 Holger Just
* :controller => a reference to the current controller instance
18 2 Holger Just
* :project => the current project (if set by the controller),
19 2 Holger Just
* :request => the current "request object":http://api.rubyonrails.org/classes/ActionController/Request.html with much information about the current web request
20 2 Holger Just
21 2 Holger Just
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 2 Holger Just
23 2 Holger Just
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 2 Holger Just
25 2 Holger Just
h2. Types of hooks
26 2 Holger Just
27 2 Holger Just
Basically, there are currently two types of hooks:
28 2 Holger Just
* View hooks
29 2 Holger Just
* Controller hooks
30 2 Holger Just
* Model hooks
31 2 Holger Just
32 2 Holger Just
While both types use exactly the same API, there is a fundamentally different use-case for these.
33 2 Holger Just
34 2 Holger Just
h3. View hooks
35 2 Holger Just
36 2 Holger Just
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 2 Holger Just
38 2 Holger Just
h3. Controller hooks
39 2 Holger Just
40 2 Holger Just
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 2 Holger Just
42 2 Holger Just
Assume the following function registered to the @do_something@ hook. See below for how to achieve that.
43 2 Holger Just
44 2 Holger Just
<pre><code class="ruby">
45 2 Holger Just
def do_something(context={ })
46 2 Holger Just
  context[:issue].subject = "Nothing to fix"
47 2 Holger Just
end
48 2 Holger Just
</code></pre>
49 2 Holger Just
50 2 Holger Just
Now consider a controller action with the following code:
51 2 Holger Just
52 2 Holger Just
<pre><code class="ruby">
53 2 Holger Just
issue = Issue.find(1)
54 2 Holger Just
# issue.subject is "Fix me"
55 2 Holger Just
call_hook(:do_something, :issue => issue)
56 2 Holger Just
# issue.subject is now "Nothing to fix"
57 2 Holger Just
</code></pre>
58 2 Holger Just
59 2 Holger Just
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 2 Holger Just
61 2 Holger Just
h3. Model hooks
62 2 Holger Just
63 2 Holger Just
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 2 Holger Just
65 1 Holger Just
h2. Register functions to hooks
66 2 Holger Just
67 2 Holger Just
h3. View hooks
68 2 Holger Just
69 2 Holger Just
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 2 Holger Just
71 2 Holger Just
# 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 2 Holger Just
<pre><code class="ruby">
73 2 Holger Just
module MyPlugin
74 2 Holger Just
  class Hooks < Redmine::Hook::ViewListener
75 3 Holger Just
    # This just renders the partial in
76 3 Holger Just
    # 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 2 Holger Just
    #
79 2 Holger Just
    # Additional context fields
80 2 Holger Just
    #   :issue  => the issue this is edited
81 2 Holger Just
    #   :f      => the form object to create additional fields
82 2 Holger Just
    render_on :view_issues_form_details_bottom,
83 2 Holger Just
              :partial => 'hooks/my_plugin/view_issues_form_details_bottom'
84 2 Holger Just
  end
85 2 Holger Just
end
86 2 Holger Just
</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 2 Holger Just
<pre><code class="ruby">
88 2 Holger Just
module MyPlugin
89 2 Holger Just
  class Hooks < Redmine::Hook::ViewListener
90 2 Holger Just
    def view_issues_form_details_bottom(context={ })
91 2 Holger Just
      # the controller parameter is part of the current params object
92 2 Holger Just
      # This will render the partial into a string and return it.
93 2 Holger Just
      context[:controller].send(:render_to_string, {
94 2 Holger Just
        :partial => "hooks/my_plugin/view_issues_form_details_bottom"
95 2 Holger Just
        :locals => context
96 2 Holger Just
      })
97 2 Holger Just
      
98 2 Holger Just
      # Instead of the above statement, you could return any string generated
99 2 Holger Just
      # by your code. That string will be included into the view
100 2 Holger Just
    end
101 2 Holger Just
  end
102 2 Holger Just
end
103 2 Holger Just
</code></pre>
104 2 Holger Just
105 2 Holger Just
2. In your @init.rb@ make sure to require the file with the hooks. It should look like this:
106 2 Holger Just
<pre><code class="ruby">
107 2 Holger Just
require 'redmine'
108 2 Holger Just
109 2 Holger Just
# This is the important line.
110 2 Holger Just
# It requires the file in lib/my_plugin/hooks.rb
111 4 Holger Just
require_dependency 'my_plugin/hooks'
112 2 Holger Just
113 2 Holger Just
Redmine::Plugin.register :my_plugin do
114 2 Holger Just
  [...]
115 2 Holger Just
end
116 2 Holger Just
</code></pre>
117 2 Holger Just
118 2 Holger Just
h3. Controller and Model hooks
119 2 Holger Just
120 2 Holger Just
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 2 Holger Just
122 2 Holger Just
<pre><code class="ruby">
123 2 Holger Just
module MyPlugin
124 2 Holger Just
  class Hooks < Redmine::Hook::ViewListener
125 2 Holger Just
    def controller_issues_bulk_edit_before_save(context={ })
126 2 Holger Just
      # set my_attribute on the issue to a default value if not set explictly
127 2 Holger Just
      context[:issue].my_attribute ||= "default"
128 2 Holger Just
    end
129 2 Holger Just
  end
130 2 Holger Just
end
131 2 Holger Just
</code></pre>
132 2 Holger Just
133 2 Holger Just
h2. Additional examples
134 2 Holger Just
135 2 Holger Just
Some additional real-life examples can be found at
136 2 Holger Just
137 2 Holger Just
* http://github.com/edavis10/redmine-budget-plugin/blob/master/lib/budget_issue_hook.rb
138 2 Holger Just
139 2 Holger Just
h2. TODO
140 2 Holger Just
141 2 Holger Just
* HowTo add filters to existing controllers?
142 2 Holger Just
* HowTo overwrite methods using alias_method_chain
143 2 Holger Just
** instance methods
144 2 Holger Just
** class methods
145 2 Holger Just
** initialize
146 2 Holger Just
** modules