| CODENOTIFIER | HelpYou are not signed inSign in |
Project: Redmine
Revision: 1786
Author: jplang
Date: 05 Sep 2008 06:31:06
Changes:Merged hooks branch @ r1785 into trunk.
Files:| ... | ...@@ -53,6 +53,7 @@ | |
| 53 | 53 | <%end |
| 54 | 54 | end %> |
| 55 | 55 | </tr> |
| 56 | <%= call_hook(:view_issues_show_details_bottom, :issue => @issue) %> | |
| 56 | 57 | </table> |
| 57 | 58 | <hr /> |
| 58 | 59 |
| ... | ...@@ -0,0 +1,2 @@ | |
| 1 | module <%= class_name %>Helper | |
| 2 | end |
| ... | ...@@ -0,0 +1 @@ | |
| 0 | <h2><%= class_name %>#<%= action %></h2> |
| ... | ...@@ -0,0 +1,11 @@ | |
| 1 | # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html | |
| 2 | one: | |
| 3 | id: 1 | |
| 4 | <% for attribute in attributes -%> | |
| 5 | <%= attribute.name %>: <%= attribute.default %> | |
| 6 | <% end -%> | |
| 7 | two: | |
| 8 | id: 2 | |
| 9 | <% for attribute in attributes -%> | |
| 10 | <%= attribute.name %>: <%= attribute.default %> | |
| 11 | <% end -%> |
| ... | ...@@ -0,0 +1,3 @@ | |
| 1 | = <%= file_name %> | |
| 2 | ||
| 3 | Description goes here |
| ... | ...@@ -6,7 +6,7 @@ | |
| 6 | 6 | <meta name="description" content="<%= Redmine::Info.app_name %>" /> |
| 7 | 7 | <meta name="keywords" content="issue,bug,tracker" /> |
| 8 | 8 | <%= stylesheet_link_tag 'application', :media => 'all' %> |
| 9 | <%= javascript_include_tag :defaults %> | |
| 9 | <%= javascript_include_tag :defaults, :cache => true %> | |
| 10 | 10 | <%= stylesheet_link_tag 'jstoolbar' %> |
| 11 | 11 | <!--[if IE]> |
| 12 | 12 | <style type="text/css"> |
| ... | ...@@ -14,8 +14,9 @@ | |
| 14 | 14 | body {behavior: url(<%= stylesheet_path "csshover.htc" %>);} |
| 15 | 15 | </style> |
| 16 | 16 | <![endif]--> |
| 17 | ||
| 18 | <!-- page specific tags --><%= yield :header_tags %> | |
| 17 | <%= call_hook :view_layouts_base_html_head %> | |
| 18 | <!-- page specific tags --> | |
| 19 | <%= yield :header_tags -%> | |
| 19 | 20 | </head> |
| 20 | 21 | <body> |
| 21 | 22 | <div id="wrapper"> |
| ... | ...@@ -0,0 +1,10 @@ | |
| 1 | require File.dirname(__FILE__) + '/../test_helper' | |
| 2 | ||
| 3 | class <%= class_name %>Test < Test::Unit::TestCase | |
| 4 | fixtures :<%= table_name %> | |
| 5 | ||
| 6 | # Replace this with your real tests. | |
| 7 | def test_truth | |
| 8 | assert true | |
| 9 | end | |
| 10 | end |
| ... | ...@@ -316,4 +316,23 @@ | |
| 316 | 316 | end |
| 317 | 317 | end |
| 318 | 318 | end |
| 319 | ||
| 320 | # A hook that is manually registered later | |
| 321 | class ProjectBasedTemplate < Redmine::Hook::ViewListener | |
| 322 | def view_layouts_base_html_head(context) | |
| 323 | # Adds a project stylesheet | |
| 324 | stylesheet_link_tag(context[:project].identifier) if context[:project] | |
| 325 | end | |
| 326 | end | |
| 327 | # Don't use this hook now | |
| 328 | Redmine::Hook.clear_listeners | |
| 329 | ||
| 330 | def test_hook_response | |
| 331 | Redmine::Hook.add_listener(ProjectBasedTemplate) | |
| 332 | get :show, :id => 1 | |
| 333 | assert_tag :tag => 'link', :attributes => {:href => '/stylesheets/ecookbook.css'}, | |
| 334 | :parent => {:tag => 'head'} | |
| 335 | ||
| 336 | Redmine::Hook.clear_listeners | |
| 337 | end | |
| 319 | 338 | end |
| ... | ...@@ -48,4 +48,6 @@ | |
| 48 | 48 | <p><label><%=l(:label_attachment_plural)%></label><%= render :partial => 'attachments/form' %></p> |
| 49 | 49 | <% end %> |
| 50 | 50 | |
| 51 | <%= call_hook(:view_issues_form_details_bottom, { :issue => @issue, :form => f }) %> | |
| 52 | ||
| 51 | 53 | <%= wikitoolbar_for 'issue_description' %> |
| ... | ...@@ -0,0 +1,8 @@ | |
| 1 | require 'redmine' | |
| 2 | ||
| 3 | Redmine::Plugin.register :<%= plugin_name %> do | |
| 4 | name '<%= plugin_pretty_name %> plugin' | |
| 5 | author 'Author name' | |
| 6 | description 'This is a plugin for Redmine' | |
| 7 | version '0.0.1' | |
| 8 | end |
| ... | ...@@ -10,6 +10,7 @@ | |
| 10 | 10 | <th><%= l(:label_user) %></th> |
| 11 | 11 | <th><%= l(:label_role) %></th> |
| 12 | 12 | <th style="width:15%"></th> |
| 13 | <%= call_hook(:view_projects_settings_members_table_header) %> | |
| 13 | 14 | </thead> |
| 14 | 15 | <tbody> |
| 15 | 16 | <% members.each do |member| %> |
| ... | ...@@ -30,6 +31,7 @@ | |
| 30 | 31 | }, :title => l(:button_delete), |
| 31 | 32 | :class => 'icon icon-del' %> |
| 32 | 33 | </td> |
| 34 | <%= call_hook(:view_projects_settings_members_table_row, :member => @member) %> | |
| 33 | 35 | </tr> |
| 34 | 36 | </tbody> |
| 35 | 37 | <% end; reset_cycle %> |
| ... | ...@@ -0,0 +1,8 @@ | |
| 1 | require File.dirname(__FILE__) + '/../test_helper' | |
| 2 | ||
| 3 | class <%= class_name %>ControllerTest < ActionController::TestCase | |
| 4 | # Replace this with your real tests. | |
| 5 | def test_truth | |
| 6 | assert true | |
| 7 | end | |
| 8 | end |
| ... | ...@@ -0,0 +1,18 @@ | |
| 1 | Description: | |
| 2 | The plugin generator creates stubs for a new Redmine plugin. | |
| 3 | ||
| 4 | Example: | |
| 5 | ./script/generate redmine_plugin meetings | |
| 6 | create vendor/plugins/redmine_meetings/app/controllers | |
| 7 | create vendor/plugins/redmine_meetings/app/helpers | |
| 8 | create vendor/plugins/redmine_meetings/app/models | |
| 9 | create vendor/plugins/redmine_meetings/app/views | |
| 10 | create vendor/plugins/redmine_meetings/db/migrate | |
| 11 | create vendor/plugins/redmine_meetings/lib/tasks | |
| 12 | create vendor/plugins/redmine_meetings/assets/images | |
| 13 | create vendor/plugins/redmine_meetings/assets/javascripts | |
| 14 | create vendor/plugins/redmine_meetings/assets/stylesheets | |
| 15 | create vendor/plugins/redmine_meetings/lang | |
| 16 | create vendor/plugins/redmine_meetings/README | |
| 17 | create vendor/plugins/redmine_meetings/init.rb | |
| 18 | create vendor/plugins/redmine_meetings/lang/en.yml |
| ... | ...@@ -0,0 +1,5 @@ | |
| 1 | Description: | |
| 2 | Generates a plugin controller. | |
| 3 | ||
| 4 | Example: | |
| 5 | ./script/generate redmine_plugin_controller MyPlugin Pools index show vote |
| ... | ...@@ -0,0 +1,83 @@ | |
| 1 | # redMine - project management software | |
| 2 | # Copyright (C) 2006-2008 Jean-Philippe Lang | |
| 3 | # | |
| 4 | # This program is free software; you can redistribute it and/or | |
| 5 | # modify it under the terms of the GNU General Public License | |
| 6 | # as published by the Free Software Foundation; either version 2 | |
| 7 | # of the License, or (at your option) any later version. | |
| 8 | # | |
| 9 | # This program is distributed in the hope that it will be useful, | |
| 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 12 | # GNU General Public License for more details. | |
| 13 | # | |
| 14 | # You should have received a copy of the GNU General Public License | |
| 15 | # along with this program; if not, write to the Free Software | |
| 16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
| 17 | ||
| 18 | require File.dirname(__FILE__) + '/../../../test_helper' | |
| 19 | ||
| 20 | class Redmine::Hook::ManagerTest < Test::Unit::TestCase | |
| 21 | ||
| 22 | # Some hooks that are manually registered in these tests | |
| 23 | class TestHook < Redmine::Hook::Listener; end | |
| 24 | ||
| 25 | class TestHook1 < TestHook | |
| 26 | def view_layouts_base_html_head(context) | |
| 27 | 'Test hook 1 listener.' | |
| 28 | end | |
| 29 | end | |
| 30 | ||
| 31 | class TestHook2 < TestHook | |
| 32 | def view_layouts_base_html_head(context) | |
| 33 | 'Test hook 2 listener.' | |
| 34 | end | |
| 35 | end | |
| 36 | ||
| 37 | class TestHook3 < TestHook | |
| 38 | def view_layouts_base_html_head(context) | |
| 39 | "Context keys: #{context.keys.collect(&:to_s).sort.join(', ')}." | |
| 40 | end | |
| 41 | end | |
| 42 | Redmine::Hook.clear_listeners | |
| 43 | ||
| 44 | def setup | |
| 45 | @hook_module = Redmine::Hook | |
| 46 | end | |
| 47 | ||
| 48 | def teardown | |
| 49 | @hook_module.clear_listeners | |
| 50 | end | |
| 51 | ||
| 52 | def test_clear_listeners | |
| 53 | assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size | |
| 54 | @hook_module.add_listener(TestHook1) | |
| 55 | @hook_module.add_listener(TestHook2) | |
| 56 | assert_equal 2, @hook_module.hook_listeners(:view_layouts_base_html_head).size | |
| 57 | ||
| 58 | @hook_module.clear_listeners | |
| 59 | assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size | |
| 60 | end | |
| 61 | ||
| 62 | def test_add_listener | |
| 63 | assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size | |
| 64 | @hook_module.add_listener(TestHook1) | |
| 65 | assert_equal 1, @hook_module.hook_listeners(:view_layouts_base_html_head).size | |
| 66 | end | |
| 67 | ||
| 68 | def test_call_hook | |
| 69 | @hook_module.add_listener(TestHook1) | |
| 70 | assert_equal 'Test hook 1 listener.', @hook_module.call_hook(:view_layouts_base_html_head) | |
| 71 | end | |
| 72 | ||
| 73 | def test_call_hook_with_context | |
| 74 | @hook_module.add_listener(TestHook3) | |
| 75 | assert_equal 'Context keys: bar, foo.', @hook_module.call_hook(:view_layouts_base_html_head, :foo => 1, :bar => 'a') | |
| 76 | end | |
| 77 | ||
| 78 | def test_call_hook_with_multiple_listeners | |
| 79 | @hook_module.add_listener(TestHook1) | |
| 80 | @hook_module.add_listener(TestHook2) | |
| 81 | assert_equal 'Test hook 1 listener.Test hook 2 listener.', @hook_module.call_hook(:view_layouts_base_html_head) | |
| 82 | end | |
| 83 | end |
| ... | ...@@ -0,0 +1,18 @@ | |
| 1 | require 'rails_generator/base' | |
| 2 | require 'rails_generator/generators/components/model/model_generator' | |
| 3 | ||
| 4 | class RedminePluginModelGenerator < ModelGenerator | |
| 5 | attr_accessor :plugin_path, :plugin_name, :plugin_pretty_name | |
| 6 | ||
| 7 | def initialize(runtime_args, runtime_options = {}) | |
| 8 | runtime_args = runtime_args.dup | |
| 9 | @plugin_name = "redmine_" + runtime_args.shift.underscore | |
| 10 | @plugin_pretty_name = plugin_name.titleize | |
| 11 | @plugin_path = "vendor/plugins/#{plugin_name}" | |
| 12 | super(runtime_args, runtime_options) | |
| 13 | end | |
| 14 | ||
| 15 | def destination_root | |
| 16 | File.join(RAILS_ROOT, plugin_path) | |
| 17 | end | |
| 18 | end |
| ... | ...@@ -4,6 +4,7 @@ | |
| 4 | 4 | require 'redmine/mime_type' |
| 5 | 5 | require 'redmine/core_ext' |
| 6 | 6 | require 'redmine/themes' |
| 7 | require 'redmine/hook' | |
| 7 | 8 | require 'redmine/plugin' |
| 8 | 9 | |
| 9 | 10 | begin |
| ... | ...@@ -0,0 +1,109 @@ | |
| 1 | # Redmine - project management software | |
| 2 | # Copyright (C) 2006-2008 Jean-Philippe Lang | |
| 3 | # | |
| 4 | # This program is free software; you can redistribute it and/or | |
| 5 | # modify it under the terms of the GNU General Public License | |
| 6 | # as published by the Free Software Foundation; either version 2 | |
| 7 | # of the License, or (at your option) any later version. | |
| 8 | # | |
| 9 | # This program is distributed in the hope that it will be useful, | |
| 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 12 | # GNU General Public License for more details. | |
| 13 | # | |
| 14 | # You should have received a copy of the GNU General Public License | |
| 15 | # along with this program; if not, write to the Free Software | |
| 16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
| 17 | ||
| 18 | module Redmine | |
| 19 | module Hook | |
| 20 | @@listener_classes = [] | |
| 21 | @@listeners = nil | |
| 22 | @@hook_listeners = {} | |
| 23 | ||
| 24 | class << self | |
| 25 | # Adds a listener class. | |
| 26 | # Automatically called when a class inherits from Redmine::Hook::Listener. | |
| 27 | def add_listener(klass) | |
| 28 | raise "Hooks must include Singleton module." unless klass.included_modules.include?(Singleton) | |
| 29 | @@listener_classes << klass | |
| 30 | clear_listeners_instances | |
| 31 | end | |
| 32 | ||
| 33 | # Returns all the listerners instances. | |
| 34 | def listeners | |
| 35 | @@listeners ||= @@listener_classes.collect {|listener| listener.instance} | |
| 36 | end | |
| 37 | ||
| 38 | # Returns the listeners instances for the given hook. | |
| 39 | def hook_listeners(hook) | |
| 40 | @@hook_listeners[hook] ||= listeners.select {|listener| listener.respond_to?(hook)} | |
| 41 | end | |
| 42 | ||
| 43 | # Clears all the listeners. | |
| 44 | def clear_listeners | |
| 45 | @@listener_classes = [] | |
| 46 | clear_listeners_instances | |
| 47 | end | |
| 48 | ||
| 49 | # Clears all the listeners instances. | |
| 50 | def clear_listeners_instances | |
| 51 | @@listeners = nil | |
| 52 | @@hook_listeners = {} | |
| 53 | end | |
| 54 | ||
| 55 | # Calls a hook. | |
| 56 | # Returns the listeners response. | |
| 57 | def call_hook(hook, context={}) | |
| 58 | response = '' | |
| 59 | hook_listeners(hook).each do |listener| | |
| 60 | response << listener.send(hook, context).to_s | |
| 61 | end | |
| 62 | response | |
| 63 | end | |
| 64 | end | |
| 65 | ||
| 66 | # Base class for hook listeners. | |
| 67 | class Listener | |
| 68 | include Singleton | |
| 69 | ||
| 70 | # Registers the listener | |
| 71 | def self.inherited(child) | |
| 72 | Redmine::Hook.add_listener(child) | |
| 73 | super | |
| 74 | end | |
| 75 | end | |
| 76 | ||
| 77 | # Listener class used for views hooks. | |
| 78 | # Listeners that inherit this class will include various helpers by default. | |
| 79 | class ViewListener < Listener | |
| 80 | include ERB::Util | |
| 81 | include ActionView::Helpers::TagHelper | |
| 82 | include ActionView::Helpers::FormHelper | |
| 83 | include ActionView::Helpers::FormTagHelper | |
| 84 | include ActionView::Helpers::FormOptionsHelper | |
| 85 | include ActionView::Helpers::JavaScriptHelper | |
| 86 | include ActionView::Helpers::PrototypeHelper | |
| 87 | include ActionView::Helpers::NumberHelper | |
| 88 | include ActionView::Helpers::UrlHelper | |
| 89 | include ActionView::Helpers::AssetTagHelper | |
| 90 | include ActionView::Helpers::TextHelper | |
| 91 | include ActionController::UrlWriter | |
| 92 | include ApplicationHelper | |
| 93 | end | |
| 94 | ||
| 95 | # Helper module included in ApplicationHelper so that hooks can be called | |
| 96 | # in views like this: | |
| 97 | # <%= call_hook(:some_hook) %> | |
| 98 | # <%= call_hook(:another_hook, :foo => 'bar' %> | |
| 99 | # | |
| 100 | # Current project is automatically added to the call context. | |
| 101 | module Helper | |
| 102 | def call_hook(hook, context={}) | |
| 103 | Redmine::Hook.call_hook(hook, {:project => @project}.merge(context)) | |
| 104 | end | |
| 105 | end | |
| 106 | end | |
| 107 | end | |
| 108 | ||
| 109 | ApplicationHelper.send(:include, Redmine::Hook::Helper) |
| ... | ...@@ -0,0 +1,38 @@ | |
| 1 | require 'source_annotation_extractor' | |
| 2 | ||
| 3 | # Modified version of the SourceAnnotationExtractor in railties | |
| 4 | # Will search for runable code that uses <tt>call_hook</tt> | |
| 5 | class PluginSourceAnnotationExtractor < SourceAnnotationExtractor | |
| 6 | # Returns a hash that maps filenames under +dir+ (recursively) to arrays | |
| 7 | # with their annotations. Only files with annotations are included, and only | |
| 8 | # those with extension +.builder+, +.rb+, +.rxml+, +.rjs+, +.rhtml+, and +.erb+ | |
| 9 | # are taken into account. | |
| 10 | def find_in(dir) | |
| 11 | results = {} | |
| 12 | ||
| 13 | Dir.glob("#{dir}/*") do |item| | |
| 14 | next if File.basename(item)[0] == ?. | |
| 15 | ||
| 16 | if File.directory?(item) | |
| 17 | results.update(find_in(item)) | |
| 18 | elsif item =~ /(hook|test)\.rb/ | |
| 19 | # skip | |
| 20 | elsif item =~ /\.(builder|(r(?:b|xml|js)))$/ | |
| 21 | results.update(extract_annotations_from(item, /\s*(#{tag})\(?\s*(.*)$/)) | |
| 22 | elsif item =~ /\.(rhtml|erb)$/ | |
| 23 | results.update(extract_annotations_from(item, /<%=\s*\s*(#{tag})\(?\s*(.*?)\s*%>/)) | |
| 24 | end | |
| 25 | end | |
| 26 | ||
| 27 | results | |
| 28 | end | |
| 29 | end | |
| 30 | ||
| 31 | namespace :redmine do | |
| 32 | namespace :plugins do | |
| 33 | desc "Enumerate all Redmine plugin hooks and their context parameters" | |
| 34 | task :hook_list do | |
| 35 | PluginSourceAnnotationExtractor.enumerate 'call_hook' | |
| 36 | end | |
| 37 | end | |
| 38 | end |
| ... | ...@@ -89,7 +89,8 @@ | |
| 89 | 89 | when 'attachment' |
| 90 | 90 | label = l(:label_attachment) |
| 91 | 91 | end |
| 92 | ||
| 92 | call_hook(:helper_issues_show_detail_after_setting, {:detail => detail, :label => label, :value => value, :old_value => old_value }) | |
| 93 | ||
| 93 | 94 | label ||= detail.prop_key |
| 94 | 95 | value ||= detail.value |
| 95 | 96 | old_value ||= detail.old_value |
| ... | ...@@ -0,0 +1,5 @@ | |
| 1 | # Load the normal Rails helper | |
| 2 | require File.expand_path(File.dirname(__FILE__) + '/../../../../test/test_helper') | |
| 3 | ||
| 4 | # Ensure that we are using the temporary fixture path | |
| 5 | Engines::Testing.set_fixture_path |
| ... | ...@@ -0,0 +1,18 @@ | |
| 1 | require 'rails_generator/base' | |
| 2 | require 'rails_generator/generators/components/controller/controller_generator' | |
| 3 | ||
| 4 | class RedminePluginControllerGenerator < ControllerGenerator | |
| 5 | attr_reader :plugin_path, :plugin_name, :plugin_pretty_name | |
| 6 | ||
| 7 | def initialize(runtime_args, runtime_options = {}) | |
| 8 | runtime_args = runtime_args.dup | |
| 9 | @plugin_name = "redmine_" + runtime_args.shift.underscore | |
| 10 | @plugin_pretty_name = plugin_name.titleize | |
| 11 | @plugin_path = "vendor/plugins/#{plugin_name}" | |
| 12 | super(runtime_args, runtime_options) | |
| 13 | end | |
| 14 | ||
| 15 | def destination_root | |
| 16 | File.join(RAILS_ROOT, plugin_path) | |
| 17 | end | |
| 18 | end |
| ... | ...@@ -0,0 +1,2 @@ | |
| 1 | class <%= class_name %> < ActiveRecord::Base | |
| 2 | end |
| ... | ...@@ -0,0 +1,5 @@ | |
| 1 | Description: | |
| 2 | Generates a plugin model. | |
| 3 | ||
| 4 | Examples: | |
| 5 | ./script/generate redmine_plugin_model MyPlugin pool title:string question:text |
| ... | ...@@ -233,6 +233,7 @@ | |
| 233 | 233 | issue.start_date = params[:start_date] unless params[:start_date].blank? |
| 234 | 234 | issue.due_date = params[:due_date] unless params[:due_date].blank? |
| 235 | 235 | issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank? |
| 236 | call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue }) | |
| 236 | 237 | # Don't save any change to the issue if the user is not authorized to apply the requested status |
| 237 | 238 | if (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save |
| 238 | 239 | # Send notification for each issue (if changed) |
| ... | ...@@ -0,0 +1,7 @@ | |
| 1 | class <%= class_name %>Controller < ApplicationController | |
| 2 | <% actions.each do |action| -%> | |
| 3 | ||
| 4 | def <%= action %> | |
| 5 | end | |
| 6 | <% end -%> | |
| 7 | end |
| ... | ...@@ -38,6 +38,7 @@ | |
| 38 | 38 | <label><%= l(:field_done_ratio) %>: |
| 39 | 39 | <%= select_tag 'done_ratio', options_for_select([[l(:label_no_change_option), '']] + (0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></label> |
| 40 | 40 | </p> |
| 41 | <%= call_hook(:view_issues_bulk_edit_details_bottom, { :issues => @issues }) %> | |
| 41 | 42 | </fieldset> |
| 42 | 43 | |
| 43 | 44 | <fieldset><legend><%= l(:field_notes) %></legend> |
| ... | ...@@ -0,0 +1,2 @@ | |
| 1 | # English strings go here | |
| 2 | my_label: "My label" |
| ... | ...@@ -0,0 +1,13 @@ | |
| 1 | class <%= migration_name %> < ActiveRecord::Migration | |
| 2 | def self.up | |
| 3 | create_table :<%= table_name %> do |t| | |
| 4 | <% for attribute in attributes -%> | |
| 5 | t.column :<%= attribute.name %>, :<%= attribute.type %> | |
| 6 | <% end -%> | |
| 7 | end | |
| 8 | end | |
| 9 | ||
| 10 | def self.down | |
| 11 | drop_table :<%= table_name %> | |
| 12 | end | |
| 13 | end |
| ... | ...@@ -0,0 +1,31 @@ | |
| 1 | class RedminePluginGenerator < Rails::Generator::NamedBase | |
| 2 | attr_reader :plugin_path, :plugin_name, :plugin_pretty_name | |
| 3 | ||
| 4 | def initialize(runtime_args, runtime_options = {}) | |
| 5 | super | |
| 6 | @plugin_name = "redmine_#{file_name.underscore}" | |
| 7 | @plugin_pretty_name = plugin_name.titleize | |
| 8 | @plugin_path = "vendor/plugins/#{plugin_name}" | |
| 9 | end | |
| 10 | ||
| 11 | def manifest | |
| 12 | record do |m| | |
| 13 | m.directory "#{plugin_path}/app/controllers" | |
| 14 | m.directory "#{plugin_path}/app/helpers" | |
| 15 | m.directory "#{plugin_path}/app/models" | |
| 16 | m.directory "#{plugin_path}/app/views" | |
| 17 | m.directory "#{plugin_path}/db/migrate" | |
| 18 | m.directory "#{plugin_path}/lib/tasks" | |
| 19 | m.directory "#{plugin_path}/assets/images" | |
| 20 | m.directory "#{plugin_path}/assets/javascripts" | |
| 21 | m.directory "#{plugin_path}/assets/stylesheets" | |
| 22 | m.directory "#{plugin_path}/lang" | |
| 23 | m.directory "#{plugin_path}/test" | |
| 24 | ||
| 25 | m.template 'README', "#{plugin_path}/README" | |
| 26 | m.template 'init.rb', "#{plugin_path}/init.rb" | |
| 27 | m.template 'en.yml', "#{plugin_path}/lang/en.yml" | |
| 28 | m.template 'test_helper.rb', "#{plugin_path}/test/test_helper.rb" | |
| 29 | end | |
| 30 | end | |
| 31 | end |
| ... | ...@@ -45,4 +45,6 @@ | |
| 45 | 45 | <% end %> |
| 46 | 46 | </div> |
| 47 | 47 | |
| 48 | <%= call_hook :view_versions_show_bottom, :version => @version %> | |
| 49 | ||
| 48 | 50 | <% html_title @version.name %> |