Plugin Tutorial » History » Version 8

Jean-Philippe Lang, 2008-08-10 18:45

1 3 Jean-Philippe Lang
{{>toc}}
2 3 Jean-Philippe Lang
3 1 Jean-Philippe Lang
h1. Plugin Tutorial
4 1 Jean-Philippe Lang
5 1 Jean-Philippe Lang
h2. Creating a new Plugin
6 1 Jean-Philippe Lang
7 1 Jean-Philippe Lang
Open up a command prompt and "cd" to your redmine directory, then execute the following command:
8 1 Jean-Philippe Lang
9 3 Jean-Philippe Lang
  % ruby script/generate redmine_plugin Pools
10 1 Jean-Philippe Lang
11 1 Jean-Philippe Lang
The plugin structure is created in @vendor/plugins/redmine_pools@:
12 1 Jean-Philippe Lang
13 1 Jean-Philippe Lang
<pre>
14 1 Jean-Philippe Lang
      create  vendor/plugins/redmine_pools/app/controllers
15 1 Jean-Philippe Lang
      create  vendor/plugins/redmine_pools/app/helpers
16 1 Jean-Philippe Lang
      create  vendor/plugins/redmine_pools/app/models
17 1 Jean-Philippe Lang
      create  vendor/plugins/redmine_pools/app/views
18 1 Jean-Philippe Lang
      create  vendor/plugins/redmine_pools/db/migrate
19 1 Jean-Philippe Lang
      create  vendor/plugins/redmine_pools/lib/tasks
20 1 Jean-Philippe Lang
      create  vendor/plugins/redmine_pools/assets/images
21 1 Jean-Philippe Lang
      create  vendor/plugins/redmine_pools/assets/javascripts
22 1 Jean-Philippe Lang
      create  vendor/plugins/redmine_pools/assets/stylesheets
23 1 Jean-Philippe Lang
      create  vendor/plugins/redmine_pools/lang
24 1 Jean-Philippe Lang
      create  vendor/plugins/redmine_pools/README
25 1 Jean-Philippe Lang
      create  vendor/plugins/redmine_pools/init.rb
26 1 Jean-Philippe Lang
      create  vendor/plugins/redmine_pools/lang/en.yml
27 1 Jean-Philippe Lang
</pre>
28 1 Jean-Philippe Lang
29 8 Jean-Philippe Lang
Edit @vendor/plugins/redmine_pools/init.rb@ to adjust plugin information (name, author, description and version):
30 1 Jean-Philippe Lang
31 1 Jean-Philippe Lang
<pre><code class="ruby">
32 1 Jean-Philippe Lang
require 'redmine'
33 1 Jean-Philippe Lang
34 1 Jean-Philippe Lang
Redmine::Plugin.register :redmine_pools do
35 1 Jean-Philippe Lang
  name 'Pools plugin'
36 1 Jean-Philippe Lang
  author 'John Smith'
37 1 Jean-Philippe Lang
  description 'A plugin for managing pools'
38 1 Jean-Philippe Lang
  version '0.0.1'
39 1 Jean-Philippe Lang
end
40 1 Jean-Philippe Lang
</code></pre>
41 1 Jean-Philippe Lang
42 2 Jean-Philippe Lang
Then restart the application and point your browser to http://localhost:3000/admin/info.
43 1 Jean-Philippe Lang
After logging in, you should see your new plugin in the plugins list:
44 2 Jean-Philippe Lang
45 4 Jean-Philippe Lang
p=. !plugins_list1.png!
46 2 Jean-Philippe Lang
47 1 Jean-Philippe Lang
h2. Generating a controller
48 1 Jean-Philippe Lang
49 3 Jean-Philippe Lang
For now, the plugin doesn't do anything. So let's create a controller for our plugin.
50 3 Jean-Philippe Lang
Go back to the command prompt and run:
51 3 Jean-Philippe Lang
52 3 Jean-Philippe Lang
<pre>
53 3 Jean-Philippe Lang
% ruby script/generate redmine_plugin_controller Pools pools index vote
54 3 Jean-Philippe Lang
      exists  app/controllers/
55 3 Jean-Philippe Lang
      exists  app/helpers/
56 3 Jean-Philippe Lang
      create  app/views/pools
57 3 Jean-Philippe Lang
      create  test/functional/
58 3 Jean-Philippe Lang
      create  app/controllers/pools_controller.rb
59 3 Jean-Philippe Lang
      create  test/functional/pools_controller_test.rb
60 3 Jean-Philippe Lang
      create  app/helpers/pools_helper.rb
61 3 Jean-Philippe Lang
      create  app/views/pools/index.html.erb
62 3 Jean-Philippe Lang
      create  app/views/pools/vote.html.erb
63 3 Jean-Philippe Lang
</pre>
64 3 Jean-Philippe Lang
65 3 Jean-Philippe Lang
A controller @PoolsController@ with 2 actions (@#index@ and @#vote@) is created.
66 3 Jean-Philippe Lang
67 5 Jean-Philippe Lang
Edit @app/controllers/pools_controller.rb@ in @redmine_pools@ directory to implement these 2 actions.
68 3 Jean-Philippe Lang
69 3 Jean-Philippe Lang
<pre><code class="ruby">
70 3 Jean-Philippe Lang
class PoolsController < ApplicationController
71 3 Jean-Philippe Lang
  unloadable
72 3 Jean-Philippe Lang
  
73 3 Jean-Philippe Lang
  @@pools = [ {:id => 1, :title => 'First pool', :question => 'This is the first pool question', :yes => 0, :no => 0},
74 3 Jean-Philippe Lang
              {:id => 2, :title => 'Second pool', :question => 'This is the second pool question', :yes => 0, :no => 0} ]
75 3 Jean-Philippe Lang
76 3 Jean-Philippe Lang
  def index
77 7 Jean-Philippe Lang
    @pools = @@pools # Pool.find(:all)
78 3 Jean-Philippe Lang
  end
79 3 Jean-Philippe Lang
80 3 Jean-Philippe Lang
  def vote
81 7 Jean-Philippe Lang
    pool = @@pools.find {|p| p[:id].to_s == params[:id]} # Pool.find(params[:id])
82 3 Jean-Philippe Lang
    # saves the vote
83 3 Jean-Philippe Lang
    pool[params[:answer].to_sym] += 1
84 3 Jean-Philippe Lang
    flash[:notice] = 'Vote saved.'
85 3 Jean-Philippe Lang
    redirect_to :action => 'index'
86 3 Jean-Philippe Lang
  end
87 3 Jean-Philippe Lang
end
88 1 Jean-Philippe Lang
</code></pre>
89 5 Jean-Philippe Lang
90 5 Jean-Philippe Lang
For the sake of this example, we simulate a pool model in our @@@pools@ class variable.
91 5 Jean-Philippe Lang
We could of course use a ActiveRecord model just like we do it in a regular Rails app.
92 3 Jean-Philippe Lang
93 3 Jean-Philippe Lang
Then edit @app/views/pools/index.html.erb@ that will display existing pools:
94 3 Jean-Philippe Lang
95 3 Jean-Philippe Lang
96 3 Jean-Philippe Lang
<pre>
97 3 Jean-Philippe Lang
<h2>Pools</h2>
98 3 Jean-Philippe Lang
99 3 Jean-Philippe Lang
<% @pools.each do |pool| %>
100 3 Jean-Philippe Lang
  <p>
101 3 Jean-Philippe Lang
  <%= pool[:question] %>?
102 3 Jean-Philippe Lang
  <%= link_to 'Yes', {:action => 'vote', :id => pool[:id], :answer => 'yes'}, :method => :post %> (<%= pool[:yes] %>) /
103 3 Jean-Philippe Lang
  <%= link_to 'No', {:action => 'vote', :id => pool[:id], :answer => 'no'}, :method => :post %> (<%= pool[:no] %>)
104 3 Jean-Philippe Lang
  </p>
105 3 Jean-Philippe Lang
<% end %>
106 3 Jean-Philippe Lang
</pre>
107 3 Jean-Philippe Lang
108 3 Jean-Philippe Lang
You can remove @vote.html.erb@ since no rendering is done by the corresponding action.
109 3 Jean-Philippe Lang
110 1 Jean-Philippe Lang
Now, restart the application and point your browser to http://localhost:3000/pools.
111 4 Jean-Philippe Lang
You should see the 2 pools and you should be able to vote for them:
112 4 Jean-Philippe Lang
113 4 Jean-Philippe Lang
p=. !pools1.png!
114 4 Jean-Philippe Lang
115 4 Jean-Philippe Lang
Note that pool results are reset on each request if you don't run the application in production mode, since our pool "model" is stored in a class variable in this example.
116 4 Jean-Philippe Lang
117 4 Jean-Philippe Lang
h2. Extending menus
118 4 Jean-Philippe Lang
119 4 Jean-Philippe Lang
Our controller works fine but users have to know the url to see the pools. Using the Redmine plugin API, you can extend standard menus.
120 4 Jean-Philippe Lang
So let's add a new item to the application menu.
121 4 Jean-Philippe Lang
122 4 Jean-Philippe Lang
h3. Extending the application menu
123 4 Jean-Philippe Lang
124 4 Jean-Philippe Lang
Edit @init.rb@ at the root of your plugin directory to add the following line at the end of the plugin registration block:
125 4 Jean-Philippe Lang
126 4 Jean-Philippe Lang
<pre><code class="ruby">
127 4 Jean-Philippe Lang
Redmine::Plugin.register :redmine_pools do
128 4 Jean-Philippe Lang
  [...]
129 4 Jean-Philippe Lang
  
130 4 Jean-Philippe Lang
  menu :application_menu, :pools, { :controller => 'pools', :action => 'index' }, :caption => 'Pools'
131 4 Jean-Philippe Lang
end
132 4 Jean-Philippe Lang
</code></pre>
133 4 Jean-Philippe Lang
134 4 Jean-Philippe Lang
Syntax is:
135 4 Jean-Philippe Lang
136 4 Jean-Philippe Lang
  menu(menu_name, item_name, url, options={})
137 4 Jean-Philippe Lang
138 4 Jean-Philippe Lang
There are 4 menus that you can extend:
139 4 Jean-Philippe Lang
140 4 Jean-Philippe Lang
* @:top_menu@ - the top left menu
141 4 Jean-Philippe Lang
* @:account_menu@ - the top right menu with sign in/sign out links
142 4 Jean-Philippe Lang
* @:application_menu@ - the main menu displayed when the user is not inside a project
143 4 Jean-Philippe Lang
* @:project_menu@ - the main menu displayed when the user is inside a project
144 4 Jean-Philippe Lang
145 4 Jean-Philippe Lang
Available options are:
146 4 Jean-Philippe Lang
147 4 Jean-Philippe Lang
* @:param@ - the parameter key that is used for the project id (default is @:id@)
148 4 Jean-Philippe Lang
* @:if@ - a Proc that is called before rendering the item, the item is displayed only if it returns true
149 4 Jean-Philippe Lang
* @:caption@ - the menu caption that can be:
150 4 Jean-Philippe Lang
151 4 Jean-Philippe Lang
  * a localized string Symbol
152 4 Jean-Philippe Lang
  * a String
153 4 Jean-Philippe Lang
  * a Proc that can take the project as argument
154 4 Jean-Philippe Lang
155 4 Jean-Philippe Lang
* @:before@, @:after@ - specify where the menu item should be inserted (eg. @:after => :activity@)
156 4 Jean-Philippe Lang
* @:last@ - if set to true, the item will stay at the end of the menu (eg. @:last => true@)
157 4 Jean-Philippe Lang
* @:html_options@ - a hash of html options that are passed to @link_to@ when rendering the menu item
158 4 Jean-Philippe Lang
159 4 Jean-Philippe Lang
In our example, we've added an item to the application menu which is emtpy by default.
160 4 Jean-Philippe Lang
Restart the application and go to http://localhost:3000:
161 4 Jean-Philippe Lang
162 4 Jean-Philippe Lang
p=. !application_menu.png!
163 4 Jean-Philippe Lang
164 4 Jean-Philippe Lang
Now you can access the pools by clicking the Pools tab from the welcome screen.
165 4 Jean-Philippe Lang
166 4 Jean-Philippe Lang
h3. Extending the project menu
167 4 Jean-Philippe Lang
168 6 Jean-Philippe Lang
Now, let's consider that the pools are defined at project level (even if it's not the case in our example pool model). So we would like to add the Pools tab to the project menu instead.
169 6 Jean-Philippe Lang
Open @init.rb@ and replace the line that was added just before with these 2 lines:
170 6 Jean-Philippe Lang
171 6 Jean-Philippe Lang
<pre><code class="ruby">
172 6 Jean-Philippe Lang
Redmine::Plugin.register :redmine_pools do
173 6 Jean-Philippe Lang
  [...]
174 6 Jean-Philippe Lang
175 6 Jean-Philippe Lang
  permission :pools, {:pools => [:index, :vote]}, :public => true
176 6 Jean-Philippe Lang
  menu :project_menu, :pools, { :controller => 'pools', :action => 'index' }, :caption => 'Pools', :after => :activity, :param => :project_id
177 6 Jean-Philippe Lang
end
178 6 Jean-Philippe Lang
</code></pre>
179 6 Jean-Philippe Lang
180 6 Jean-Philippe Lang
The second line adds our Pools tab to the project menu, just after the activity tab.
181 6 Jean-Philippe Lang
The first line is required and declares that our 2 actions from @PoolsController@ are public. We'll come back later to explain this with more details.
182 6 Jean-Philippe Lang
183 6 Jean-Philippe Lang
Restart the application again and go to one of your projects:
184 6 Jean-Philippe Lang
185 6 Jean-Philippe Lang
p=. !project_menu.png!
186 6 Jean-Philippe Lang
187 6 Jean-Philippe Lang
If you click the Pools tab, you should notice that the project menu is no longer displayed.
188 6 Jean-Philippe Lang
To make the project menu visible, you have to initialize the controller's instance variable @@project@.
189 6 Jean-Philippe Lang
190 6 Jean-Philippe Lang
Edit your PoolsController to do so:
191 6 Jean-Philippe Lang
192 6 Jean-Philippe Lang
<pre><code class="ruby">
193 6 Jean-Philippe Lang
def index
194 6 Jean-Philippe Lang
  @project = Project.find(params[:project_id])
195 7 Jean-Philippe Lang
  @pools = @@pools # @project.pools
196 6 Jean-Philippe Lang
end
197 6 Jean-Philippe Lang
</code></pre>
198 6 Jean-Philippe Lang
199 6 Jean-Philippe Lang
The project id is available in the @:project_id@ param because of the @:param => :project_id@ option in the menu item declaration above.
200 6 Jean-Philippe Lang
201 6 Jean-Philippe Lang
Now, you should see the project menu when viewing the pools:
202 6 Jean-Philippe Lang
203 6 Jean-Philippe Lang
p=. !project_menu_pools.png!
204 4 Jean-Philippe Lang
205 4 Jean-Philippe Lang
h2. Adding new permissions
206 4 Jean-Philippe Lang
207 4 Jean-Philippe Lang
TODO
208 4 Jean-Philippe Lang
209 4 Jean-Philippe Lang
h2. Creating a project module
210 4 Jean-Philippe Lang
211 4 Jean-Philippe Lang
TODO