basyura's blog

あしたになったらほんきだす。

redmine の hook が登録される流れ

チケットの誤爆登録


ちこちこやって幸せになれたと思ったら

タイトル部での Enter をキャンセルするプラグインがあった。更に幸せに慣れた。

そもそも redmine の plugin はどういうふうに登録されるのか

redmine_enter_cancel plugin のソースを覗いてみる

require 'enter_cancel_listener'

Redmine::Plugin.register :redmine_enter_cancel do
  name 'Redmine Enter Cancel plugin'
  author 'suer'
  description 'prevent unintended ticket creation by enter key'
  version '0.0.2'
  url 'https://github.com/suer/redmine_enter_cancel'
  author_url 'http://d.hatena.ne.jp/suer'
end
class EnterCancelListener < Redmine::Hook::ViewListener

  def view_issues_form_details_bottom(context)
<<SCRIPT
<script type="text/javascript">
$(document).ready(function() {
    $("#issue_subject").keypress(function(event) {
      if ((event.which && event.which === 13) || (event.keyCode && event.keyCode === 13)) {
        return false;
      } else {
        return true;
      }
    });
  });
  </script>
  SCRIPT
end

plugin 登録用と本体のとてもシンプルな構成。シンプルだから真似してこう書けばいいのだなというのは雰囲気は分かるのだけど、なぜこう書いたらプラグインとして登録されるのかが分からん・・・。

しらべてみる

redmine のソースを覗いてみる。多少迷子になりながら。

  • redmine/lib/redmine/hook.rb
module Redmine
  module Hook
    class Listener
      include Singleton
      include Redmine::I18n

      # Registers the listener
      def self.inherited(child)
        Redmine::Hook.add_listener(child)
        super
      end
    end
    class ViewListener < Listener
      # 省略
    end
  end
end

今回のように View に関するフックを作りたい場合は、Redmine::Hook::ViewListener を継承したクラスを作ることになる。ViewListener は Listener を継承していてるので継承したクラスが定義されたタイミングで Listener.inherited メソッドが呼ばれる。すると Listener.inherited の中で Redmine::Hook.add_lister を呼ぶことで hook に登録される。

よって、init.rb で lib にあるファイルを require する必要があり、require されることでクラス定義が読み込まれて Listener への登録が行われる。

疑問

でもこれだと、init.rb では require されてるだけだから Redmine::Plugin.register の内容となんのヒモ付けもないように見える。
検証のため、init.rb で requre 以外はコメントアウトしておく。

require 'enter_cancel_listener'

#Redmine::Plugin.register :redmine_enter_cancel do
#  name 'Redmine Enter Cancel plugin'
#  author 'suer'
#  description 'prevent unintended ticket creation by enter key'
#  version '0.0.2'
#  url 'https://github.com/suer/redmine_enter_cancel'
#  author_url 'http://d.hatena.ne.jp/suer'
#end

redmine を起動すると 問題なく plugin は動作する。が、管理画面にある plugin の一覧には表示されない。

よって、

  • Redmine::Hook::ViewListener を継承した plugin が listener に登録される
  • register した内容(名前) が管理画面の plugin 一覧に表示される

となる。register した名前と実際の Listener は明確に紐付いてないけど、紐付いてなくても困らない。

ということかな (?)

redmine で親プロジェクトのリビジョンにリンクをはる

親プロジェクトでリポジトリの設定をしている場合に、子プロジェクトで r100 と書いてもリビジョンへのリンクが貼られない。残念。その場合、parent_project_id:r100 とものすごく冗長な記法が必要になる。つらい。次プロジェクトにリポジトリが無い場合は親をたどればいいのにと思うのだけど、判定のレスポンスにもよるのかなと想像。

とはいえ、めんどくさいし見づらいしなのでパッチをあててみた。親プロジェクトの id が 1 の場合。

  • redmine/app/helpers/application_helper.rb
  def parse_redmine_links(text, default_project, obj, attr, only_path, options)
    text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-_]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m|
      leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17
      link = nil
      project = default_project
      if project_identifier
        project = Project.visible.find_by_identifier(project_identifier)
      end
      if esc.nil?
        if prefix.nil? && sep == 'r'
          if project
            repository = nil
            if repo_identifier
              repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
+           elsif project.id != 1
+             project = Project.find(1)
+             repository = project.repository
            else
              repository = project.repository
            end

割といいかんじ。

Redmine のチケット一覧のヘッダに class を追加する

プラグインを作ってみた。

チケットの一覧で名前の間に空白があると、空白で改行されて 2 行になっちゃって無駄に高さ取るよ ウワァァァァァァヽ(`Д´)ノァァァァァァン! となることしばしば。

f:id:basyura:20140328232140p:plain

ソースを直接いじってたんだけど、毎回いじるのもつらくなってきたので Redmineのプロジェクトの並びが気に入らないという話その2 Redmineプラグイン試作 - yunomuのブログ を参考に plugin 化してみた。

プラグインのひな形を作る

% bundle exec rails g redmine_plugin redmine_add_class_to_column
% cd plugins/redmine_add_class_to_column

沢山できるけど、必要なのは init.rb ぐらい。

.
├── README.rdoc
├── init.rb
└── lib
    └── redmine_add_class_to_column.rb

init.rb

QueriesHelper にあるメソッドを書き換えたいので include 時のフックを登録

require File.dirname(__FILE__) + '/lib/redmine_add_class_to_column'

ActionDispatch::Callbacks.to_prepare do
  require_dependency 'queries_helper'
  QueriesHelper.send(:include, Redmine::Plugins::RedmineAddClassToClumn)
end

Redmine::Plugin.register :redmine_add_class_to_column do
  name 'Redmine Add Class To Column plugin'
  author 'basyura'
  description 'add class to column'
  version '0.0.1'
  url ''
  author_url 'http://blog.basyura.org'
end

redmine_add_class_to_column.rb

class を出力するように書き換えた module を定義

module Redmine
  module Plugins
    module RedmineAddClassToClumn
      def self.included(base)
        base.send(:include, InstanceMethods)
        base.class_eval do
          alias_method_chain :column_header, :class
        end
      end

      module InstanceMethods
        def column_header_with_class(column)
          column.sortable ? 
             sort_header_tag(column.name.to_s, :caption => column.caption,
                                               :default_order => column.default_order,
                                               :class => "column_header_#{column.name.to_s}"
                                                ) :
             content_tag('th', h(column.caption),
                        :class => "column_header_#{column.name.to_s}")
        end
      end
    end
  end
end

起動

% bundle exec rails s

f:id:basyura:20140328231118p:plain

class がついたので、application.cs にセルに幅を定義する。

.column_header_assigned_to {
  min-width : 120px;
}

できましたよっと ヽ( ゚∀゚)ノ

f:id:basyura:20140328232836p:plain

( ・´ー・`)どや

apache + rails をサブディレクトリで動かしたい

http://localhost:3000 じゃなくて、http://localhost/sample で動かしたい。

httpd.conf

<VirtualHost *:80>
  ProxyPass /sample http://localhost:3000/sample
  ProxyPassReverse /sample http://localhost:3000/sample
</VirtualHost>

config/environments/development.rb

config.relative_url_root = "/sample"

起動

bundle exec thin start --prefix /sample

できたけど正解が分からない。

application.rbconfig.assets.prefix = "/sample/assets/" と定義すればいいのかと思ってたけど違ったみたい。こっちはどういう時に使うのかよく分からんかった。

git-scouter

コミットの変更度を出してみる を gem 化してみた (rubygems.org にはあげてない)。

使い方

今日の変更度を出力

$ git-scouter

全てのコミットを出力

$ git-scouter -a

ユーザ指定で出力 (user を正規表現でマッチングしてるだけ)

$ git-scouter -u user

日付指定で出力

$ git-scouter -d 20140301

年を省略した日付指定

$ git-scouter -d 0301

今日を指定

$ git-scouter -d today

同じく今日を指定

$ git-scouter -d 0

昨日を指定

$ git-scouter -d -1

直近の 5 コミットを指定

$ git-scouter -n 5

コミットを指定

$ git-scouter -c HEAD

$ git-scouter -c HEAD~

$ git-scouter -c c1b543b46ddfc87a2ab1f5088a33ff3634be94a6

昨日の僕のコミットを指定

$ git-scouter -u basyura -d -1

出力内容

  0 : 6bcf7d95c0289289794499b0959e20bfb91b750d - basyura  Sat Feb 15 22:26:08 2014 +0900
      Merge: 9478169 1f017a3
          Merge pull request #36 from ompugao/ignore_withheld_content
          ignore withheld content
 71 : 1f017a3d2ad61dd70f1265b50c4f4d3d8ba2b704 - Shohei Fujii  Sat Feb 15 16:12:26 2014 +0900
          ignore withheld content
 43 : 9478169f4213b9e59b37e416a925a783c36a6db5 - basyura  Sat Dec 7 17:17:02 2013 +0900
          echomsg if tweetvim filetype
930 : a5c80c116c2b660d81af75872c12b6313f48e903 - basyura  Sun Nov 17 22:28:48 2013 +0900
          added bang option to exract by track words
 86 : d33d0ff7c8fb64cfe63b526cdf030521dfe6fcda - basyura  Sun Nov 17 10:49:03 2013 +0900
          fixed reload userstream behavior with track
305 : 51acfa535fbb984889d0369cb476e3110db94846 - basyura  Mon Nov 4 17:26:37 2013 +0900
          defined <Plug>
  1 : 9bc7fef07a2741862f2080746d9a348210afdc0a - basyura  Mon Nov 4 09:49:27 2013 +0900

先頭の数字が変更度。

自分で使って統計的なものを出してみることにする (予定)。