プラグイン チュートリアル

Version 1 (Haru Iida, 05/28/2009 03:29 pm)

1 1 Haru Iida
www.redmine.orgの"Plugin Turorial":http://www.redmine.org/wiki/redmine/Plugin_Tutorial を和訳してみます。
2 1 Haru Iida
3 1 Haru Iida
h1. プラグイン チュートリアル
4 1 Haru Iida
5 1 Haru Iida
注意: このチュートリアルはRedmineの開発リビジョン r1786 以上を対象にしています。
6 1 Haru Iida
7 1 Haru Iida
{{toc}}
8 1 Haru Iida
9 1 Haru Iida
h2. 新しいプラグインを作る。
10 1 Haru Iida
11 1 Haru Iida
プラグインの新規作成はRedmineのプラグインジェネレータを使用して行うことができます。
12 1 Haru Iida
ジェネレータのコマンドは以下です。:
13 1 Haru Iida
14 1 Haru Iida
<pre>ruby script/generate redmine_plugin <plugin_name></pre>
15 1 Haru Iida
16 1 Haru Iida
コマンドプロンプトを開き "cd" であなたがredmineをインストールしたディレクトリに移動し、以下のコマンドを実行してみましょう。:
17 1 Haru Iida
18 1 Haru Iida
  % ruby script/generate redmine_plugin Polls
19 1 Haru Iida
20 1 Haru Iida
The plugin structure is created in @vendor/plugins/redmine_polls@:
21 1 Haru Iida
22 1 Haru Iida
<pre>
23 1 Haru Iida
      create  vendor/plugins/redmine_polls/app/controllers
24 1 Haru Iida
      create  vendor/plugins/redmine_polls/app/helpers
25 1 Haru Iida
      create  vendor/plugins/redmine_polls/app/models
26 1 Haru Iida
      create  vendor/plugins/redmine_polls/app/views
27 1 Haru Iida
      create  vendor/plugins/redmine_polls/db/migrate
28 1 Haru Iida
      create  vendor/plugins/redmine_polls/lib/tasks
29 1 Haru Iida
      create  vendor/plugins/redmine_polls/assets/images
30 1 Haru Iida
      create  vendor/plugins/redmine_polls/assets/javascripts
31 1 Haru Iida
      create  vendor/plugins/redmine_polls/assets/stylesheets
32 1 Haru Iida
      create  vendor/plugins/redmine_polls/lang
33 1 Haru Iida
      create  vendor/plugins/redmine_polls/README
34 1 Haru Iida
      create  vendor/plugins/redmine_polls/init.rb
35 1 Haru Iida
      create  vendor/plugins/redmine_polls/lang/en.yml
36 1 Haru Iida
</pre>
37 1 Haru Iida
38 1 Haru Iida
Edit @vendor/plugins/redmine_polls/init.rb@ to adjust plugin information (name, author, description and version):
39 1 Haru Iida
40 1 Haru Iida
<pre><code class="ruby">
41 1 Haru Iida
require 'redmine'
42 1 Haru Iida
43 1 Haru Iida
Redmine::Plugin.register :redmine_polls do
44 1 Haru Iida
  name 'Polls plugin'
45 1 Haru Iida
  author 'John Smith'
46 1 Haru Iida
  description 'A plugin for managing polls'
47 1 Haru Iida
  version '0.0.1'
48 1 Haru Iida
end
49 1 Haru Iida
</code></pre>
50 1 Haru Iida
51 1 Haru Iida
Then restart the application and point your browser to http://localhost:3000/admin/info.
52 1 Haru Iida
After logging in, you should see your new plugin in the plugins list:
53 1 Haru Iida
54 1 Haru Iida
p=. !plugins_list1.png!
55 1 Haru Iida
56 1 Haru Iida
h2. Generating a model
57 1 Haru Iida
58 1 Haru Iida
Let's create a simple Poll model for our plugin:
59 1 Haru Iida
60 1 Haru Iida
  ruby script/generate redmine_plugin_model polls poll question:string yes:integer no:integer
61 1 Haru Iida
62 1 Haru Iida
This creates the Poll model and the corresponding migration file.
63 1 Haru Iida
64 1 Haru Iida
Please note that 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.
65 1 Haru Iida
66 1 Haru Iida
Migrate the database using the following command:
67 1 Haru Iida
68 1 Haru Iida
  rake db:migrate_plugins
69 1 Haru Iida
70 1 Haru Iida
Note that each plugin has its own set of migrations.
71 1 Haru Iida
72 1 Haru Iida
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
73 1 Haru Iida
74 1 Haru Iida
<pre>
75 1 Haru Iida
script/console
76 1 Haru Iida
>> Poll.create(:question => "Can you see this poll ?")
77 1 Haru Iida
>> Poll.create(:question => "And can you see this other poll ?")
78 1 Haru Iida
>> exit
79 1 Haru Iida
</pre>
80 1 Haru Iida
81 1 Haru Iida
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:
82 1 Haru Iida
83 1 Haru Iida
<pre><code class="ruby">
84 1 Haru Iida
class Poll < ActiveRecord::Base
85 1 Haru Iida
  def vote(answer)
86 1 Haru Iida
    increment(answer == 'yes' ? :yes : :no)
87 1 Haru Iida
  end
88 1 Haru Iida
end
89 1 Haru Iida
</code></pre>
90 1 Haru Iida
91 1 Haru Iida
h2. Generating a controller
92 1 Haru Iida
93 1 Haru Iida
For now, the plugin doesn't do anything. So let's create a controller for our plugin.
94 1 Haru Iida
We can use the plugin controller generator for that. Syntax is:
95 1 Haru Iida
96 1 Haru Iida
<pre>ruby script/generate redmine_plugin_controller <plugin_name> <controller_name> [<actions>]</pre>
97 1 Haru Iida
98 1 Haru Iida
So go back to the command prompt and run:
99 1 Haru Iida
100 1 Haru Iida
<pre>
101 1 Haru Iida
% ruby script/generate redmine_plugin_controller Polls polls index vote
102 1 Haru Iida
      exists  app/controllers/
103 1 Haru Iida
      exists  app/helpers/
104 1 Haru Iida
      create  app/views/polls
105 1 Haru Iida
      create  test/functional/
106 1 Haru Iida
      create  app/controllers/polls_controller.rb
107 1 Haru Iida
      create  test/functional/polls_controller_test.rb
108 1 Haru Iida
      create  app/helpers/polls_helper.rb
109 1 Haru Iida
      create  app/views/polls/index.html.erb
110 1 Haru Iida
      create  app/views/polls/vote.html.erb
111 1 Haru Iida
</pre>
112 1 Haru Iida
113 1 Haru Iida
A controller @PollsController@ with 2 actions (@#index@ and @#vote@) is created.
114 1 Haru Iida
115 1 Haru Iida
Edit @vendor/plugins/redmine_polls/app/controllers/polls_controller.rb@ in @redmine_polls@ directory to implement these 2 actions.
116 1 Haru Iida
117 1 Haru Iida
<pre><code class="ruby">
118 1 Haru Iida
class PollsController < ApplicationController
119 1 Haru Iida
  unloadable
120 1 Haru Iida
121 1 Haru Iida
  def index
122 1 Haru Iida
    @polls = Poll.find(:all)
123 1 Haru Iida
  end
124 1 Haru Iida
125 1 Haru Iida
  def vote
126 1 Haru Iida
    poll = Poll.find(params[:id])
127 1 Haru Iida
    poll.vote(params[:answer])
128 1 Haru Iida
    if poll.save
129 1 Haru Iida
      flash[:notice] = 'Vote saved.'
130 1 Haru Iida
      redirect_to :action => 'index'
131 1 Haru Iida
    end
132 1 Haru Iida
  end
133 1 Haru Iida
end
134 1 Haru Iida
</code></pre>
135 1 Haru Iida
136 1 Haru Iida
Then edit @vendor/plugins/redmine_polls/app/views/polls/index.html.erb@ that will display existing polls:
137 1 Haru Iida
138 1 Haru Iida
139 1 Haru Iida
<pre>
140 1 Haru Iida
<h2>Polls</h2>
141 1 Haru Iida
142 1 Haru Iida
<% @polls.each do |poll| %>
143 1 Haru Iida
  <p>
144 1 Haru Iida
  <%= poll[:question] %>?
145 1 Haru Iida
  <%= link_to 'Yes', {:action => 'vote', :id => poll[:id], :answer => 'yes'}, :method => :post %> (<%= poll[:yes] %>) /
146 1 Haru Iida
  <%= link_to 'No', {:action => 'vote', :id => poll[:id], :answer => 'no'}, :method => :post %> (<%= poll[:no] %>)
147 1 Haru Iida
  </p>
148 1 Haru Iida
<% end %>
149 1 Haru Iida
</pre>
150 1 Haru Iida
151 1 Haru Iida
You can remove @vendor/plugins/redmine_polls/app/views/polls/vote.html.erb@ since no rendering is done by the corresponding action.
152 1 Haru Iida
153 1 Haru Iida
Now, restart the application and point your browser to http://localhost:3000/polls.
154 1 Haru Iida
You should see the 2 polls and you should be able to vote for them:
155 1 Haru Iida
156 1 Haru Iida
p=. !pools1.png!
157 1 Haru Iida
158 1 Haru Iida
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.
159 1 Haru Iida
160 1 Haru Iida
h2. Extending menus
161 1 Haru Iida
162 1 Haru Iida
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.
163 1 Haru Iida
So let's add a new item to the application menu.
164 1 Haru Iida
165 1 Haru Iida
h3. Extending the application menu
166 1 Haru Iida
167 1 Haru Iida
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:
168 1 Haru Iida
169 1 Haru Iida
<pre><code class="ruby">
170 1 Haru Iida
Redmine::Plugin.register :redmine_polls do
171 1 Haru Iida
  [...]
172 1 Haru Iida
  
173 1 Haru Iida
  menu :application_menu, :polls, { :controller => 'polls', :action => 'index' }, :caption => 'Polls'
174 1 Haru Iida
end
175 1 Haru Iida
</code></pre>
176 1 Haru Iida
177 1 Haru Iida
Syntax is:
178 1 Haru Iida
179 1 Haru Iida
  menu(menu_name, item_name, url, options={})
180 1 Haru Iida
181 1 Haru Iida
There are 4 menus that you can extend:
182 1 Haru Iida
183 1 Haru Iida
* @:top_menu@ - the top left menu
184 1 Haru Iida
* @:account_menu@ - the top right menu with sign in/sign out links
185 1 Haru Iida
* @:application_menu@ - the main menu displayed when the user is not inside a project
186 1 Haru Iida
* @:project_menu@ - the main menu displayed when the user is inside a project
187 1 Haru Iida
188 1 Haru Iida
Available options are:
189 1 Haru Iida
190 1 Haru Iida
* @:param@ - the parameter key that is used for the project id (default is @:id@)
191 1 Haru Iida
* @:if@ - a Proc that is called before rendering the item, the item is displayed only if it returns true
192 1 Haru Iida
* @:caption@ - the menu caption that can be:
193 1 Haru Iida
194 1 Haru Iida
  * a localized string Symbol
195 1 Haru Iida
  * a String
196 1 Haru Iida
  * a Proc that can take the project as argument
197 1 Haru Iida
198 1 Haru Iida
* @:before@, @:after@ - specify where the menu item should be inserted (eg. @:after => :activity@)
199 1 Haru Iida
* @:last@ - if set to true, the item will stay at the end of the menu (eg. @:last => true@)
200 1 Haru Iida
* @:html_options@ - a hash of html options that are passed to @link_to@ when rendering the menu item
201 1 Haru Iida
202 1 Haru Iida
In our example, we've added an item to the application menu which is emtpy by default.
203 1 Haru Iida
Restart the application and go to http://localhost:3000:
204 1 Haru Iida
205 1 Haru Iida
p=. !application_menu.png!
206 1 Haru Iida
207 1 Haru Iida
Now you can access the polls by clicking the Polls tab from the welcome screen.
208 1 Haru Iida
209 1 Haru Iida
h3. Extending the project menu
210 1 Haru Iida
211 1 Haru Iida
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.
212 1 Haru Iida
Open @init.rb@ and replace the line that was added just before with these 2 lines:
213 1 Haru Iida
214 1 Haru Iida
<pre><code class="ruby">
215 1 Haru Iida
Redmine::Plugin.register :redmine_polls do
216 1 Haru Iida
  [...]
217 1 Haru Iida
218 1 Haru Iida
  permission :polls, {:polls => [:index, :vote]}, :public => true
219 1 Haru Iida
  menu :project_menu, :polls, { :controller => 'polls', :action => 'index' }, :caption => 'Polls', :after => :activity, :param => :project_id
220 1 Haru Iida
end
221 1 Haru Iida
</code></pre>
222 1 Haru Iida
223 1 Haru Iida
The second line adds our Polls tab to the project menu, just after the activity tab.
224 1 Haru Iida
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.
225 1 Haru Iida
226 1 Haru Iida
Restart the application again and go to one of your projects:
227 1 Haru Iida
228 1 Haru Iida
p=. !project_menu.png!
229 1 Haru Iida
230 1 Haru Iida
If you click the Polls tab, you should notice that the project menu is no longer displayed.
231 1 Haru Iida
To make the project menu visible, you have to initialize the controller's instance variable @@project@.
232 1 Haru Iida
233 1 Haru Iida
Edit your PollsController to do so:
234 1 Haru Iida
235 1 Haru Iida
<pre><code class="ruby">
236 1 Haru Iida
def index
237 1 Haru Iida
  @project = Project.find(params[:project_id])
238 1 Haru Iida
  @polls = Poll.find(:all) # @project.polls
239 1 Haru Iida
end
240 1 Haru Iida
</code></pre>
241 1 Haru Iida
242 1 Haru Iida
The project id is available in the @:project_id@ param because of the @:param => :project_id@ option in the menu item declaration above.
243 1 Haru Iida
244 1 Haru Iida
Now, you should see the project menu when viewing the polls:
245 1 Haru Iida
246 1 Haru Iida
p=. !project_menu_pools.png!
247 1 Haru Iida
248 1 Haru Iida
h2. Adding new permissions
249 1 Haru Iida
250 1 Haru Iida
For now, anyone can vote for polls. Let's make it more configurable by changing the permission declaration.
251 1 Haru Iida
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).
252 1 Haru Iida
253 1 Haru Iida
Edit @vendor/plugins/redmine_polls/init.rb@ to replace the previous permission declaration with these 2 lines:
254 1 Haru Iida
255 1 Haru Iida
<pre><code class="ruby">
256 1 Haru Iida
257 1 Haru Iida
  permission :view_polls, :polls => :index
258 1 Haru Iida
  permission :vote_polls, :polls => :vote
259 1 Haru Iida
</code></pre>
260 1 Haru Iida
261 1 Haru Iida
262 1 Haru Iida
Restart the application and go to http://localhost:3000/roles/report:
263 1 Haru Iida
264 1 Haru Iida
p=. !permissions1.png!
265 1 Haru Iida
266 1 Haru Iida
You're now able to give these permissions to your existing roles.
267 1 Haru Iida
268 1 Haru Iida
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.
269 1 Haru Iida
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.
270 1 Haru Iida
271 1 Haru Iida
Here is how it would look like for the @#index@ action:
272 1 Haru Iida
273 1 Haru Iida
<pre><code class="ruby">
274 1 Haru Iida
class PollsController < ApplicationController
275 1 Haru Iida
  unloadable
276 1 Haru Iida
  
277 1 Haru Iida
  before_filter :find_project, :authorize, :only => :index
278 1 Haru Iida
279 1 Haru Iida
  [...]
280 1 Haru Iida
  
281 1 Haru Iida
  def index
282 1 Haru Iida
    @polls = Poll.find(:all) # @project.polls
283 1 Haru Iida
  end
284 1 Haru Iida
285 1 Haru Iida
  [...]
286 1 Haru Iida
  
287 1 Haru Iida
  private
288 1 Haru Iida
  
289 1 Haru Iida
  def find_project
290 1 Haru Iida
    # @project variable must be set before calling the authorize filter
291 1 Haru Iida
    @project = Project.find(params[:project_id])
292 1 Haru Iida
  end
293 1 Haru Iida
end
294 1 Haru Iida
</code></pre>
295 1 Haru Iida
296 1 Haru Iida
Retrieving the current project before the @#vote@ action could be done using a similar way.
297 1 Haru Iida
After this, viewing and voting polls will be only available to admin users or users that have the appropriate role on the project.
298 1 Haru Iida
299 1 Haru Iida
h2. Creating a project module
300 1 Haru Iida
301 1 Haru Iida
For now, the poll functionality is added to all your projects. But you way want to enable polls for some projects only.
302 1 Haru Iida
So, let's create a 'Polls' project module. This is done by wrapping the permissions declaration inside a call to @#project_module@.
303 1 Haru Iida
304 1 Haru Iida
Edit @init.rb@ and change the permissions declaration:
305 1 Haru Iida
306 1 Haru Iida
<pre><code class="ruby">
307 1 Haru Iida
  project_module :polls do
308 1 Haru Iida
    permission :view_polls, :polls => :index
309 1 Haru Iida
    permission :vote_polls, :polls => :vote
310 1 Haru Iida
  end
311 1 Haru Iida
</code></pre>
312 1 Haru Iida
313 1 Haru Iida
Restart the application and go to one of your project settings.
314 1 Haru Iida
Click on the Modules tab. You should see the Polls module at the end of the modules list (disabled by default):
315 1 Haru Iida
316 1 Haru Iida
p=. !modules.png!
317 1 Haru Iida
318 1 Haru Iida
You can now enable/disable polls at project level.
319 1 Haru Iida
320 1 Haru Iida
h2. Improving the plugin views
321 1 Haru Iida
322 1 Haru Iida
h3. Adding stylesheets
323 1 Haru Iida
324 1 Haru Iida
Let's start by adding a stylesheet to our plugin views.
325 1 Haru Iida
Create a file named @voting.css@ in the @vendor/plugins/redmine_polls/assets/stylesheets@ directory:
326 1 Haru Iida
327 1 Haru Iida
<pre>
328 1 Haru Iida
a.vote { font-size: 120%; }
329 1 Haru Iida
a.vote.yes { color: green; }
330 1 Haru Iida
a.vote.no  { color: red; }
331 1 Haru Iida
</pre>
332 1 Haru Iida
333 1 Haru Iida
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.
334 1 Haru Iida
335 1 Haru Iida
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:
336 1 Haru Iida
337 1 Haru Iida
<pre>
338 1 Haru Iida
<% content_for :header_tags do %>
339 1 Haru Iida
    <%= stylesheet_link_tag 'voting', :plugin => 'redmine_polls' %>
340 1 Haru Iida
<% end %>
341 1 Haru Iida
</pre>
342 1 Haru Iida
343 1 Haru Iida
Note that the @:plugin => 'redmine_polls'@ option is required when calling the @stylesheet_link_tag@ helper.
344 1 Haru Iida
345 1 Haru Iida
Javascripts can be included in plugin views using the @javascript_include_tag@ helper in the same way.
346 1 Haru Iida
347 1 Haru Iida
h3. Setting page title
348 1 Haru Iida
349 1 Haru Iida
You can set the HTML title from inside your views by using the @html_title@ helper.
350 1 Haru Iida
Example:
351 1 Haru Iida
352 1 Haru Iida
  <% html_title "Polls" -%>