Plugin Tutorial » History » Version 74

Etienne Massip, 2012-05-29 10:40
Added code markup for erb code snippet

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