Plugin Tutorial » History » Version 71

Jean-Philippe Lang, 2012-05-28 10:54

1 1 Jean-Philippe Lang
h1. Plugin Tutorial
2 12 Jean-Philippe Lang
3 67 Jean-Philippe Lang
This tutorial is based on Redmine 2.x. You can view a previous version of this tutorial for Redmine 1.x "here":/projects/redmine/wiki/Plugin_Tutorial?version=66.
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 68 Jean-Philippe Lang
$ 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 67 Jean-Philippe Lang
<pre>ruby script/rails generate redmine_plugin <plugin_name></pre>
25 9 Jean-Philippe Lang
26 1 Jean-Philippe Lang
So open up a command prompt and "cd" to your redmine directory, then execute the following command:
27 1 Jean-Philippe Lang
28 1 Jean-Philippe Lang
<pre>
29 68 Jean-Philippe Lang
$ ruby script/rails generate redmine_plugin Polls
30 67 Jean-Philippe Lang
      create  plugins/polls/app
31 67 Jean-Philippe Lang
      create  plugins/polls/app/controllers
32 67 Jean-Philippe Lang
      create  plugins/polls/app/helpers
33 67 Jean-Philippe Lang
      create  plugins/polls/app/models
34 67 Jean-Philippe Lang
      create  plugins/polls/app/views
35 67 Jean-Philippe Lang
      create  plugins/polls/db/migrate
36 67 Jean-Philippe Lang
      create  plugins/polls/lib/tasks
37 67 Jean-Philippe Lang
      create  plugins/polls/assets/images
38 67 Jean-Philippe Lang
      create  plugins/polls/assets/javascripts
39 67 Jean-Philippe Lang
      create  plugins/polls/assets/stylesheets
40 67 Jean-Philippe Lang
      create  plugins/polls/config/locales
41 67 Jean-Philippe Lang
      create  plugins/polls/test
42 67 Jean-Philippe Lang
      create  plugins/polls/README.rdoc
43 67 Jean-Philippe Lang
      create  plugins/polls/init.rb
44 67 Jean-Philippe Lang
      create  plugins/polls/config/routes.rb
45 67 Jean-Philippe Lang
      create  plugins/polls/config/locales/en.yml
46 67 Jean-Philippe Lang
      create  plugins/polls/test/test_helper.rb
47 1 Jean-Philippe Lang
</pre>
48 1 Jean-Philippe Lang
49 67 Jean-Philippe Lang
The plugin structure is created in @plugins/polls@. Edit @plugins/polls/init.rb@ to adjust plugin information (name, author, description and version):
50 1 Jean-Philippe Lang
51 1 Jean-Philippe Lang
<pre><code class="ruby">
52 67 Jean-Philippe Lang
Redmine::Plugin.register :polls do
53 1 Jean-Philippe Lang
  name 'Polls plugin'
54 1 Jean-Philippe Lang
  author 'John Smith'
55 1 Jean-Philippe Lang
  description 'A plugin for managing polls'
56 1 Jean-Philippe Lang
  version '0.0.1'
57 1 Jean-Philippe Lang
end
58 1 Jean-Philippe Lang
</code></pre>
59 1 Jean-Philippe Lang
60 1 Jean-Philippe Lang
Then restart the application and point your browser to http://localhost:3000/admin/plugins.
61 1 Jean-Philippe Lang
After logging in, you should see your new plugin in the plugins list:
62 1 Jean-Philippe Lang
63 71 Jean-Philippe Lang
p=. !plugins_list1.png!
64 1 Jean-Philippe Lang
65 1 Jean-Philippe Lang
h2. Generating a model
66 1 Jean-Philippe Lang
67 1 Jean-Philippe Lang
For now plugin doesn't store anything. Let's create a simple Poll model for our plugin. Syntax is:
68 1 Jean-Philippe Lang
69 1 Jean-Philippe Lang
<pre>
70 67 Jean-Philippe Lang
   ruby script/rails generate redmine_plugin_model <plugin_name> <model_name> [field[:type][:index] field[:type][:index] ...]
71 1 Jean-Philippe Lang
</pre>
72 1 Jean-Philippe Lang
73 1 Jean-Philippe Lang
So, go to the command prompt and run:
74 1 Jean-Philippe Lang
75 1 Jean-Philippe Lang
<pre>
76 68 Jean-Philippe Lang
$ ruby script/rails generate redmine_plugin_model polls poll question:string yes:integer no:integer
77 67 Jean-Philippe Lang
      create  plugins/polls/app/models/poll.rb
78 67 Jean-Philippe Lang
      create  plugins/polls/test/unit/poll_test.rb
79 67 Jean-Philippe Lang
      create  plugins/polls/db/migrate/001_create_polls.rb
80 13 Jean-Philippe Lang
</pre>
81 1 Jean-Philippe Lang
82 67 Jean-Philippe Lang
This creates the Poll model and the corresponding migration file @001_create_polls.rb@ in @plugins/polls/db/migrate@:
83 1 Jean-Philippe Lang
84 67 Jean-Philippe Lang
<pre><code class="ruby">
85 67 Jean-Philippe Lang
class CreatePolls < ActiveRecord::Migration
86 67 Jean-Philippe Lang
  def change
87 67 Jean-Philippe Lang
    create_table :polls do |t|
88 67 Jean-Philippe Lang
      t.string :question
89 67 Jean-Philippe Lang
      t.integer :yes, :default => 0
90 67 Jean-Philippe Lang
      t.integer :no, :default => 0
91 67 Jean-Philippe Lang
    end
92 67 Jean-Philippe Lang
  end
93 67 Jean-Philippe Lang
end
94 67 Jean-Philippe Lang
</code></pre>
95 1 Jean-Philippe Lang
96 67 Jean-Philippe Lang
You can adjust your migration file (eg. default values...) then migrate the database using the following command:
97 14 Jean-Philippe Lang
98 67 Jean-Philippe Lang
<pre>
99 68 Jean-Philippe Lang
$ rake redmine:plugins:migrate
100 1 Jean-Philippe Lang
101 67 Jean-Philippe Lang
Migrating polls (Polls plugin)...
102 67 Jean-Philippe Lang
==  CreatePolls: migrating ====================================================
103 67 Jean-Philippe Lang
-- create_table(:polls)
104 67 Jean-Philippe Lang
   -> 0.0410s
105 67 Jean-Philippe Lang
==  CreatePolls: migrated (0.0420s) ===========================================
106 67 Jean-Philippe Lang
</pre>
107 24 Eric Davis
108 64 Denny Schäfer
Note that each plugin has its own set of migrations.
109 64 Denny Schäfer
110 24 Eric Davis
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
111 24 Eric Davis
112 24 Eric Davis
<pre>
113 67 Jean-Philippe Lang
ruby script/rails console
114 15 Jean-Philippe Lang
>> Poll.create(:question => "Can you see this poll")
115 19 Jean-Philippe Lang
>> Poll.create(:question => "And can you see this other poll")
116 15 Jean-Philippe Lang
>> exit
117 15 Jean-Philippe Lang
</pre>
118 15 Jean-Philippe Lang
119 67 Jean-Philippe Lang
Edit @plugins/polls/app/models/poll.rb@ in your plugin directory to add a #vote method that will be invoked from our controller:
120 1 Jean-Philippe Lang
121 15 Jean-Philippe Lang
<pre><code class="ruby">
122 15 Jean-Philippe Lang
class Poll < ActiveRecord::Base
123 1 Jean-Philippe Lang
  def vote(answer)
124 1 Jean-Philippe Lang
    increment(answer == 'yes' ? :yes : :no)
125 60 Mischa The Evil
  end
126 57 Etienne Massip
end
127 9 Jean-Philippe Lang
</code></pre>
128 9 Jean-Philippe Lang
129 23 Jean-Baptiste Barth
h2. Generating a controller
130 9 Jean-Philippe Lang
131 67 Jean-Philippe Lang
*Warning*: Redmine does not provide the default wildcard route (@':controller/:action/:id'@). Plugins have to declare the routes they need in their proper @config/routes.rb@ file.
132 3 Jean-Philippe Lang
133 3 Jean-Philippe Lang
For now, the plugin doesn't do anything. So let's create a controller for our plugin.
134 1 Jean-Philippe Lang
We can use the plugin controller generator for that. Syntax is:
135 18 Jean-Philippe Lang
136 67 Jean-Philippe Lang
<pre>ruby script/rails generate redmine_plugin_controller <plugin_name> <controller_name> [<actions>]</pre>
137 1 Jean-Philippe Lang
138 1 Jean-Philippe Lang
So go back to the command prompt and run:
139 1 Jean-Philippe Lang
140 1 Jean-Philippe Lang
<pre>
141 68 Jean-Philippe Lang
$ ruby script/rails generate redmine_plugin_controller Polls polls index vote
142 67 Jean-Philippe Lang
      create  plugins/polls/app/controllers/polls_controller.rb
143 67 Jean-Philippe Lang
      create  plugins/polls/app/helpers/polls_helper.rb
144 67 Jean-Philippe Lang
      create  plugins/polls/test/functional/polls_controller_test.rb
145 67 Jean-Philippe Lang
      create  plugins/polls/app/views/polls/index.html.erb
146 67 Jean-Philippe Lang
      create  plugins/polls/app/views/polls/vote.html.erb
147 1 Jean-Philippe Lang
</pre>
148 3 Jean-Philippe Lang
149 1 Jean-Philippe Lang
A controller @PollsController@ with 2 actions (@#index@ and @#vote@) is created.
150 3 Jean-Philippe Lang
151 67 Jean-Philippe Lang
Edit @plugins/polls/app/controllers/polls_controller.rb@ to implement these 2 actions.
152 1 Jean-Philippe Lang
153 1 Jean-Philippe Lang
<pre><code class="ruby">
154 7 Jean-Philippe Lang
class PollsController < ApplicationController
155 19 Jean-Philippe Lang
  unloadable
156 3 Jean-Philippe Lang
157 7 Jean-Philippe Lang
  def index
158 19 Jean-Philippe Lang
    @polls = Poll.find(:all)
159 1 Jean-Philippe Lang
  end
160 21 Jean-Baptiste Barth
161 25 Eric Davis
  def vote
162 25 Eric Davis
    poll = Poll.find(params[:id])
163 25 Eric Davis
    poll.vote(params[:answer])
164 25 Eric Davis
    if poll.save
165 3 Jean-Philippe Lang
      flash[:notice] = 'Vote saved.'
166 3 Jean-Philippe Lang
      redirect_to :action => 'index'
167 1 Jean-Philippe Lang
    end
168 5 Jean-Philippe Lang
  end
169 26 Eric Davis
end
170 3 Jean-Philippe Lang
</code></pre>
171 3 Jean-Philippe Lang
172 67 Jean-Philippe Lang
Then edit @plugins/polls/app/views/polls/index.html.erb@ that will display existing polls:
173 3 Jean-Philippe Lang
174 19 Jean-Philippe Lang
<pre>
175 3 Jean-Philippe Lang
<h2>Polls</h2>
176 19 Jean-Philippe Lang
177 50 Igor Zubkov
<% @polls.each do |poll| %>
178 50 Igor Zubkov
  <p>
179 3 Jean-Philippe Lang
  <%= poll[:question] %>?
180 3 Jean-Philippe Lang
  <%= link_to 'Yes', { :action => 'vote', :id => poll[:id], :answer => 'yes' }, :method => :post %> (<%= poll[:yes] %>) /
181 3 Jean-Philippe Lang
  <%= link_to 'No', { :action => 'vote', :id => poll[:id], :answer => 'no' }, :method => :post %> (<%= poll[:no] %>)
182 3 Jean-Philippe Lang
  </p>
183 26 Eric Davis
<% end %>
184 3 Jean-Philippe Lang
</pre>
185 18 Jean-Philippe Lang
186 67 Jean-Philippe Lang
You can remove @plugins/polls/app/views/polls/vote.html.erb@ since no rendering is done by the corresponding action.
187 4 Jean-Philippe Lang
188 67 Jean-Philippe Lang
Edit @plugins/polls/config/routes.rb@ to add the 2 routes for the 2 actions:
189 67 Jean-Philippe Lang
190 67 Jean-Philippe Lang
<pre><code class="ruby">
191 67 Jean-Philippe Lang
get 'polls', :to => 'polls#index'
192 67 Jean-Philippe Lang
post 'post/:id/vote', :to => 'polls#vote'
193 67 Jean-Philippe Lang
</code></pre>
194 67 Jean-Philippe Lang
195 38 Randy Syring
Now, restart the application and point your browser to http://localhost:3000/polls.
196 1 Jean-Philippe Lang
You should see the 2 polls and you should be able to vote for them:
197 38 Randy Syring
198 71 Jean-Philippe Lang
p=. !pools1.png!
199 4 Jean-Philippe Lang
200 18 Jean-Philippe Lang
h2. Translations
201 4 Jean-Philippe Lang
202 67 Jean-Philippe Lang
The translation files must be stored in config/locales, eg. @plugins/polls/config/locales/@.
203 1 Jean-Philippe Lang
204 1 Jean-Philippe Lang
h2. Extending menus
205 4 Jean-Philippe Lang
206 26 Eric Davis
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.
207 4 Jean-Philippe Lang
So let's add a new item to the application menu.
208 4 Jean-Philippe Lang
209 18 Jean-Philippe Lang
h3. Extending the application menu
210 4 Jean-Philippe Lang
211 67 Jean-Philippe Lang
Edit @plugins/polls/init.rb@ at the root of your plugin directory to add the following line at the end of the plugin registration block:
212 18 Jean-Philippe Lang
213 4 Jean-Philippe Lang
<pre><code class="ruby">
214 4 Jean-Philippe Lang
Redmine::Plugin.register :redmine_polls do
215 4 Jean-Philippe Lang
  [...]
216 4 Jean-Philippe Lang
  
217 4 Jean-Philippe Lang
  menu :application_menu, :polls, { :controller => 'polls', :action => 'index' }, :caption => 'Polls'
218 4 Jean-Philippe Lang
end
219 4 Jean-Philippe Lang
</code></pre>
220 42 Mischa The Evil
221 4 Jean-Philippe Lang
Syntax is:
222 4 Jean-Philippe Lang
223 4 Jean-Philippe Lang
  menu(menu_name, item_name, url, options={})
224 4 Jean-Philippe Lang
225 4 Jean-Philippe Lang
There are five menus that you can extend:
226 1 Jean-Philippe Lang
227 4 Jean-Philippe Lang
* @:top_menu@ - the top left menu
228 4 Jean-Philippe Lang
* @:account_menu@ - the top right menu with sign in/sign out links
229 4 Jean-Philippe Lang
* @:application_menu@ - the main menu displayed when the user is not inside a project
230 4 Jean-Philippe Lang
* @:project_menu@ - the main menu displayed when the user is inside a project
231 4 Jean-Philippe Lang
* @:admin_menu@ - the menu displayed on the Administration page (can only insert after Settings, before Plugins)
232 4 Jean-Philippe Lang
233 4 Jean-Philippe Lang
Available options are:
234 4 Jean-Philippe Lang
235 1 Jean-Philippe Lang
* @:param@ - the parameter key that is used for the project id (default is @:id@)
236 36 Jérémie Delaitre
* @:if@ - a Proc that is called before rendering the item, the item is displayed only if it returns true
237 36 Jérémie Delaitre
* @:caption@ - the menu caption that can be:
238 4 Jean-Philippe Lang
239 4 Jean-Philippe Lang
  * a localized string Symbol
240 4 Jean-Philippe Lang
  * a String
241 4 Jean-Philippe Lang
  * a Proc that can take the project as argument
242 29 Vinod Singh
243 4 Jean-Philippe Lang
* @:before@, @:after@ - specify where the menu item should be inserted (eg. @:after => :activity@)
244 18 Jean-Philippe Lang
* @:first@, @:last@ - if set to true, the item will stay at the beginning/end of the menu (eg. @:last => true@)
245 4 Jean-Philippe Lang
* @:html@ - a hash of html options that are passed to @link_to@ when rendering the menu item
246 4 Jean-Philippe Lang
247 1 Jean-Philippe Lang
In our example, we've added an item to the application menu which is emtpy by default.
248 19 Jean-Philippe Lang
Restart the application and go to http://localhost:3000:
249 6 Jean-Philippe Lang
250 71 Jean-Philippe Lang
p=. !application_menu.png!
251 6 Jean-Philippe Lang
252 18 Jean-Philippe Lang
Now you can access the polls by clicking the Polls tab from the welcome screen.
253 6 Jean-Philippe Lang
254 6 Jean-Philippe Lang
h3. Extending the project menu
255 51 Igor Zubkov
256 18 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.
257 6 Jean-Philippe Lang
Open @init.rb@ and replace the line that was added just before with these 2 lines:
258 6 Jean-Philippe Lang
259 6 Jean-Philippe Lang
<pre><code class="ruby">
260 18 Jean-Philippe Lang
Redmine::Plugin.register :redmine_polls do
261 18 Jean-Philippe Lang
  [...]
262 6 Jean-Philippe Lang
263 6 Jean-Philippe Lang
  permission :polls, { :polls => [:index, :vote] }, :public => true
264 6 Jean-Philippe Lang
  menu :project_menu, :polls, { :controller => 'polls', :action => 'index' }, :caption => 'Polls', :after => :activity, :param => :project_id
265 1 Jean-Philippe Lang
end
266 1 Jean-Philippe Lang
</code></pre>
267 6 Jean-Philippe Lang
268 67 Jean-Philippe Lang
The second line adds our Polls tab to the project menu, just after the activity tab. 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. Restart the application again and go to one of your projects:
269 6 Jean-Philippe Lang
270 71 Jean-Philippe Lang
p=. !project_menu.png!
271 6 Jean-Philippe Lang
272 67 Jean-Philippe Lang
If you click the Polls tab (in 3rd position), you should notice that the project menu is no longer displayed.
273 19 Jean-Philippe Lang
To make the project menu visible, you have to initialize the controller's instance variable @@project@.
274 6 Jean-Philippe Lang
275 61 Harry Garrood
Edit your PollsController to do so:
276 61 Harry Garrood
277 6 Jean-Philippe Lang
<pre><code class="ruby">
278 6 Jean-Philippe Lang
def index
279 18 Jean-Philippe Lang
  @project = Project.find(params[:project_id])
280 6 Jean-Philippe Lang
  @polls = Poll.find(:all) # @project.polls
281 39 Ric Turley
end
282 4 Jean-Philippe Lang
</code></pre>
283 1 Jean-Philippe Lang
284 4 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.
285 18 Jean-Philippe Lang
286 18 Jean-Philippe Lang
Now, you should see the project menu when viewing the polls:
287 10 Jean-Philippe Lang
288 71 Jean-Philippe Lang
p=. !project_menu_pools.png!
289 10 Jean-Philippe Lang
290 10 Jean-Philippe Lang
h2. Adding new permissions
291 20 Jean-Philippe Lang
292 18 Jean-Philippe Lang
For now, anyone can vote for polls. Let's make it more configurable by changing the permission declaration.
293 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).
294 1 Jean-Philippe Lang
295 67 Jean-Philippe Lang
Edit @plugins/polls/init.rb@ to replace the previous permission declaration with these 2 lines:
296 10 Jean-Philippe Lang
297 10 Jean-Philippe Lang
<pre><code class="ruby">
298 1 Jean-Philippe Lang
  permission :view_polls, :polls => :index
299 29 Vinod Singh
  permission :vote_polls, :polls => :vote
300 1 Jean-Philippe Lang
</code></pre>
301 10 Jean-Philippe Lang
302 10 Jean-Philippe Lang
Restart the application and go to http://localhost:3000/roles/report:
303 18 Jean-Philippe Lang
304 71 Jean-Philippe Lang
p=. !permissions1.png!
305 10 Jean-Philippe Lang
306 10 Jean-Philippe Lang
You're now able to give these permissions to your existing roles.
307 1 Jean-Philippe Lang
308 67 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. 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 18 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 10 Jean-Philippe Lang
<pre><code class="ruby">
313 10 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 19 Jean-Philippe Lang
318 10 Jean-Philippe Lang
  [...]
319 10 Jean-Philippe Lang
  
320 10 Jean-Philippe Lang
  def index
321 10 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 18 Jean-Philippe Lang
  end
332 4 Jean-Philippe Lang
end
333 31 Markus Bockman
</code></pre>
334 1 Jean-Philippe Lang
335 37 Randy Syring
Retrieving the current project before the @#vote@ action could be done using a similar way.
336 31 Markus Bockman
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 31 Markus Bockman
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 67 Jean-Philippe Lang
Simply create an *.yml (eg. @en.yml@) file in @plugins/polls/config/locales@ and fill it with labels like this:
340 31 Markus Bockman
341 31 Markus Bockman
<pre><code class="ruby">
342 31 Markus Bockman
  permission_view_polls: View Polls
343 31 Markus Bockman
  permission_vote_polls: Vote Polls
344 31 Markus Bockman
</code></pre>
345 31 Markus Bockman
346 67 Jean-Philippe Lang
In this example the created file is known as @en.yml@, but all other supported language files are also possible too.
347 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. 
348 31 Markus Bockman
349 4 Jean-Philippe Lang
Restart your application and point the permission section.
350 4 Jean-Philippe Lang
351 56 Thomas Winkel
h2. Creating a project module
352 26 Eric Davis
353 11 Jean-Philippe Lang
For now, the poll functionality is added to all your projects. But you may want to enable polls for some projects only.
354 11 Jean-Philippe Lang
So, let's create a 'Polls' project module. This is done by wrapping the permissions declaration inside a call to @#project_module@.
355 11 Jean-Philippe Lang
356 11 Jean-Philippe Lang
Edit @init.rb@ and change the permissions declaration:
357 18 Jean-Philippe Lang
358 18 Jean-Philippe Lang
<pre><code class="ruby">
359 18 Jean-Philippe Lang
  project_module :polls do
360 11 Jean-Philippe Lang
    permission :view_polls, :polls => :index
361 1 Jean-Philippe Lang
    permission :vote_polls, :polls => :vote
362 11 Jean-Philippe Lang
  end
363 11 Jean-Philippe Lang
</code></pre>
364 18 Jean-Philippe Lang
365 11 Jean-Philippe Lang
Restart the application and go to one of your project settings.
366 29 Vinod Singh
Click on the Modules tab. You should see the Polls module at the end of the modules list (disabled by default):
367 11 Jean-Philippe Lang
368 71 Jean-Philippe Lang
p=. !modules.png!
369 11 Jean-Philippe Lang
370 11 Jean-Philippe Lang
You can now enable/disable polls at project level.
371 11 Jean-Philippe Lang
372 16 Jean-Philippe Lang
h2. Improving the plugin views
373 16 Jean-Philippe Lang
374 16 Jean-Philippe Lang
h3. Adding stylesheets
375 26 Eric Davis
376 16 Jean-Philippe Lang
Let's start by adding a stylesheet to our plugin views.
377 67 Jean-Philippe Lang
Create a file named @voting.css@ in the @plugins/polls/assets/stylesheets@ directory:
378 16 Jean-Philippe Lang
379 16 Jean-Philippe Lang
<pre>
380 16 Jean-Philippe Lang
a.vote { font-size: 120%; }
381 16 Jean-Philippe Lang
a.vote.yes { color: green; }
382 16 Jean-Philippe Lang
a.vote.no  { color: red; }
383 18 Jean-Philippe Lang
</pre>
384 16 Jean-Philippe Lang
385 67 Jean-Philippe Lang
When starting the application, plugin assets are automatically copied to @public/plugin_assets/polls/@ to make them available through your web server. So any change to your plugin stylesheets or javascripts needs an application restart.
386 16 Jean-Philippe Lang
387 67 Jean-Philippe Lang
Then, append the following lines at the end of @plugins/polls/app/views/polls/index.html.erb@ so that your stylesheet get included in the page header by Redmine:
388 16 Jean-Philippe Lang
389 18 Jean-Philippe Lang
<pre>
390 16 Jean-Philippe Lang
<% content_for :header_tags do %>
391 16 Jean-Philippe Lang
    <%= stylesheet_link_tag 'voting', :plugin => 'redmine_polls' %>
392 16 Jean-Philippe Lang
<% end %>
393 18 Jean-Philippe Lang
</pre>
394 16 Jean-Philippe Lang
395 16 Jean-Philippe Lang
Note that the @:plugin => 'redmine_polls'@ option is required when calling the @stylesheet_link_tag@ helper.
396 16 Jean-Philippe Lang
397 16 Jean-Philippe Lang
Javascripts can be included in plugin views using the @javascript_include_tag@ helper in the same way.
398 16 Jean-Philippe Lang
399 16 Jean-Philippe Lang
h3. Setting page title
400 1 Jean-Philippe Lang
401 16 Jean-Philippe Lang
You can set the HTML title from inside your views by using the @html_title@ helper.
402 53 Igor Zubkov
Example:
403 34 Tom Bostelmann
404 34 Tom Bostelmann
  <% html_title "Polls" %>
405 34 Tom Bostelmann
406 34 Tom Bostelmann
h2. Testing your plugin
407 34 Tom Bostelmann
408 71 Jean-Philippe Lang
h3. test/test_helper.rb
409 34 Tom Bostelmann
410 34 Tom Bostelmann
Here are the contents of my test helper file:
411 34 Tom Bostelmann
412 1 Jean-Philippe Lang
<pre>
413 69 Jean-Philippe Lang
require File.expand_path(File.dirname(__FILE__) + '/../../../test/test_helper')
414 34 Tom Bostelmann
</pre>
415 34 Tom Bostelmann
416 70 Jean-Philippe Lang
h3. Sample test
417 34 Tom Bostelmann
418 70 Jean-Philippe Lang
Contents of @polls_controller_test.rb@:
419 1 Jean-Philippe Lang
420 1 Jean-Philippe Lang
<pre><code class="ruby">
421 69 Jean-Philippe Lang
require File.expand_path('../../test_helper', __FILE__)
422 34 Tom Bostelmann
423 69 Jean-Philippe Lang
class PollsControllerTest < ActionController::TestCase
424 69 Jean-Philippe Lang
  fixtures :projects
425 34 Tom Bostelmann
426 69 Jean-Philippe Lang
  def test_index
427 69 Jean-Philippe Lang
    get :index, :project_id => 1
428 34 Tom Bostelmann
429 69 Jean-Philippe Lang
    assert_response 'success'
430 69 Jean-Philippe Lang
    assert_template 'index'
431 34 Tom Bostelmann
  end
432 52 Igor Zubkov
end
433 54 Igor Zubkov
</code></pre>
434 34 Tom Bostelmann
435 70 Jean-Philippe Lang
h3. Running test
436 34 Tom Bostelmann
437 70 Jean-Philippe Lang
Initialize the test database if necessary:
438 68 Jean-Philippe Lang
439 34 Tom Bostelmann
<pre>
440 34 Tom Bostelmann
$ rake db:drop db:create db:migrate db:migrate_plugins redmine:load_default_data RAILS_ENV=test
441 48 Igor Zubkov
</pre>
442 34 Tom Bostelmann
443 69 Jean-Philippe Lang
To execute the polls_controller_test.rb:
444 34 Tom Bostelmann
445 34 Tom Bostelmann
<pre>
446 69 Jean-Philippe Lang
$ ruby plugins\polls\test\functionals\polls_controller_test.rb
447 47 Mo Morsi
</pre>
448 35 Tom Bostelmann
449 35 Tom Bostelmann
h3. Testing with permissions
450 35 Tom Bostelmann
451 1 Jean-Philippe Lang
If your plugin requires membership to a project, add the following to the beginning of your functional tests:
452 47 Mo Morsi
453 47 Mo Morsi
<pre>
454 47 Mo Morsi
def test_index
455 1 Jean-Philippe Lang
  @request.session[:user_id] = 2
456 47 Mo Morsi
  ...
457 1 Jean-Philippe Lang
end
458 47 Mo Morsi
</pre>
459 47 Mo Morsi
460 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):
461 1 Jean-Philippe Lang
462 47 Mo Morsi
<pre>
463 47 Mo Morsi
def test_index
464 1 Jean-Philippe Lang
  Role.find(1).add_permission! :my_permission
465 1 Jean-Philippe Lang
  ...
466 35 Tom Bostelmann
end
467 47 Mo Morsi
</pre>
468 47 Mo Morsi
469 47 Mo Morsi
470 47 Mo Morsi
You may enable/disable a specific module like so:
471 47 Mo Morsi
472 47 Mo Morsi
<pre>
473 47 Mo Morsi
def test_index
474 47 Mo Morsi
  Project.find(1).enabled_module_names = [:mymodule]
475 47 Mo Morsi
  ...
476 1 Jean-Philippe Lang
end
477 1 Jean-Philippe Lang
</pre>