Plugin Tutorial » History » Version 66

Etienne Massip, 2012-05-25 11:13

1 1 Jean-Philippe Lang
h1. Plugin Tutorial
2 12 Jean-Philippe Lang
3 20 Jean-Philippe Lang
Note: To follow this tutorial, you need to run Redmine devel r1786 or higher.
4 20 Jean-Philippe Lang
5 30 Vinod Singh
{{>toc}}
6 1 Jean-Philippe Lang
7 1 Jean-Philippe Lang
h2. Creating a new Plugin
8 40 Nick Peelman
 
9 40 Nick Peelman
You may need to set the RAILS_ENV variable in order to use the command below:
10 32 Jiří Křivánek
11 32 Jiří Křivánek
<pre>
12 32 Jiří Křivánek
$ export RAILS_ENV="production"
13 32 Jiří Křivánek
</pre>
14 32 Jiří Křivánek
15 59 Harry Garrood
On windows:
16 59 Harry Garrood
17 59 Harry Garrood
<pre>
18 66 Etienne Massip
set RAILS_ENV=production
19 59 Harry Garrood
</pre>
20 59 Harry Garrood
21 9 Jean-Philippe Lang
Creating a new plugin can be done using the Redmine plugin generator.
22 9 Jean-Philippe Lang
Syntax for this generator is:
23 1 Jean-Philippe Lang
24 23 Jean-Baptiste Barth
<pre>ruby script/generate redmine_plugin <plugin_name></pre>
25 9 Jean-Philippe Lang
26 9 Jean-Philippe Lang
So open up a command prompt and "cd" to your redmine directory, then execute the following command:
27 9 Jean-Philippe Lang
28 18 Jean-Philippe Lang
  % ruby script/generate redmine_plugin Polls
29 1 Jean-Philippe Lang
30 18 Jean-Philippe Lang
The plugin structure is created in @vendor/plugins/redmine_polls@:
31 1 Jean-Philippe Lang
32 1 Jean-Philippe Lang
<pre>
33 18 Jean-Philippe Lang
      create  vendor/plugins/redmine_polls/app/controllers
34 18 Jean-Philippe Lang
      create  vendor/plugins/redmine_polls/app/helpers
35 18 Jean-Philippe Lang
      create  vendor/plugins/redmine_polls/app/models
36 18 Jean-Philippe Lang
      create  vendor/plugins/redmine_polls/app/views
37 18 Jean-Philippe Lang
      create  vendor/plugins/redmine_polls/db/migrate
38 18 Jean-Philippe Lang
      create  vendor/plugins/redmine_polls/lib/tasks
39 18 Jean-Philippe Lang
      create  vendor/plugins/redmine_polls/assets/images
40 18 Jean-Philippe Lang
      create  vendor/plugins/redmine_polls/assets/javascripts
41 18 Jean-Philippe Lang
      create  vendor/plugins/redmine_polls/assets/stylesheets
42 18 Jean-Philippe Lang
      create  vendor/plugins/redmine_polls/lang
43 65 Toshi MARUYAMA
      create  vendor/plugins/redmine_polls/config/locales
44 65 Toshi MARUYAMA
      create  vendor/plugins/redmine_polls/test
45 65 Toshi MARUYAMA
      create  vendor/plugins/redmine_polls/README.rdoc
46 1 Jean-Philippe Lang
      create  vendor/plugins/redmine_polls/init.rb
47 65 Toshi MARUYAMA
      create  vendor/plugins/redmine_polls/config/locales/en.yml
48 65 Toshi MARUYAMA
      create  vendor/plugins/redmine_polls/test/test_helper.rb
49 1 Jean-Philippe Lang
</pre>
50 1 Jean-Philippe Lang
51 18 Jean-Philippe Lang
Edit @vendor/plugins/redmine_polls/init.rb@ to adjust plugin information (name, author, description and version):
52 1 Jean-Philippe Lang
53 1 Jean-Philippe Lang
<pre><code class="ruby">
54 1 Jean-Philippe Lang
require 'redmine'
55 1 Jean-Philippe Lang
56 18 Jean-Philippe Lang
Redmine::Plugin.register :redmine_polls do
57 18 Jean-Philippe Lang
  name 'Polls plugin'
58 1 Jean-Philippe Lang
  author 'John Smith'
59 18 Jean-Philippe Lang
  description 'A plugin for managing polls'
60 1 Jean-Philippe Lang
  version '0.0.1'
61 1 Jean-Philippe Lang
end
62 1 Jean-Philippe Lang
</code></pre>
63 1 Jean-Philippe Lang
64 27 Eduardo Yáñez Parareda
Then restart the application and point your browser to http://localhost:3000/admin/plugins.
65 1 Jean-Philippe Lang
After logging in, you should see your new plugin in the plugins list:
66 4 Jean-Philippe Lang
67 29 Vinod Singh
!plugins_list1.png!
68 1 Jean-Philippe Lang
69 13 Jean-Philippe Lang
h2. Generating a model
70 13 Jean-Philippe Lang
71 44 John Yani
For now plugin doesn't store anything. Let's create a simple Poll model for our plugin. Syntax is:
72 1 Jean-Philippe Lang
73 44 John Yani
<pre>
74 44 John Yani
   ruby script/generate redmine_plugin_model <plugin_name> <model_name> [<fields>]
75 44 John Yani
</pre>
76 44 John Yani
77 44 John Yani
So, go to the command prompt and run:
78 44 John Yani
79 44 John Yani
<pre>
80 1 Jean-Philippe Lang
   ruby script/generate redmine_plugin_model polls poll question:string yes:integer no:integer
81 44 John Yani
</pre>
82 14 Jean-Philippe Lang
83 19 Jean-Philippe Lang
This creates the Poll model and the corresponding migration file.
84 1 Jean-Philippe Lang
85 28 John Fisher
*Please note you may have to rename your migration.* Timestamped migrations are not supported by the actual Redmine plugin engine (Engines). If your migrations are named with a timestamp, rename it using "001", "002", etc. instead.
86 28 John Fisher
87 49 Igor Zubkov
   <pre>mv vendor/plugins/redmine_polls/db/migrate/20091009211553_create_polls.rb vendor/plugins/redmine_polls/db/migrate/001_create_polls.rb</pre>
88 28 John Fisher
89 28 John Fisher
If you have already created a database table record in plugin_schema_info with the timestamp version number, you will have to change it to reflect your new version number, or the migration will hang.
90 28 John Fisher
91 21 Jean-Baptiste Barth
92 14 Jean-Philippe Lang
Migrate the database using the following command:
93 14 Jean-Philippe Lang
94 14 Jean-Philippe Lang
  rake db:migrate_plugins
95 14 Jean-Philippe Lang
96 14 Jean-Philippe Lang
Note that each plugin has its own set of migrations.
97 14 Jean-Philippe Lang
98 55 Thomas Winkel
Lets add some Polls in the console so we have something to work with.  The console is where you can interactively work and examine the Redmine environment and is very informative to play around in.  But for now we just need create two Poll objects
99 24 Eric Davis
100 24 Eric Davis
<pre>
101 24 Eric Davis
script/console
102 64 Denny Schäfer
>> Poll.create(:question => "Can you see this poll")
103 64 Denny Schäfer
>> Poll.create(:question => "And can you see this other poll")
104 24 Eric Davis
>> exit
105 24 Eric Davis
</pre>
106 24 Eric Davis
107 26 Eric Davis
Edit @vendor/plugins/redmine_polls/app/models/poll.rb@ in your plugin directory to add a #vote method that will be invoked from our controller:
108 15 Jean-Philippe Lang
109 15 Jean-Philippe Lang
<pre><code class="ruby">
110 19 Jean-Philippe Lang
class Poll < ActiveRecord::Base
111 15 Jean-Philippe Lang
  def vote(answer)
112 15 Jean-Philippe Lang
    increment(answer == 'yes' ? :yes : :no)
113 15 Jean-Philippe Lang
  end
114 15 Jean-Philippe Lang
end
115 15 Jean-Philippe Lang
</code></pre>
116 15 Jean-Philippe Lang
117 1 Jean-Philippe Lang
h2. Generating a controller
118 1 Jean-Philippe Lang
119 60 Mischa The Evil
*Warning*: starting from version#40, Redmine won't provide anymore the default wildcard route (@':controller/:action/:id'@). Plugins will have to declare the routes they need in their proper @config/routes.rb@ file.
120 57 Etienne Massip
121 1 Jean-Philippe Lang
For now, the plugin doesn't do anything. So let's create a controller for our plugin.
122 9 Jean-Philippe Lang
We can use the plugin controller generator for that. Syntax is:
123 9 Jean-Philippe Lang
124 23 Jean-Baptiste Barth
<pre>ruby script/generate redmine_plugin_controller <plugin_name> <controller_name> [<actions>]</pre>
125 9 Jean-Philippe Lang
126 9 Jean-Philippe Lang
So go back to the command prompt and run:
127 3 Jean-Philippe Lang
128 3 Jean-Philippe Lang
<pre>
129 18 Jean-Philippe Lang
% ruby script/generate redmine_plugin_controller Polls polls index vote
130 3 Jean-Philippe Lang
      exists  app/controllers/
131 3 Jean-Philippe Lang
      exists  app/helpers/
132 18 Jean-Philippe Lang
      create  app/views/polls
133 3 Jean-Philippe Lang
      create  test/functional/
134 18 Jean-Philippe Lang
      create  app/controllers/polls_controller.rb
135 18 Jean-Philippe Lang
      create  test/functional/polls_controller_test.rb
136 18 Jean-Philippe Lang
      create  app/helpers/polls_helper.rb
137 18 Jean-Philippe Lang
      create  app/views/polls/index.html.erb
138 18 Jean-Philippe Lang
      create  app/views/polls/vote.html.erb
139 3 Jean-Philippe Lang
</pre>
140 3 Jean-Philippe Lang
141 18 Jean-Philippe Lang
A controller @PollsController@ with 2 actions (@#index@ and @#vote@) is created.
142 3 Jean-Philippe Lang
143 26 Eric Davis
Edit @vendor/plugins/redmine_polls/app/controllers/polls_controller.rb@ in @redmine_polls@ directory to implement these 2 actions.
144 3 Jean-Philippe Lang
145 3 Jean-Philippe Lang
<pre><code class="ruby">
146 18 Jean-Philippe Lang
class PollsController < ApplicationController
147 1 Jean-Philippe Lang
  unloadable
148 1 Jean-Philippe Lang
149 7 Jean-Philippe Lang
  def index
150 19 Jean-Philippe Lang
    @polls = Poll.find(:all)
151 3 Jean-Philippe Lang
  end
152 7 Jean-Philippe Lang
153 19 Jean-Philippe Lang
  def vote
154 1 Jean-Philippe Lang
    poll = Poll.find(params[:id])
155 21 Jean-Baptiste Barth
    poll.vote(params[:answer])
156 25 Eric Davis
    if poll.save
157 25 Eric Davis
      flash[:notice] = 'Vote saved.'
158 25 Eric Davis
      redirect_to :action => 'index'
159 25 Eric Davis
    end
160 3 Jean-Philippe Lang
  end
161 3 Jean-Philippe Lang
end
162 1 Jean-Philippe Lang
</code></pre>
163 5 Jean-Philippe Lang
164 26 Eric Davis
Then edit @vendor/plugins/redmine_polls/app/views/polls/index.html.erb@ that will display existing polls:
165 3 Jean-Philippe Lang
166 3 Jean-Philippe Lang
167 3 Jean-Philippe Lang
<pre>
168 18 Jean-Philippe Lang
<h2>Polls</h2>
169 3 Jean-Philippe Lang
170 19 Jean-Philippe Lang
<% @polls.each do |poll| %>
171 3 Jean-Philippe Lang
  <p>
172 19 Jean-Philippe Lang
  <%= poll[:question] %>?
173 50 Igor Zubkov
  <%= link_to 'Yes', { :action => 'vote', :id => poll[:id], :answer => 'yes' }, :method => :post %> (<%= poll[:yes] %>) /
174 50 Igor Zubkov
  <%= link_to 'No', { :action => 'vote', :id => poll[:id], :answer => 'no' }, :method => :post %> (<%= poll[:no] %>)
175 3 Jean-Philippe Lang
  </p>
176 3 Jean-Philippe Lang
<% end %>
177 3 Jean-Philippe Lang
</pre>
178 3 Jean-Philippe Lang
179 26 Eric Davis
You can remove @vendor/plugins/redmine_polls/app/views/polls/vote.html.erb@ since no rendering is done by the corresponding action.
180 3 Jean-Philippe Lang
181 18 Jean-Philippe Lang
Now, restart the application and point your browser to http://localhost:3000/polls.
182 18 Jean-Philippe Lang
You should see the 2 polls and you should be able to vote for them:
183 4 Jean-Philippe Lang
184 29 Vinod Singh
!pools1.png!
185 4 Jean-Philippe Lang
186 19 Jean-Philippe Lang
Note that poll results are reset on each request if you don't run the application in production mode, since our poll "model" is stored in a class variable in this example.
187 4 Jean-Philippe Lang
188 37 Randy Syring
h2. Translations
189 37 Randy Syring
190 38 Randy Syring
The location of *.yml translation files is dependent on the version of Redmine that is being run:
191 38 Randy Syring
192 38 Randy Syring
|_. Version |_. Path|
193 38 Randy Syring
| < 0.9 | @.../redmine_polls/lang@ |
194 38 Randy Syring
| >= 0.9 | @.../redmine_polls/config/locales@ |
195 38 Randy Syring
196 38 Randy Syring
If you want your plugin to work in both versions, you will need to have the same translation file in both locations.
197 37 Randy Syring
198 4 Jean-Philippe Lang
h2. Extending menus
199 4 Jean-Philippe Lang
200 18 Jean-Philippe Lang
Our controller works fine but users have to know the url to see the polls. Using the Redmine plugin API, you can extend standard menus.
201 4 Jean-Philippe Lang
So let's add a new item to the application menu.
202 4 Jean-Philippe Lang
203 4 Jean-Philippe Lang
h3. Extending the application menu
204 4 Jean-Philippe Lang
205 26 Eric Davis
Edit @vendor/plugins/redmine_polls/init.rb@ at the root of your plugin directory to add the following line at the end of the plugin registration block:
206 4 Jean-Philippe Lang
207 4 Jean-Philippe Lang
<pre><code class="ruby">
208 18 Jean-Philippe Lang
Redmine::Plugin.register :redmine_polls do
209 4 Jean-Philippe Lang
  [...]
210 4 Jean-Philippe Lang
  
211 18 Jean-Philippe Lang
  menu :application_menu, :polls, { :controller => 'polls', :action => 'index' }, :caption => 'Polls'
212 4 Jean-Philippe Lang
end
213 4 Jean-Philippe Lang
</code></pre>
214 4 Jean-Philippe Lang
215 4 Jean-Philippe Lang
Syntax is:
216 4 Jean-Philippe Lang
217 4 Jean-Philippe Lang
  menu(menu_name, item_name, url, options={})
218 4 Jean-Philippe Lang
219 42 Mischa The Evil
There are five menus that you can extend:
220 4 Jean-Philippe Lang
221 4 Jean-Philippe Lang
* @:top_menu@ - the top left menu
222 4 Jean-Philippe Lang
* @:account_menu@ - the top right menu with sign in/sign out links
223 4 Jean-Philippe Lang
* @:application_menu@ - the main menu displayed when the user is not inside a project
224 4 Jean-Philippe Lang
* @:project_menu@ - the main menu displayed when the user is inside a project
225 41 Nick Peelman
* @:admin_menu@ - the menu displayed on the Administration page (can only insert after Settings, before Plugins)
226 4 Jean-Philippe Lang
227 4 Jean-Philippe Lang
Available options are:
228 4 Jean-Philippe Lang
229 4 Jean-Philippe Lang
* @:param@ - the parameter key that is used for the project id (default is @:id@)
230 4 Jean-Philippe Lang
* @:if@ - a Proc that is called before rendering the item, the item is displayed only if it returns true
231 4 Jean-Philippe Lang
* @:caption@ - the menu caption that can be:
232 4 Jean-Philippe Lang
233 4 Jean-Philippe Lang
  * a localized string Symbol
234 4 Jean-Philippe Lang
  * a String
235 4 Jean-Philippe Lang
  * a Proc that can take the project as argument
236 4 Jean-Philippe Lang
237 4 Jean-Philippe Lang
* @:before@, @:after@ - specify where the menu item should be inserted (eg. @:after => :activity@)
238 36 Jérémie Delaitre
* @:first@, @:last@ - if set to true, the item will stay at the beginning/end of the menu (eg. @:last => true@)
239 36 Jérémie Delaitre
* @:html@ - a hash of html options that are passed to @link_to@ when rendering the menu item
240 4 Jean-Philippe Lang
241 4 Jean-Philippe Lang
In our example, we've added an item to the application menu which is emtpy by default.
242 4 Jean-Philippe Lang
Restart the application and go to http://localhost:3000:
243 4 Jean-Philippe Lang
244 29 Vinod Singh
!application_menu.png!
245 4 Jean-Philippe Lang
246 18 Jean-Philippe Lang
Now you can access the polls by clicking the Polls tab from the welcome screen.
247 4 Jean-Philippe Lang
248 4 Jean-Philippe Lang
h3. Extending the project menu
249 4 Jean-Philippe Lang
250 19 Jean-Philippe Lang
Now, let's consider that the polls are defined at project level (even if it's not the case in our example poll model). So we would like to add the Polls tab to the project menu instead.
251 6 Jean-Philippe Lang
Open @init.rb@ and replace the line that was added just before with these 2 lines:
252 6 Jean-Philippe Lang
253 6 Jean-Philippe Lang
<pre><code class="ruby">
254 18 Jean-Philippe Lang
Redmine::Plugin.register :redmine_polls do
255 6 Jean-Philippe Lang
  [...]
256 6 Jean-Philippe Lang
257 51 Igor Zubkov
  permission :polls, { :polls => [:index, :vote] }, :public => true
258 18 Jean-Philippe Lang
  menu :project_menu, :polls, { :controller => 'polls', :action => 'index' }, :caption => 'Polls', :after => :activity, :param => :project_id
259 6 Jean-Philippe Lang
end
260 6 Jean-Philippe Lang
</code></pre>
261 6 Jean-Philippe Lang
262 18 Jean-Philippe Lang
The second line adds our Polls tab to the project menu, just after the activity tab.
263 18 Jean-Philippe Lang
The first line is required and declares that our 2 actions from @PollsController@ are public. We'll come back later to explain this with more details.
264 6 Jean-Philippe Lang
265 6 Jean-Philippe Lang
Restart the application again and go to one of your projects:
266 6 Jean-Philippe Lang
267 39 Ric Turley
!http://www.redmine.org/attachments/3773/project_menu.png!
268 6 Jean-Philippe Lang
269 18 Jean-Philippe Lang
If you click the Polls tab, you should notice that the project menu is no longer displayed.
270 6 Jean-Philippe Lang
To make the project menu visible, you have to initialize the controller's instance variable @@project@.
271 6 Jean-Philippe Lang
272 18 Jean-Philippe Lang
Edit your PollsController to do so:
273 6 Jean-Philippe Lang
274 6 Jean-Philippe Lang
<pre><code class="ruby">
275 6 Jean-Philippe Lang
def index
276 63 Harry Garrood
  @project = Project.find(params[:project_id])
277 19 Jean-Philippe Lang
  @polls = Poll.find(:all) # @project.polls
278 6 Jean-Philippe Lang
end
279 61 Harry Garrood
</code></pre>
280 61 Harry Garrood
281 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.
282 6 Jean-Philippe Lang
283 18 Jean-Philippe Lang
Now, you should see the project menu when viewing the polls:
284 6 Jean-Philippe Lang
285 39 Ric Turley
!http://www.redmine.org/attachments/3774/project_menu_pools.png!
286 4 Jean-Philippe Lang
287 4 Jean-Philippe Lang
h2. Adding new permissions
288 4 Jean-Philippe Lang
289 18 Jean-Philippe Lang
For now, anyone can vote for polls. Let's make it more configurable by changing the permission declaration.
290 18 Jean-Philippe Lang
We're going to declare 2 project based permissions, one for viewing the polls and an other one for voting. These permissions are no longer public (@:public => true@ option is removed).
291 10 Jean-Philippe Lang
292 26 Eric Davis
Edit @vendor/plugins/redmine_polls/init.rb@ to replace the previous permission declaration with these 2 lines:
293 10 Jean-Philippe Lang
294 10 Jean-Philippe Lang
<pre><code class="ruby">
295 20 Jean-Philippe Lang
296 18 Jean-Philippe Lang
  permission :view_polls, :polls => :index
297 18 Jean-Philippe Lang
  permission :vote_polls, :polls => :vote
298 1 Jean-Philippe Lang
</code></pre>
299 14 Jean-Philippe Lang
300 10 Jean-Philippe Lang
301 10 Jean-Philippe Lang
Restart the application and go to http://localhost:3000/roles/report:
302 10 Jean-Philippe Lang
303 29 Vinod Singh
!permissions1.png!
304 10 Jean-Philippe Lang
305 10 Jean-Philippe Lang
You're now able to give these permissions to your existing roles.
306 10 Jean-Philippe Lang
307 18 Jean-Philippe Lang
Of course, some code needs to be added to the PollsController so that actions are actually protected according to the permissions of the current user.
308 10 Jean-Philippe Lang
For this, we just need to append the @:authorize@ filter and make sure that the @project instance variable is properly set before calling this filter.
309 10 Jean-Philippe Lang
310 10 Jean-Philippe Lang
Here is how it would look like for the @#index@ action:
311 10 Jean-Philippe Lang
312 1 Jean-Philippe Lang
<pre><code class="ruby">
313 18 Jean-Philippe Lang
class PollsController < ApplicationController
314 10 Jean-Philippe Lang
  unloadable
315 10 Jean-Philippe Lang
  
316 10 Jean-Philippe Lang
  before_filter :find_project, :authorize, :only => :index
317 10 Jean-Philippe Lang
318 10 Jean-Philippe Lang
  [...]
319 10 Jean-Philippe Lang
  
320 10 Jean-Philippe Lang
  def index
321 19 Jean-Philippe Lang
    @polls = Poll.find(:all) # @project.polls
322 10 Jean-Philippe Lang
  end
323 10 Jean-Philippe Lang
324 10 Jean-Philippe Lang
  [...]
325 10 Jean-Philippe Lang
  
326 10 Jean-Philippe Lang
  private
327 10 Jean-Philippe Lang
  
328 10 Jean-Philippe Lang
  def find_project
329 10 Jean-Philippe Lang
    # @project variable must be set before calling the authorize filter
330 10 Jean-Philippe Lang
    @project = Project.find(params[:project_id])
331 10 Jean-Philippe Lang
  end
332 10 Jean-Philippe Lang
end
333 10 Jean-Philippe Lang
</code></pre>
334 10 Jean-Philippe Lang
335 18 Jean-Philippe Lang
Retrieving the current project before the @#vote@ action could be done using a similar way.
336 4 Jean-Philippe Lang
After this, viewing and voting polls will be only available to admin users or users that have the appropriate role on the project.
337 31 Markus Bockman
338 1 Jean-Philippe Lang
If you want to display the symbols of your permissions in a multilangual way, you need to add the necessary text labels in a language file.
339 37 Randy Syring
Simply create an *.yml file in the correct translation directory for your Redmine version and fill it with labels like this:
340 31 Markus Bockman
341 31 Markus Bockman
<pre><code class="ruby">
342 31 Markus Bockman
343 31 Markus Bockman
  permission_view_polls: View Polls
344 31 Markus Bockman
  permission_vote_polls: Vote Polls
345 31 Markus Bockman
346 31 Markus Bockman
</code></pre>
347 31 Markus Bockman
348 31 Markus Bockman
In this example the created file is known as en.yml, but all other supported language files are also possible too.
349 31 Markus Bockman
As you can see on the example above, the labels consists of the permission symbols @:view_polls@ and @:vote_polls@ with an additional @permission_@ added at the front. 
350 31 Markus Bockman
351 31 Markus Bockman
Restart your application and point the permission section.
352 31 Markus Bockman
353 4 Jean-Philippe Lang
h2. Creating a project module
354 4 Jean-Philippe Lang
355 56 Thomas Winkel
For now, the poll functionality is added to all your projects. But you may want to enable polls for some projects only.
356 26 Eric Davis
So, let's create a 'Polls' project module. This is done by wrapping the permissions declaration inside a call to @#project_module@.
357 11 Jean-Philippe Lang
358 11 Jean-Philippe Lang
Edit @init.rb@ and change the permissions declaration:
359 11 Jean-Philippe Lang
360 11 Jean-Philippe Lang
<pre><code class="ruby">
361 18 Jean-Philippe Lang
  project_module :polls do
362 18 Jean-Philippe Lang
    permission :view_polls, :polls => :index
363 18 Jean-Philippe Lang
    permission :vote_polls, :polls => :vote
364 11 Jean-Philippe Lang
  end
365 11 Jean-Philippe Lang
</code></pre>
366 11 Jean-Philippe Lang
367 11 Jean-Philippe Lang
Restart the application and go to one of your project settings.
368 18 Jean-Philippe Lang
Click on the Modules tab. You should see the Polls module at the end of the modules list (disabled by default):
369 11 Jean-Philippe Lang
370 29 Vinod Singh
!modules.png!
371 11 Jean-Philippe Lang
372 18 Jean-Philippe Lang
You can now enable/disable polls at project level.
373 11 Jean-Philippe Lang
374 11 Jean-Philippe Lang
h2. Improving the plugin views
375 11 Jean-Philippe Lang
376 16 Jean-Philippe Lang
h3. Adding stylesheets
377 16 Jean-Philippe Lang
378 16 Jean-Philippe Lang
Let's start by adding a stylesheet to our plugin views.
379 26 Eric Davis
Create a file named @voting.css@ in the @vendor/plugins/redmine_polls/assets/stylesheets@ directory:
380 16 Jean-Philippe Lang
381 16 Jean-Philippe Lang
<pre>
382 16 Jean-Philippe Lang
a.vote { font-size: 120%; }
383 16 Jean-Philippe Lang
a.vote.yes { color: green; }
384 16 Jean-Philippe Lang
a.vote.no  { color: red; }
385 16 Jean-Philippe Lang
</pre>
386 16 Jean-Philippe Lang
387 18 Jean-Philippe Lang
When starting the application, plugin assets are automatically copied to @public/plugin_assets/redmine_polls/@ by Rails Engines to make them available through your web server. So any change to your plugin stylesheets or javascripts needs an application restart.
388 16 Jean-Philippe Lang
389 26 Eric Davis
Then, append the following lines at the end of @vendor/plugins/redmine_polls/app/views/polls/index.html.erb@ so that your stylesheet get included in the page header by Redmine:
390 16 Jean-Philippe Lang
391 16 Jean-Philippe Lang
<pre>
392 16 Jean-Philippe Lang
<% content_for :header_tags do %>
393 18 Jean-Philippe Lang
    <%= stylesheet_link_tag 'voting', :plugin => 'redmine_polls' %>
394 16 Jean-Philippe Lang
<% end %>
395 16 Jean-Philippe Lang
</pre>
396 16 Jean-Philippe Lang
397 18 Jean-Philippe Lang
Note that the @:plugin => 'redmine_polls'@ option is required when calling the @stylesheet_link_tag@ helper.
398 16 Jean-Philippe Lang
399 16 Jean-Philippe Lang
Javascripts can be included in plugin views using the @javascript_include_tag@ helper in the same way.
400 16 Jean-Philippe Lang
401 16 Jean-Philippe Lang
h3. Setting page title
402 16 Jean-Philippe Lang
403 16 Jean-Philippe Lang
You can set the HTML title from inside your views by using the @html_title@ helper.
404 16 Jean-Philippe Lang
Example:
405 16 Jean-Philippe Lang
406 53 Igor Zubkov
  <% html_title "Polls" %>
407 34 Tom Bostelmann
408 34 Tom Bostelmann
409 34 Tom Bostelmann
h2. Testing your plugin
410 34 Tom Bostelmann
411 34 Tom Bostelmann
h3. test/test_helper.rb:
412 34 Tom Bostelmann
413 34 Tom Bostelmann
Here are the contents of my test helper file:
414 34 Tom Bostelmann
415 34 Tom Bostelmann
<pre>
416 34 Tom Bostelmann
require File.expand_path(File.dirname(__FILE__) + '/../../../../test/test_helper')
417 34 Tom Bostelmann
</pre>
418 34 Tom Bostelmann
419 34 Tom Bostelmann
h3. Sample test:
420 34 Tom Bostelmann
421 34 Tom Bostelmann
Contents of requirements_controller_test.rb:
422 34 Tom Bostelmann
423 54 Igor Zubkov
<pre><code class="ruby">
424 34 Tom Bostelmann
require File.dirname(__FILE__) + '/../test_helper'
425 34 Tom Bostelmann
require 'requirements_controller'
426 34 Tom Bostelmann
427 34 Tom Bostelmann
class RequirementsControllerTest < ActionController::TestCase
428 34 Tom Bostelmann
  fixtures :projects, :versions, :users, :roles, :members, :member_roles, :issues, :journals, :journal_details,
429 34 Tom Bostelmann
           :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages,
430 34 Tom Bostelmann
           :attachments, :custom_fields, :custom_values, :time_entries
431 34 Tom Bostelmann
432 34 Tom Bostelmann
  def setup
433 34 Tom Bostelmann
    @skill = Skill.new(:skill_name => 'Java')
434 34 Tom Bostelmann
    @project = Project.find(1)
435 34 Tom Bostelmann
    @request    = ActionController::TestRequest.new
436 34 Tom Bostelmann
    @response   = ActionController::TestResponse.new
437 34 Tom Bostelmann
    User.current = nil
438 34 Tom Bostelmann
  end
439 34 Tom Bostelmann
440 34 Tom Bostelmann
  def test_routing
441 34 Tom Bostelmann
    assert_routing(
442 34 Tom Bostelmann
      {:method => :get, :path => '/requirements'},
443 34 Tom Bostelmann
      :controller => 'requirements', :action => 'index'
444 34 Tom Bostelmann
    )
445 34 Tom Bostelmann
  end
446 52 Igor Zubkov
end
447 54 Igor Zubkov
</code></pre>
448 34 Tom Bostelmann
449 34 Tom Bostelmann
h3. Initialize Test DB:
450 34 Tom Bostelmann
451 34 Tom Bostelmann
I found it easiest to initialize the test db directly with the following rake call:
452 34 Tom Bostelmann
453 34 Tom Bostelmann
<pre>
454 43 David Fischer
rake db:drop db:create db:migrate db:migrate_plugins redmine:load_default_data RAILS_ENV=test
455 34 Tom Bostelmann
</pre>
456 34 Tom Bostelmann
457 34 Tom Bostelmann
h3. Run test:
458 34 Tom Bostelmann
459 48 Igor Zubkov
To execute the requirements_controller_test.rb I used the following command:
460 34 Tom Bostelmann
461 34 Tom Bostelmann
<pre>
462 34 Tom Bostelmann
rake test:engines:all PLUGIN=redmine_requirements
463 34 Tom Bostelmann
</pre>
464 35 Tom Bostelmann
465 47 Mo Morsi
h3. Testing with permissions
466 35 Tom Bostelmann
467 35 Tom Bostelmann
If your plugin requires membership to a project, add the following to the beginning of your functional tests:
468 35 Tom Bostelmann
469 1 Jean-Philippe Lang
<pre>
470 47 Mo Morsi
def test_index
471 47 Mo Morsi
  @request.session[:user_id] = 2
472 47 Mo Morsi
  ...
473 1 Jean-Philippe Lang
end
474 47 Mo Morsi
</pre>
475 1 Jean-Philippe Lang
476 47 Mo Morsi
If your plugin requires a specific permission, you can add that to a user role like so (lookup which role is appropriate for the user in the fixtures):
477 47 Mo Morsi
478 47 Mo Morsi
<pre>
479 1 Jean-Philippe Lang
def test_index
480 47 Mo Morsi
  Role.find(1).add_permission! :my_permission
481 47 Mo Morsi
  ...
482 1 Jean-Philippe Lang
end
483 1 Jean-Philippe Lang
</pre>
484 35 Tom Bostelmann
485 47 Mo Morsi
486 47 Mo Morsi
You may enable/disable a specific module like so:
487 47 Mo Morsi
488 47 Mo Morsi
<pre>
489 47 Mo Morsi
def test_index
490 47 Mo Morsi
  Project.find(1).enabled_module_names = [:mymodule]
491 47 Mo Morsi
  ...
492 47 Mo Morsi
end
493 47 Mo Morsi
</pre>