Plugin Tutorial » History » Version 35

Tom Bostelmann, 2010-01-25 15:33

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