Redmine のカスタムフィールド「リスト」をチェックリストとして使う
Redmine のカスタムフィールドには、リストを選択するための書式「リスト」があります。(そのまんま)
その書式で定義したカスタムフィールドを「チェックリスト」として使いたかったのですが、デフォルトの Redmine では使いにくかったので、プラグイン「View Customize Plugin」を使ってカスタマイズした話を書きます。
※これ以降、書式「リスト」のカスタムフィールドのことを 「リストフィールド」 と記載します。
前提
- View Customize Plugin を導入している Redmine
https://redmine.org/plugins/redmine_view_customize
上記プラグインがあれば何でもできるので、ぜひ導入しましょう。
デフォルト Redmine を使ったチェックリストの問題点
デフォルト Redmine で、リストフィールドを利用したチェックリストを実装しようとすると、以下の問題に気が付きます。
- チケット表示画面では、リストフィールドのチェックされている項目は見えるが、チェックされていない項目が見えない。
具体的に、以下のようなリストフィールドを作成し、チケット入力画面と表示画面を見比べてみます。
カスタムフィールド定義
チェックリスト項目「テスト用リストフィールド」を定義します。
項番 | 説明 |
---|---|
(1) | 書式に「リスト」を指定します |
(2) | 複数選択可にチェックを付けます |
(3) | 選択肢に、チェックリストの項目を指定します |
(4) | 表示に「チェックボックス」を指定します |
チケット入力画面
作成したリストフィールドは、入力画面では以下のとおり表示されます。
この画面だけだと、チェックリストのように使えるのではないかと思ってしまいます。
チケット表示画面
問題のチケット表示画面です。
赤枠で囲まれている箇所がリストフィールドの表示箇所ですが、チェックした項目のみ表示されており全体が見えません。また、改行もされず読みにくいです。
解決案
解決案としては、チェックボックスをそのまま表示するだけで格段に見やすくなると思います。
今回は、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; } });
適用後の画面
圧倒的見やすさ!
チェックしていない項目も表示されていて、チェック項目の見た目も入力画面と統一できました。これならチェックリストとして使えそうです。
まとめ
Redmine のカスタムフィールドを使ってチェックリストを実現する方法についてまとめました。
ほぼ View Customize Plugin のサンプルみたいな記事ですが、とても便利ですし Redmine を魔改造しやすいのでとてもおすすめです。 (時間があれば、カスタムフィールドをグループ化 / 並べ替えするスクリプトも作っているので紹介しようと思います。)
みなさんも便利なスクリプトがあればぜひ教えてください!
Kotlinで特定のブロック内でのみ有効な拡張関数を定義する
Kotlinのドキュメントに、Type-Safe Buildersという記事があります。 これは、Groovyでよく使われている builders パターンというものを、Kotlinで表現した際にどうかるかを示したドキュメントです。
Groovy使ったことないですが、build.gradle
の定義にも使われているこういう書き方を指します。
buildscript {
ext {
kotlinVersion = '1.0.0'
}
}
要は、独自のブロックを定義して、その中で初期化処理を実行しましょうということです。 Kotlinのブロックについては、親子関係を持つ独自のブロックを作るという内容で「逆引きKotlin」に追加されています。
親子関係のある独自のブロックを作りたい - 逆引きKotlin - 逆引きKotlin
今回は、この機能を利用した「特定のブロック内でのみ有効な拡張関数」を定義する方法について紹介したいと思います。
元ネタはこちらです。
やりたいこと
例として、ブロックを抜けたら指定したリソースを開放するブロック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()
が定義されています。
使い方
メソッドに対して、コンパイルを通しつつ未実装を表す場合は、以下のように書きます。 引数に文字列を指定することで、例外にコメントを追加することも可能です。
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") }
まとめ
未実装を表す kotlin.NotImplementedError
と TODO()
について紹介しました。
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.rabitarochan
、 springboot-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
マクロを作成する
勝手に名前つけましたが、以下の手順で上で書いたマクロを作成していきます。
- IntelliJ IDEA を起動します
- メニューの [Edit] → [Macros] → [Start Macro Recording] をクリックします。(これ以降の操作がマクロの内容となります。)
- 【操作1】メニューの [File] → [Save All] をクリックします。
- 【操作2】メニューの [Build] → [Make Project] をクリックします。
- メニューの [Edit] → [Macros] → [Stop Macro Recording] をクリックし、マクロの記録を停止します。
- マクロ名を入力するダイアログが表示されるので、適当に名前を入力します。(ここでは
Save all and make
と入力します。)
3.2. マクロを実行するショートカットを設定する
マクロを Ctrl (or Cmd) + Shift + S
で実行できるようにするため、以下の手順でショートカットを登録します。
- メニューの [IntelliJ IDEA] → [Preferences...] をクリックします。(これは OS X の場合です。Windows は [File] → [Preferences...] だったはず・・・)
- 設定一覧の [Keymap] を選択します。
- 右側に検索ボックスが表示されているので、[save all] で検索すると、先ほど作成したマクロが表示されるので、[右クリック] → [Add Keyboard Shortcut] をクリックします。
- ダイアログが表示されるので、設定するショートカットを入力します。ショートカットの内容を確認して [OK] ボタンをクリックします。
これで IntelliJ IDEA の設定は完了です。
4. IntelliJ IDEA のプロジェクトを作成する
gradle のコマンドで IntelliJ IDEA のプロジェクトファイルを作成します。
ターミナルを起動して ./gradlew idea
を実行するだけです。Windows の場合は gradlew idea
です。
あとは、IntelliJ IDEA からプロジェクトのディレクトリを指定して Open します。
5. 実行する
まだ Kotlin は書いてませんが、実行方法を書いておきます。
Application
クラスを [右クリック] → [Debug ...] をクリックします。- あとはソース、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 で実装したソースもありますので、記事を書くまではこちらをご参照ください。
でわ。
2016年の抱負
もう1/5ですが、あけましておめでとうございます。
これまで抱負的なものは書いてなかったので、今年は書いてみようと思います。
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 はカッコの外に書いています。
これって言語的にはどういう機能で、リファレンスに記載のあるものでしょうか?(見つけられなかった)