Redmine のカスタムフィールド「リスト」をチェックリストとして使う

Redmine のカスタムフィールドには、リストを選択するための書式「リスト」があります。(そのまんま)

その書式で定義したカスタムフィールドを「チェックリスト」として使いたかったのですが、デフォルトの Redmine では使いにくかったので、プラグイン「View Customize Plugin」を使ってカスタマイズした話を書きます。

※これ以降、書式「リスト」のカスタムフィールドのことを 「リストフィールド」 と記載します。

前提

上記プラグインがあれば何でもできるので、ぜひ導入しましょう。

デフォルト Redmine を使ったチェックリストの問題点

デフォルト Redmine で、リストフィールドを利用したチェックリストを実装しようとすると、以下の問題に気が付きます。

  • チケット表示画面では、リストフィールドのチェックされている項目は見えるが、チェックされていない項目が見えない。

具体的に、以下のようなリストフィールドを作成し、チケット入力画面と表示画面を見比べてみます。

カスタムフィールド定義

チェックリスト項目「テスト用リストフィールド」を定義します。

f:id:rabitarochan:20160905184214p:plain

項番 説明
(1) 書式に「リスト」を指定します
(2) 複数選択可にチェックを付けます
(3) 選択肢に、チェックリストの項目を指定します
(4) 表示に「チェックボックス」を指定します

チケット入力画面

作成したリストフィールドは、入力画面では以下のとおり表示されます。

この画面だけだと、チェックリストのように使えるのではないかと思ってしまいます。

f:id:rabitarochan:20160905184219p:plain

チケット表示画面

問題のチケット表示画面です。

赤枠で囲まれている箇所がリストフィールドの表示箇所ですが、チェックした項目のみ表示されており全体が見えません。また、改行もされず読みにくいです。

f:id:rabitarochan:20160905184224p:plain

解決案

解決案としては、チェックボックスをそのまま表示するだけで格段に見やすくなると思います。

今回は、View Customize Plugin を使ってチケット表示画面に JavaScript を適用して解決しようと思います。

解決したスクリプト

早速答え合わせですが、以下の JavaScript を登録することで、カスタムフィールド名に「チェックリスト」が含まれている場合のみチェックリスト表示にしてくれます。

カスタムフィールド名に「チェックリスト」を含むことを条件とした理由は、リストフィールドの本来の使い方をしたい場合と差別化するためです。

Path pattern: /issues/[0-9]+

Type: JavaScript

// カスタムフィールド名に「チェックリスト」が含まれる場合、表示時にチェックボックスを表示する。
$(function () {

  // 処理対象のカスタムフィールドを抽出する。
  var $checklists = [];
  $('.attribute .label span').each(function () {
    var $this = $(this);
    if (!isChecklist($this)) return ;

    var $parent = $this.parent().parent();
    $checklists.push($parent);
  });

  // カスタムフィールドをチェックリスト形式で表示する。
  $.each($checklists, function (i, $this) {
    var id = getCfId($this);
    if (id === null) return;

    var $items = getChecklistItems(id);
    var newHtml = createChecklistHtml($items);

    $this.find('.value').html(newHtml);
  });

  // fun: 処理対象のカスタムフィールドかどうかを判定する
  function isChecklist($this) {
    var label = $this.text();
    return (label.indexOf('チェックリスト') >= 0);
  }

  // fun: カスタムフィールドの id を取得する
  function getCfId($this) {
    var id = $this.attr('class').match(/\d+/);
    if (id === null) return null;
    return id[0];
  }

  // fun: カスタムフィールドのすべての項目を取得します。
  function getChecklistItems(id) {
    var selector = '#issue_custom_field_values_' + id;
    var $items = $(selector).parent().parent().find('label');
    return $items;
  }

  // fun: 表示用の HTML 要素を作成します。
  function createChecklistHtml($items) {
    var html = [];
    $items.each(function () {
      var $this = $(this);
      var $input = $this.find('input');
      var result = '<input type="checkbox" value="test" disabled="true" /> ';

      if ($input.attr('checked')) {
        result = '<input type="checkbox" value="test" checked="checked" disabled="true" /> ';
      }

      result += $input.attr('value');
      html.push(result);
    });

    var newHtml = html.join('<br />')
    return newHtml;
  }

});

適用後の画面

圧倒的見やすさ!

チェックしていない項目も表示されていて、チェック項目の見た目も入力画面と統一できました。これならチェックリストとして使えそうです。

f:id:rabitarochan:20160905185815p:plain

まとめ

Redmine のカスタムフィールドを使ってチェックリストを実現する方法についてまとめました。

ほぼ View Customize Plugin のサンプルみたいな記事ですが、とても便利ですし Redmine魔改造しやすいのでとてもおすすめです。 (時間があれば、カスタムフィールドをグループ化 / 並べ替えするスクリプトも作っているので紹介しようと思います。)

みなさんも便利なスクリプトがあればぜひ教えてください!

Kotlinで特定のブロック内でのみ有効な拡張関数を定義する

Kotlinのドキュメントに、Type-Safe Buildersという記事があります。 これは、Groovyでよく使われている builders パターンというものを、Kotlinで表現した際にどうかるかを示したドキュメントです。

kotlinlang.org

Groovy使ったことないですが、build.gradleの定義にも使われているこういう書き方を指します。

buildscript {

    ext {
        
        kotlinVersion = '1.0.0'

    }

}

要は、独自のブロックを定義して、その中で初期化処理を実行しましょうということです。 Kotlinのブロックについては、親子関係を持つ独自のブロックを作るという内容で「逆引きKotlin」に追加されています。

親子関係のある独自のブロックを作りたい - 逆引きKotlin - 逆引きKotlin

今回は、この機能を利用した「特定のブロック内でのみ有効な拡張関数」を定義する方法について紹介したいと思います。

元ネタはこちらです。

discuss.kotlinlang.org

やりたいこと

例として、ブロックを抜けたら指定したリソースを開放するブロックusingを定義します。

開放するリソースを指定する拡張関数autoClose()を、usingブロック内でのみ有効化します。

using {

    val in = Files.newInputStream("in.txt").autoClose()
    val out = Files.newOutputStream("out.txt").autoClose()

    // ごにょごにょ

}

定義

ポイントは、関数のレシーバーに指定したクラス内で拡張関数を定義することです。 こうすることで、クラス内でのみ拡張関数が有効となります。

usingに渡されたブロックは、レシーバーを指定したことによりレシーバー内のスコープとなり、拡張関数が有効となる仕組みです。

// usingブロック
fun <R> using(block: ResourceHolder.() -> R): R {
    ResourceHolder().use {
        return it.block()
    }
}

// レシーバークラス
class ResourceHolder : Closeable {

    val resources = mutableListOf<Any>()

    // Closeable インターフェースを実装したクラスに対する拡張関数
    fun <T: Closeable> T.autoClose(): T {
        resources.add(this)
        return this
    } 

    // AutoCloseable インターフェースを実装したクラスに対する拡張関数
    fun <T: AutoCloseable> T.autoClose(): T {
        resources.add(this)
        return this
    } 

    override fun close() {
        resources.reverses().forEach {
            when (it) {
                is Closeable -> it.close()
                is AutoCloseable -> it.close()
            }
        }
    }

}

使い方

使い方は「やりたいこと」に書いたとおりです。

定義した拡張関数 autoClose() は、usingブロック以外からは呼び出すことが出来ず、コンパイルエラーとなります。

// テスト用のクラス
class TestCloseable : Closeable {
    override fun close() {
        println("TestCloseable")
    }
}
class TestAutoCloseable : AutoCloseable {
    override fun close() {
        println("TestAutoCloseable")
    }
}

@Test
fun usingTest() {

    TestCloseable().autoClose() // コンパイルエラー!

    using {

        TestCloseable().autoClose()
        TestAutoCloseable().autoClose()
        TestCloseable().autoClose()
        TestAutoCloseable().autoClose()
        TestCloseable().autoClose()
        TestCloseable().autoClose()
        TestAutoCloseable().autoClose()

    }

    // 出力
    // TestAutoCloseable
    // TestCloseable
    // TestCloseable
    // TestAutoCloseable
    // TestCloseable
    // TestAutoCloseable
    // TestCloseable

}

まとめ

特定のブロック内でのみ有効な拡張関数を定義する方法について紹介しました。

上記のようなリソース開放の他、トランザクションブロック内ではStringを直接SQLとして実行できるようにする、という使い方もできます。

拡張関数は便利だけど、使わせる範囲を明示的に指定したい場合に使ってみてください。

Kotlinで未実装を表す方法

小ネタです。

Scalaでは ??? という関数を実行することで、未実装を表す scala.NotImplementedError を返すことができます。

Kotlinにも例外 kotlin.NotImplementedError があり、それを呼び出す関数として TODO() が定義されています。

github.com

使い方

メソッドに対して、コンパイルを通しつつ未実装を表す場合は、以下のように書きます。 引数に文字列を指定することで、例外にコメントを追加することも可能です。

fun hoge(s: String): String = TODO()

// 未実装コメントを追加することも可能。
fun fuga(s: String): String = TODO("未実装ですよ")

また、inline修飾子が付いているため、メソッドの途中に書くことでIntellijの場合はそれ以降の処理が実行されないという警告が表示されます。

fun foo(s: String): String = {

  println("before: $s")

  TODO()

  println("after: $s")

}

f:id:rabitarochan:20160225082936p:plain

まとめ

未実装を表す kotlin.NotImplementedErrorTODO() について紹介しました。

TODO() のように、例外のみを返すメソッドをインライン展開させるという書き方も便利なので、ぜひ使ってみてください。

SpringBoot + Kotlin + Devtools を IntelliJ IDEA で上手く動かす

Kotlin で Web 開発をするために、とりあえず今は Spring Boot を使っています。

Eclipse の HTML, JavaScript エディタが自分に合わない(合うプラグインを見つけられなかった)ため、IntelliJ IDEA を使おうと思って設定を始めたのですが、上手く動かす方法を見つけるのが大変だったのでここでまとめておこうと思います。備忘録。

1. プロジェクト作成

Spring Boot のプロジェクトについては、Spring Initializr を使って作ります。

今回は以下の内容で作ります。

  • Gradle project
  • Spring Boot Version 1.3.1
  • Dependencies:
    • Web
    • Devtools
    • Thymeleaf

Group と Artifact はご自由に。今回は com.rabitarochanspringboot-kotlin-intellij で作ってみます。

プロジェクトファイル一式を ZIP ファイルでダウンロードしたら、任意のディレクトリに展開します。

2. build.gradle 修正

次に、build.gradle に Kotlin 用の設定を追加していきます。

先に、変更箇所をコメントで記載した build.gradle 全体を載せます。

buildscript {
    ext {
        springBootVersion = '1.3.1.RELEASE'
        // (1) Kotlin のバージョンを指定
        kotlinVersion = '1.0.0-beta-4584'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
        // (2) Kotlin の Gradle plugin を追加
        classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
    }
}

apply plugin: 'java'
// (3) Kotlin plugin を有効化
apply plugin: 'kotlin'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'spring-boot' 

jar {
    baseName = 'springboot-kotlin-intellij'
    version = '0.0.1-SNAPSHOT'
}
sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories {
    mavenCentral()
}


dependencies {
    // (4) Kotlin のライブラリを参照追加
    compile("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
    compile('org.springframework.boot:spring-boot-devtools')
    compile('org.springframework.boot:spring-boot-starter-thymeleaf')
    compile('org.springframework.boot:spring-boot-starter-web')
    testCompile('org.springframework.boot:spring-boot-starter-test') 
}

// (5) IntelliJ IDEA 用の設定を追加
idea {
    module {
        inheritOutputDirs = false
        outputDir = file("$buildDir/classes/main/")
    }
}

eclipse {
    classpath {
         containers.remove('org.eclipse.jdt.launching.JRE_CONTAINER')
         containers 'org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8'
    }
}

task wrapper(type: Wrapper) {
    gradleVersion = '2.9'
}

(1) で、Kotlin のバージョンをプロパティとして定義します。Kotlin は現時点 (2016/1/12) でバージョン 1.0 のベータ版という状態のため、新しいバージョンが頻繁に出ます。今回の build.gradle でも 2 箇所で Kotlin のバージョンが必要となりますので、プロパティにしておいたほうがいいでしょう。

Kotlin のバージョンは、Kotlin の Twitter アカウントUsing Gradle で確認しています。

(2)、(3) で、Kotlin の Gradle プラグインを有効化しています。

(4) で、Kotlin の標準ライブラリを依存ライブラリに追加します。

(5) では、IntelliJ IDEA で make した際の出力ディレクトリを上書きしています。これは、Spring Boot Devtools の自動再起動を有効化するために必要です。

IntelliJ IDEA では make した際のデフォルト出力先は out ディレクトリとなっていますが、Spring Boot Devtools はこのディレクトリではなく、idea.module.outputDirs で指定した $buildDir/classes/main/ を見ているようです。IntelliJ IDEA プラグインの設定については IdeaModule - Gradle DSL Version 2.10 をご参照ください。

3. IntelliJ IDEA のショートカット登録

Spring Boot + IntelliJ IDEA で調べるとすぐ発覚するのですが、IntelliJ IDEA で実行 / デバッグ実行中に自動 make が起動しないという仕様があります。これに対応するために、Ctrl (or Cmd) + Shift + S でファイル保存と make をするマクロを実行するように設定していきます。

3.1. Save all and make マクロを作成する

勝手に名前つけましたが、以下の手順で上で書いたマクロを作成していきます。

  1. IntelliJ IDEA を起動します
  2. メニューの [Edit] → [Macros] → [Start Macro Recording] をクリックします。(これ以降の操作がマクロの内容となります。)
  3. 【操作1】メニューの [File] → [Save All] をクリックします。
  4. 【操作2】メニューの [Build] → [Make Project] をクリックします。
  5. メニューの [Edit] → [Macros] → [Stop Macro Recording] をクリックし、マクロの記録を停止します。
  6. マクロ名を入力するダイアログが表示されるので、適当に名前を入力します。(ここでは Save all and make と入力します。)

3.2. マクロを実行するショートカットを設定する

マクロを Ctrl (or Cmd) + Shift + S で実行できるようにするため、以下の手順でショートカットを登録します。

  1. メニューの [IntelliJ IDEA] → [Preferences...] をクリックします。(これは OS X の場合です。Windows は [File] → [Preferences...] だったはず・・・)
  2. 設定一覧の [Keymap] を選択します。
  3. 右側に検索ボックスが表示されているので、[save all] で検索すると、先ほど作成したマクロが表示されるので、[右クリック] → [Add Keyboard Shortcut] をクリックします。
  4. ダイアログが表示されるので、設定するショートカットを入力します。ショートカットの内容を確認して [OK] ボタンをクリックします。

これで IntelliJ IDEA の設定は完了です。

4. IntelliJ IDEA のプロジェクトを作成する

gradle のコマンドで IntelliJ IDEA のプロジェクトファイルを作成します。

ターミナルを起動して ./gradlew idea を実行するだけです。Windows の場合は gradlew idea です。

あとは、IntelliJ IDEA からプロジェクトのディレクトリを指定して Open します。

5. 実行する

まだ Kotlin は書いてませんが、実行方法を書いておきます。

  1. Applicationクラスを [右クリック] → [Debug ...] をクリックします。
  2. あとはソース、HTML ファイル等を編集して、上で登録したショートカットを押すと、デバッグ実行中でも自動再起動やホットスワップがおこなわれ、スムーズに開発できます!

また、コントローラーにルーティングを追加した場合や、クラスのメソッドを追加した際には、ホットスワップが上手く動かないらしく以下のメッセージが表示されます。

Hot Swap failed:
com.rabitarochan.MyApplication: add method not implemented;
com.rabitarochan.MyApplication: Operation not supported by VM

メッセージ的には failed と書いてありますが、Devtools の自動再起動にてちゃんと認識してくれるようですので、あまり気にしなくてもいいでしょう。ちゃんと認識しない場合はちゃんと再起動してあげたほうがいいです。

まとめ

あまり大きなアプリを開発しているわけではありませんが、以上の手順で今のところスムーズに開発できてます。

Kotlin で Spring Boot を書くためには少し工夫が必要だったりするので、それについてはまた後で記事を書こうと思います。まずは開発環境を構築するということで、ご査収ください。

今回のソースは以下のリポジトリです。Application クラスと Controller を Kotlin で実装したソースもありますので、記事を書くまではこちらをご参照ください。

github.com

でわ。

2016年の抱負

もう1/5ですが、あけましておめでとうございます。

これまで抱負的なものは書いてなかったので、今年は書いてみようと思います。

  1. Kotlin の学習
  2. Web サービス作る
  3. iOS / Android のキャッチアップ
  4. 社内勉強会の継続

Kotlin の学習

昨年の10月あたりから Kotlin 触ってました。Android 開発に人気があるみたいですが、自分の領域的には Web などバックエンド側が多いので、そっち寄りを中心に学習していこうと思います。

まだまだ純 Kotlin のフレームワーク・ライブラリも多くないことから、時間を見つけて作ってみたいです。

あと、Scala も置いていかれない程度に追いかけたい。

Web サービス作る

まずは社内向けに Web サービスを作ろうと思っています。「この Web サービスいいんだけど、ウチの社員数的に利用料金が高くなる・・・。」というものが結構多いので、それを代替できるようなものを作っていこうと思います。

仕事・業務以外でも1つくらい作りたい。

iOS / Android のキャッチアップ

昨年末に iOS アプリの研修を受けたので、その時作りかけたアプリをリリースするところまで持って行きたいです。ついでに Android 版も作ってみて、一通り iOS / Android 開発を経験しておきたいです。

社内勉強会の継続

自分が言い出しっぺというわけではないですが、社内勉強会・アイディアソンを何回かやりました。これは今年も継続していきたいです。

アイディアソンでは色々とアイディアはでる一方、実現までたどり着けていないのが多いので、今年は少しでも前進しなくてはと思ってます。

直近の目標としては、社員旅行制度を使ってハッカソンやりたい。


やりたいことはいっぱいあるので、ゆっくりじっくり取り組んでいきたいです。本年もどうぞよろしくお願いいたします。

Kotlinのプロパティをタイプセーフに取得する

おはようございます。Kotlinアドベントカレンダー2015の22日目の記事でございます。 DBアクセスライブラリを作っていたときに見つけたものを紹介します。

Kotlinのプロパティをタイプセーフに取得する

Kotlinのプロパティは、Java8のメソッド参照のように書くことでプロパティ定義(kotlin.reflect.KProperty1<T, R>)を取得することができます。

// データクラスを定義
data class Person(val name: String, val age: String)

fun main(args: Array<String>): Unit {
    val prop1 = Person::name
    println(prop1.name) // => "name"

    val prop2 = Person::age
    println(prop2.name) // => "age"
}

コンパイル結果を確認していないので不確かですが、上記のプロパティ名を取得する処理については、コンパイル時に解決しているのかループで回してもあまりコストがかかっていませんでした。

もちろん、プロパティ名を変更した場合は参照箇所がコンパイルエラーになるため、プロパティ名をハードコーディングしていた箇所を置き換えることができます。

データベースの問い合わせ結果をマッピングする

Scala の Skinny Framework や Scalikejdbc の作者の @seratch さんが、Kotlin の DB アクセスライブラリを書いていました。

seratch/kotliquery https://github.com/seratch/kotliquery

ことりクエリーかわいい。自分のDBアクセスライブラリは置いといてこっち触りましょうw ことりクエリーでは、問い合わせ結果が Row という ResultSet をラップしたクラスで返ってきます。このクラスに上で書いたプロパティ参照を受け取るメソッドを追加することで、プロパティ名をハードコーディングしなくてもよくなります。

// 追加するメソッドの例
fun <T> get(prop: KProperty1<T, String?>): String? {
    val columnLabel = camelToSnake(prop.name) // camelCase を snake_case に変換するメソッド
    return string(columnLabel)
}

fun <T> get(prop: KProperty1<T, Int?>): Int? {
    val columnLabel = camelToSnake(prop.name)
    return int(columnLabel)
}

// データ型の数だけ続く・・・
// マッピング
val toMemberReflection: (Row) -> Member = { row ->
    // プロパティ名のハードコーディングがなくなる!
    Member(row.get(Member::id)!!, row.get(Member::name), row.get(Member::createdAt)!!)
}

この拡張をしてみたソースを置いておきます。

kotliquery/Row.kt at mapping-reflection · rabitarochan/kotliquery https://github.com/rabitarochan/kotliquery/blob/mapping-reflection/src/main/kotlin/kotliquery/Row.kt

まとめ

例として、データベースの結果を取得する方法をあげましたが、Webリクエストのパラメータを取得する、JSONシリアライズ/デシリアライズなど使える箇所はいろいろあると思います。 プロパティ以外にも、メソッド参照も取得できますし、JavaのClassも取得することができます。みなさんもKotlinのリフレクションを触ってみてください!

明日は mattak さんです!!

Kotlin で Loan パターン (別解)

最近 Kotlin ばっかりやってます。

さて、Qiita に同じ内容の記事が出てました。

Kotlin - Kotolin で Loan パターン - Qiita http://qiita.com/deadbeef/items/862b908ac54fdd58c3df

やり始めた時に自分も作ってたので載せておこうと思います。 合わせて、疑問点も書いておきます。

package sandbox.loanpattern

fun <T : Closeable, R> using(resource: T, f: (T) -> R): R {
    try {
        return f(resource)
    } finally {
        resource.close()
    }
}

使い方

using (ByteArrayOutputStream()) { outStream ->
    outStream.write(...)
}

ほぼ Scala と同じにかけます。

疑問

using 関数の定義を見てみると、リソースと、リソースを利用する関数の2引数を取るものとなっています。 しかし、使い方を見てみると using 関数に渡す引数 f はカッコの外に書いています。

これって言語的にはどういう機能で、リファレンスに記載のあるものでしょうか?(見つけられなかった)