Plugin Tutorial » History » Version 54

Igor Zubkov, 2011-10-28 10:01
Clean

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