torutkのブログ

ソフトウェア・エンジニアのブログ

Redmine 4.0(Rails 5.1)でのプラグイン作成について(5)

Redmine 4.0(Rails 5.1)でのプラグイン作成について(4) - torutkのブログ の続き。

Glossaryプラグインの再構築でファイル添付機能を実装しようとこの数日格闘していました。
書籍「Redmine Plugin Extension and Development」は4章にファイル添付の解説があり、基本そのままたどってみたのですがいくつか落とし穴(書籍の記述を見落としたものもあり)があって、長引いています。

Redmine Plugin Extension and Development

Redmine Plugin Extension and Development

  • 作者:Alex Bevilacqua
  • 出版社/メーカー: Packt Publishing
  • 発売日: 2014/03/19
  • メディア: ペーパーバック

ファイル添付できない

書籍では、コントローラーのupdateメソッドに次の記述を追加するサンプルが記載されています。

def update
  # ...
  if @article.update_attributes(params[:article])
    @article.save_attachments(params[:attachments])
    render_attachment_warning_if_needed(@article)

Glossaryプラグインの再構築では、updateメソッドで最初次のように記述しましたが、ファイルが添付されませんでした。

def update
  @term.attributes = glossary_term_params
  if @term.save
    @term.save_attachments params[:attachments]
    render_attachment_warning_if_needed(@term)

モデルのsaveの後にsave_attachmentsを呼ぶのがまずいのかもと、試行錯誤的に順番を入れ替えてみたところ、ファイルが添付されました。次のコードです。

def update
  @term.attributes = glossary_term_params
  @term.save_attachments params[:attachments]
  if @term.save
    render_attachment_warning_if_needed(@term)

Redmine本体で添付ファイルを扱っている個所を探してみたところ、ドキュメントコントローラーに類似コードがありました。

  • app/controllers/documents_controller.rb
def create
   :
  @document.safe_attributes = params[:document]
  @document.save_attachments(params[:attachments])
  if @document.save
    render_attachment_warning_if_needed(@document)

このコードとほぼ同じなので、試行錯誤の結果は妥当かと思います。

権限の名前が不一致で添付ファイルのダウンロードが403エラー

書籍ではモデルクラスに次の1行を書くサンプルが記載されています。

acts_as_attachable

その後ろの本文の中でデフォルトの権限はモデル名に基づき生成されるとの記述があったのですが読み落としており、添付したファイルをクリックすると403エラーとなってしまいました。Redmineのコードにputs文で値を出しつつ原因を調べたところ、モデル名から生成される権限名(GlossaryTermモデルならview_glossary_term)と、Glossaryプラグインの設定(init.rb)で定義した権限名(今回はview_glossaryと指定)が違っていたためと判明しました。それから再度書籍を見ると、ちゃんと書いてありました。見落としでした。ただ、Redmineのコードを追いかけていたので、書籍(Redmine 2.x)の記載と今のコード(Redmine 3.4)では権限の指定種類が1つ増えている違いがあるのが分かったのがせめてもの収穫でした。

acts_as_attachable view_permission: :view_glossary,
  edit_permission: :manage_glossary, delete_permission: :manage_glossary

ファイル添付が成功しても、失敗のメッセージ表示が出る

コントローラーでファイル添付の際に失敗があれば警告メッセージを表示する次の呼び出しを書いています。

render_attachment_warning_if_needed(@term)

ところが、なぜかファイル添付が成功していても、警告メッセージが表示されてしまいます。
調べていくと、フォームからファイル添付時に送るパラメーターにゴミが含まれていることが分かりました。次は編集時にファイルを1つ添付した場合にフォームから送られる添付ファイルのパラメータです。

"attachments"=>{
  "1"=>{"filename"=>"f022.png", "description"=>"aaa", "token"=>"38.8e4d1dd26bde5a3b4c0f79c186713c1f6922ce57a1d42dca1e9dcbaab5604f84"},
  "dummy"=>{"file"=>""}
}

なぜか、キー"dummy"が付いており、1つのファイルを添付し他のに関わらず2つのキーバリューが生成されています。添付ファイル1件の処理が終わった時点で、残りが1件と誤認識され警告メッセージが表示されていると思われます。

書籍に言及はなく、ググっていると次のブログに解決方法を見つけました。
【Redmine拡張】Redmineの添付ファイル機能を使う : ほんにゃら重工

曰く

フォームはmultipartをONにしておく必要あり。

edit.html.erbを修正しました。htmlをキーにmultipart指定を追加しています。

<%= labelled_form_for @term, url: project_glossary_term_path, html: {multipart: true} do |f| %>

その他

WSLのOpenSUSE環境が壊れた?

主にWSLのOpenSUSEプラグインの作成を行ってきましたが、今日、bundle execでrailsコマンドを実行しようとすると、rubyがエラー(pthread のinvalid argumentエラー)になってしまいました。

結局、OpenSUSEMicrosoft Storeアプリ)をアンインストールしてインストールし直しました。

Redmineプラグインの作成について、1か月経過時の感想

今年の5月1日から、Redmineの用語集プラグインをゼロから作ってみる作業を始めて一ヶ月が経過しました。(始めた経緯は、Redmine 4.0(Rails 5.1)でのプラグイン作成について - torutkのブログ に記載)

基本、オフの時間帯に細々と取り組んでいるので、まだ用語集としての基本機能がやっと通ったかなという段階です。経過は以下のWikiに執筆中です。

Redmineプラグイン開発環境 - ソフトウェアエンジニアリング - Torutk

ここまで作ってきてのRedmineプラグイン作成の感想を残しておこうと思います。

Rubyのコードに馴染んでいないと、参考にするプラグインRedmineソースコードを読むときの敷居が数段高くなる

特に、メソッドの引数の括弧の省略、メソッドの引数の並びで最後がハッシュのときはハッシュの波括弧が省略される書き方は読み手を混乱させます。また、引数の省略も頻出し、これも読み手を惑わせます。
これは、Rubyのコードに慣れるしかないかなぁと思います。

用語集プラグインは、Redmineの機能拡張というよりも、むしろRedmineが使っているフレームワークRuby on Rails上に新規アプリケーションとして作ったものをRedmineと同居させているようなもの。MVC構造を一から作っているので、だから作るのが大変である

Redmineのモデルと関連を持たせているものの、MVCはそれぞれRedmineとは独立して存在しています(メニューの登録等一部関係はあり)。Redmineプラグインが相当な自由度を用意しているといえるのかもしれません。

以前作りかけて挫折した採番プラグインも、思えばMVC構造を一から作ろうとしていたので用語集と同じに大変だったのかもしれません(以下リンク)。
Redmineプラグイン開発(できるといいなぁ)−採番プラグインを目指して

Railsフレームワークの振る舞い(特にコントローラーとビューとの間のインタラクション)が頭の中にイメージできるまで、コントローラーとビューのコードを見てもしっくりこない

プラグイン開発の参考資料の拠り所

この一ヶ月で頻繁に参照した、あるいはとても役に立った参考資料をいくつか挙げておきます。

Redmine 2.xの記述で少し古いですが、プラグインの作り方についてはここの解説が柱となっています。

英語の書籍ですが100ページ弱と分量はそう多くなく、雛形生成、メニュー登録、権限制御、ファイル添付、活動への反映、検索対象化などが解説されています。常に手元において内容確認しています。

ビューに関して、いくつかの領域、サイドバー、フォーム、nodata、fieldset、などなどのテクニックを紹介しています。


その他いくつかあり、随時追加予定

「似たプラグインのコードを参考にせよ」は参考にならない

よく、プラグインを作るなら「似たような機能のプラグインを探し、そのコードを真似ればいいよ」というアドバイスがあります。受け手によるとは思いますが、このアドバイスプラグイン開発入門者には厳しいと感じています。

まず、似た機能のプラグインを探すところからです。機能(振る舞い)が似ていてもコードが参考になるかは中を見てみないと分かりませんし、プラグイン開発初心者はそのコードがよい手本となるものか、粗悪なものなのか見て判断つきません(判断つけば中級者です)。特に昨今Railsのバージョンアップにプラグインを追従させるため、書き方が古いもの、場当たり的にバージョンアップ対応してきているもの、きれいに対象バージョンに合わせて書かれたもの、など玉石混淆過ぎます。

また、完成形のプラグインのコードは、セキュリティ、バリデーション、ロジックなど様々なアスペクトが同じ個所に集約されているので、まだそれらアスペクトを理解しないうちのコードを読んでもなかなか理解をするのが難しいです。

どうしてもチュートリアルアスペクトを別々に切り出して解説があるのが望ましい)が必要になってきます。

Redmine 4.0(Rails 5.1)でのプラグイン作成について(4)

Redmine 4.0(Rails 5.1)でのプラグイン作成について(3) - torutkのブログの続きです。

用語一覧表示において、べたに一覧表示する方法と、カテゴリ毎に一覧表示する方法とをラジオボタンで切り替える機能をどのように実装すればよいかを探っています。

アプローチの模索

スタンドアロン(デスクトップ)アプリケーションと違って、Webアプリケーションはサーバー側で表示(HTML)を生成するので、表示設定の変更もいったんサーバーに送って新たに表示を生成することになります。

JavaScriptで表示を制御するとしたら

JavaScriptを駆使して、ブラウザ上で表示を変更することもできないかとアプローチしてみましたが、表示する可能性のあるHTML要素を予め生成しておき、JavaScriptからHTML要素の表示有無を切り替えるといった振る舞いとなり、今回であればべた一覧表示とカテゴリ毎の一覧を両方生成し、どちらを表示するかを切り替えるといった実装になりそうです。
ラジオボタンのコールバックにJavaScriptの関数を指定し、ラジオボタンが押されたら、対応するHTML要素を有効化し、残りを無効化するようなイメージです。

べた一覧表示を、

...
で囲い、カテゴリ毎に一覧表示を、
...
で囲い、JavaScriptにおいて、var not_grouping = document.getElementById("not_grouping")で取得すると、取得した要素のオブジェクトに対し、style.display="none"を設定すると非表示にすることができる模様です。

ちょっと力業過ぎるかと思い、実装には入りませんでした。

フォームで制御するとしたら

ラジオボタンの設定をフォームでサーバーにサブミットし、サーバー側で設定を見てべた一覧表示かカテゴリ毎の一覧表示かを判別してHTMLを生成します。

ここで、Ruby on Rails では通常はモデルクラスを用意し、フォームではそのモデルのインスタンスを指定しインスタンスの属性を新規作成ならcreate、既存の変更ならeditアクションを実行します。

しかし、今回は表示設定なのでモデルクラスはありません。表示設定をデータベース化するならモデルクラスからコントローラークラス、ビューとフルに作ってもよいですが、そこまで頑張らずに済む方法を追求します。

用語コントローラーのindexアクションで、ラジオボタンの設定をリクエストパラメーターに持たせ、indexの描画時にパラメーターに応じてべた一覧表示かカテゴリ毎に一覧表示かを切り替えることとします。

まず、RedmineRuby on Rails)でフォームを実装するAPIとして利用できそうなものを探したところ次が見つかりました。

API 内容
form_for モデルインスタンス用のフォーム
form_tag カスタムURL(モデルインスタンス不要)用のフォーム
form_with Ruby on Rails 5.1から導入され、form_forとform_tagの両者の機能を持ち将来的に置き換わるAPI
labelled_form_for Redmineが提供するform_forのラッパー

今回はモデルクラスとモデルインスタンスを指定せずにフォームを実現したいので、モデルの指定が不要なform_tagまたはform_withを使用します。

form_withについては、従来のform_forとform_tagの機能を兼ね備え、これらを代替するものとしてRails 5.1で導入されました。将来的にはform_forとform_tagはデプリケートとなる予定です(Rails 6あたり)。

form_tagでラジオボタンを設置

書式は、

form_tag(URL [, options]) do
end

といった感じです。

用語一覧のコントローラーのindexアクションはルーティング設定でproject_glossary_termsの末尾に_pathを付けたものになります。
メソッドはデフォルトではpostになってしまうので、明示的にgetを指定しています。

<%= form_tag(project_glossary_terms_path, method: :get) do %>
  <%= radio_button_tag :grouping, 1, @grouping == '1' ? true : false %>
  <%= label_tag 'カテゴリで分類' %>
  <%= radio_button_tag :grouping, 0, @grouping == '0' ? true : false %>
  <%= label_tag '分類なし' %>
  <%= submit_tag '表示' %>
<% end %>

ラベルの指定は不十分かもしれません(ラジオボタンと紐づくラベルを生成するにはさらなる指定が必要な模様)。

form_tagを使用する場合、radio_buttonではなくradio_button_tagを使用するようです。

form_with

これは、用語集プラグイン再構築で実装したので日記では省略します。

Redmine 4.0(Rails 5.1)でのプラグイン作成について(3)

Redmine 4.0(Rails 5.1)でのプラグイン作成について(2) - torutkのブログの続きです。
この5月は、RedmineのGlossaryプラグインをステップ・バイ・ステップで再構築する試みをずっと続けています。その様子は次のWikiページに執筆中です。

Redmine Glossaryプラグイン再構築 - ソフトウェアエンジニアリング - Torutk

日記の方では、プラグイン再構築作業をしていて感じたことなどを随時書いていこうと思っていたものの、ゆとりがなく日記更新が追い付いていませんでした。

プログラミング言語Rubyについて

いままで、幾度かRubyをかじってみたものの手に馴染む前にあきらめて放置していました。また、Redmineプラグインを作ろうとして、あるいは既存のプラグインが動かないので手を入れようとして、そのプラグインが書かれているRubyのコードを見てみましたが、何をしているのか判然とせず、1行1行何をしているのか理解に至れていませんでした。

最初は、前置コロン、後置コロン、ハッシュの書き方がよく分からず、(例えば次のようなコード)

:name => 'glossary'
name: :glossary

メソッド呼び出しの引数の丸括弧の省略がよく分からず(例えば次のコード)

<%=l :label_category %>
<%=l(:label_category) %>

メソッド呼び出しの引数の最後がハッシュのときはハッシュの波括弧が省略できるので、それを理解せずコードを読むと多数の引数が指定されているように見えて、しかもリファレンス上そのような多数の引数が存在せず、大混乱したり、などなど。

そんなおり、去年の年末ごろは仕事の関係でExcel VBAを使って業務の手順を効率化するツールを作る機会があり(Excel VBAプラットフォームでプログラミング;-( - torutkのブログ)、VBAでは関数呼び出しで括弧が省略できることを知りました(省略できる場合、できない場合、setが必要な場合などかなり混乱させられましたが)。
また、今年1月からJava読書会BOFで「Kotlinイン・アクション」を読み始め、Kotlinもメソッド呼び出しでの括弧
省略や最後の引数の特別扱いなどを学び、ようやくRubyのコードに理解が及ぶようになりました。

面倒な表示設定

オリジナルのGlossaryプラグインでは、用語の一覧表示をべたに一覧する場合と、カテゴリ毎に分類してカテゴリ毎に別々に一覧表示する場合があります。

どちらの一覧表示にするかの設定は、サイドバーのラジオボタンで制御します。

しかし、Webアプリケーションでは、ユーザーの操作はフォームによってサーバーへサブミットし、その結果で表示が更新されるというものです。そのため、表示の見え方を設定するのにも、いったんサーバーへ情報を送る必要が生じます。

Webアプリケーションって一体誰得なんでしょうかねぇ。

5月の土曜日は・・・

今日は子供の小学校の運動会でした。うちの子の小学校はだいたい5月後半の土曜日に開催されます。近所の小学校だと先週の土曜日に開催されるところもありました。

今日は日本Javaユーザーグループ主催のカンファレンスJJUG CCC 2018 Springの開催日であり、さらにRedmine.tokyo主催の第14回勉強会 - redmine.tokyoの開催日でした。

どちらも可能なら参加してきましたが、家族イベントの方が優先なので今回も参加できず・・・。小学校のお子さんを持つパパ・ママ・エンジニアはなかなか参加できないことがあるのではないでしょうか。
なお、11月も土曜日に小学校イベントがあったりします。

また、JJUG CCCとRedmine.tokyoはどちらも年2回春・秋開催で、かなりの頻度で開催日が重なるイベントです。

土曜日については、代表を務めているJava読書会BOF主催のJava読書会も月1回開催されているのでそちらと重なる可能性もあり、なかなかな状況です。読書会会場確保(抽選申し込み)時点で開催日が判明していれば避けた日付にしていますが、分からないことも多いのでそのときは運次第となります。

Redmine.tokyoは

毎回YouTubeでのライブ配信と、その後の録画配信があるので参加できなくてもある程度勉強会の内容が得られるので大変ありがたいです。
ただ、Redmine.tokyoの勉強会はグループディスカッションで参加者同士の情報交換時間があり、これがとても刺激になるのでやはり直接参加できるのが一番です。あと懇親会も濃ゆい話が聞けるので価値あり。

JJUG CCCは

こちらは登壇者が個人的に資料公開するのが慣習になっているので、大半のセッション資料は後で参照できますが、やはり資料だけではセッション聴講で得られることの数分の一(もっと少ないかも)になってしまいます。

Redmine 4.0(Rails 5.1)でのプラグイン作成について(2)

Redmine 4.0(Rails 5.1)でのプラグイン作成について - torutkのブログ の続きです。

ゴールデンウィークをほぼGlossaryプラグインの再構築に費やしています。が、ステップバイステップの段階のやっと半分を超えたところです。

Rails 5ではbelongs_toのデフォルトはNULL許さず

他のモデルとの関連付けの一つにbelongs_toがあります。belongs_toを使って他のモデルを参照することはプラグインでは定番ではと思います。
このbelongs_toですが、Rails 5ではNULLを許容しない設定に変更になっています。
プラグインRedmine 4.0対応の一つの課題になります。

Redmine 4.0(Rails 5.1)でのプラグイン作成について

redmine trunkを落としてglossary pluginが動くか試してみる(続々々) - torutkのブログにおいて、GlossaryプラグインRedmine 4.0対応に難航し暗礁に乗り上げてしまったので、別アプローチとしてゼロからGlossaryプラグインを作成する作業を5月1日より開始しています。この作業を通じてRedmineプラグイン作成に習熟できたなら、既存のGlossaryプラグインRedmine 4.0(Rails 5.1)対応の修正をすることも容易になるだろうし、あるいはゼロから作った新Glossaryプラグインを主とし、既存のデータベースのマイグレーションを用意するのもの可能になるのかと思います。

ゼロからGlossaryプラグインを作る過程を、次のWikiに絶賛記載途中です。

Redmine Glossaryプラグイン再構築 - ソフトウェアエンジニアリング - Torutk

ステップバイステップで小さなプラグインから開始しています。最初は、モデルクラス1つ、コントローラー1つ、アクションに対応するビューが少々で、プロジェクトとの関連もない、権限の設定もないシンプルなものを作っています。本日時点でやっと一通り出来てきました。次はモデルクラスを2つに増やすところです。

ゼロからの作成作業をしていて、気づいたこと、知ったことなどを時々こちらの日記に書いていきます。

国際化対応テキストの変換メソッド名

Redmineのコードでは、ビュー(.erb)でよく見かけるのが"l"(エル)メソッドです。

<%= link_to l(:button_edit) ... %>

Rails関係を調べると、RailsI18n.translateメソッドの省略(ヘルパーメソッド)でtが、I18n.localizeメソッドの省略でlが利用できます。localaize(l)は、日付・時刻などをロケールに合わせた表現に変換するものです。

Redmineでは、なぜlメソッドを使っているのかとても悩みました。tの間違いでは?でもならなぜ正常に機能しているのか不思議でした。

調べると、Redmineのlメソッドは内部でRailsI18n.tを呼び出していました。
最初RedmineのlとRailsのlと同じ名前の関数で混同していましたが、やっと腑に落ちました。それにしても紛らわしいですね。

link_to で確認ダイアログを表示

削除アイコンをクリックしたときに、安全のため(?)、確認ダイアログを表示することがあります。

以前の書き方は、

<%= link_to l(:button_delete), hello_path, :confirm => l(:text_are_you_sure),
 :method => :delete, :class => 'icon icon-del') %>

のように、confirmをキーとするハッシュを引数に指定していました。

Rails 4.1でこのconfirmをキーとしたハッシュは廃止(削除)となっており、代替記述として次のようにdataをキーとしたハッシュでその中にconfirmをキーとしたハッシュを指定します。

<%= link_to l(:button_delete), hello_path, method: :delete,
 data: {confirm: l(:text_are_you_sure)}, class: 'icon icon-del' %>

ハッシュの書き方は、=> を使う記法と、:を使う記法があり、後者はruby 1.9で導入されたものです。Redmine 2.xはRails 3ベースでruby 1.8をサポートする必要があり、=>を使う記法でした。Redmine 3.xでRails 4.2ベースになった際、Rails 4.2がruby 1.9.3以降のサポートとなったので、Redmine 3.x以降を対象にするならハッシュの書き方に:を使う記法を使用できるようになります。