あらためて感心したRedmineのデータ利用方法(UserPrefernce 編)


原田です。当社では福利厚生の一つとして「チケットレストラン」を導入し社員に提供しています。私も昼食購入時に利用していますが、いつも同じものを購入するので健康面から見るとよろしくないと思っています。たまにはお腹を満たすだけでなくおいしいものを食べたいですね。

Redmineはさまざまな情報を管理しています

当社のサービス紹介やブログ等をお読みの方の多くはRedmineに興味を持たれ、ご利用・ご検討いただいていると思います。 Redmineの改善作業を行っていますと実際に入力した情報(データ)をどのように取り扱っているのか非常に興味を惹かれます。 今回は少し異質(特徴的)なデータの持ち方をしている UserPreference についてご紹介します。

sqlite> .header on
sqlite> .mode column
sqlite> pragma table_info('user_preferences');
cid         name        type        notnull     dflt_value  pk
----------  ----------  ----------  ----------  ----------  ----------
0           id          integer     1                       1
1           user_id     integer     1           0           0
2           others      text        0           NULL        0
3           hide_mail   boolean     0           1           0
4           time_zone   varchar     0           NULL        0

user_preferencesテーブルの構造

UserPreferenceとは

UserPreferenceとは、Redmineの中で定義されたモデルのひとつです。そのUserPreferenceが初めて登場したのは2006年12月4日の リビジョン 62 でした。この時はマイページに表示したいブロック(カレンダーや文書等)と配置場所(レイアウト)を利用者(ユーザ)ごとに管理(保持)するために UserPreference が作成されたようです。これ以降利用者(User)ごとのさまざまな(任意の)設定情報を格納しています。


UserPrefernceは当初はマイページの選択されたブロックを保持するために使われていた

My Redmine

他のモデル(テーブル)と異なる点

ユーザに関する任意の設定情報は主に UserPreference(実テーブル:user_preferences)の others 列(カラム)で管理しています。この others は ActiveRecord::AttributeMethods::Serialization.serialize を使って宣言します。実際のデータはHashですがこれを YAML形式 に文字列化(シリアライズ)して格納します。Hashなので特定のデータ型に縛られない点が他のモデル(テーブル)とは異なります。

UserPreference#others 宣言部(app/models/user_preference.rb)

class UserPreference < ActiveRecord::Base
  include Redmine::SafeAttributes

  belongs_to :user
  serialize :others

UserPreference の格納データ

$ RAILS_ENV=development bundle exec rails console
Loading development environment (Rails 5.2.3)
[1] pry(main)> user_preference = User.find(2).pref
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."type" IN ('User', 'AnonymousUser') AND "users"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  UserPreference Load (0.1ms)  SELECT  "user_preferences".* FROM "user_preferences" WHERE "user_preferences"."user_id" = ? LIMIT ?  [["user_id", 2], ["LIMIT", 1]]
=> #"0",
   :comments_sorting=>"asc",
   :warn_on_leaving_unsaved=>"1",
   :textarea_font=>"",
   :my_page_layout=>{"left"=>["issuesassignedtome"], "right"=>["issuesreportedbyme"], "top"=>["issuesupdatedbyme"]},
   :my_page_settings=>{},
   :recently_used_project_ids=>"1"},
 hide_mail: true,
 time_zone: "">
[2] pry(main)>

others への値の設定・取得は、othersに直接ではなくアクセサメソッドを介して行っています。アクセサメソッドを使うことで設定可能な属性(キー)を制限することができます。

アクセサメソッドの一部(app/models/user_preference.rb)

  def [](attr_name)
    if has_attribute? attr_name
      super
    else
      others ? others[attr_name] : nil
    end
  end

  def []=(attr_name, value)
    if has_attribute? attr_name
      super
    else
      h = (read_attribute(:others) || {}).dup
      h.update(attr_name => value)
      write_attribute(:others, h)
      value
    end
  end

  def no_self_notified; (self[:no_self_notified] == true || self[:no_self_notified] == '1'); end

アクセサメソッドの使用例

[2] pry(main)> user_preference.no_self_notified
=> false
[3] pry(main)> user_preference.no_self_notified = '1'
=> "1"
[4] pry(main)> user_preference.no_self_notified
=> true
[5] pry(main)> user_preference
=> #<UserPreference:0x00007f87acac3b28
 id: 2,
 user_id: 2,
 others:
  {:no_self_notified=>"1",
   :comments_sorting=>"asc",
   :warn_on_leaving_unsaved=>"1",
   :textarea_font=>"",
   :my_page_layout=>{"left"=>["issuesassignedtome"], "right"=>["issuesreportedbyme"], "top"=>["issuesupdatedbyme"]},
   :my_page_settings=>{},
   :recently_used_project_ids=>"1"},
 hide_mail: true,
 time_zone: "">
[6] pry(main)>

他のモデルでも同じ機能を持てばRedmineの使い勝手は良くなるはず

UserPreference はとても便利なモデルです。同様にシリアライズ(文字列化)したデータを格納するモデルは他にもあります(Role, CustomField, Query等)が、さまざまなデータ型を格納し利用しているのは UserPreference だけではないかと思います。UserPreferenceのおかげでRedmineの使い勝手が向上している画面(機能)もあります。


使い勝手が向上している一例
プロジェクトセレクタにブックマークと最近使用したプロジェクトを表示

個人的には Redmine.org - Feature #4016: Make app settings overridable at project level で UserPreference と同等の機能を持つ(ProjectPreference のような)モデルが組み込まれればプロジェクトごとの設定項目を拡張することも容易になる (画面テーマなどもプロジェクト単位で設定が可能になる)はずです。これによりRedmineはさらに使い勝手が良くなると思います。

Redmineには特徴的なモデルや機能(処理)が他にもありそうです。今後もRedmine改善作業を行いながら新たな技術や実装方法を見つけ出したいと思います(宝探しをしているような気持ちもありますけど)。


Redmine demo

こちらの記事もオススメです!
ファーエンドテクノロジーによるRedmine開発状況(2019年1〜4月)
ファーエンドテクノロジーによる2019年1月〜4月のRedmineの開発状況です。注目している新機能やイチ押し機能をご紹介!
ShowNetの裏側を見た!
InteropのShowNetテクニカルツアー&セッションに参加。NOC内部など裏側にも入り、スタッフの情熱を肌で感じました。
「Redmine 4.1 新機能選抜総選挙」で紹介できなかった新機能 10選
redmine.tokyoでRedmine4.1の新機能16個を紹介。そのほか紹介できなかった便利な新機能10個をピックアップして紹介します。
初めて行った韓国で驚いたこと・印象に残ったこと
GWに旅行で韓国へ。料理は量が多くて安い、電車賃も安い韓国と日本との違いを楽しみました。
なんでこの機能Redmineにないの??の裏事情
Redmineで「こんな機能あったらいいのにな」と思う機能があります。どういった理由で作業が止まっているのでしょうか。
ファーエンドテクノロジーからのお知らせ(2024/03/27更新)
2024年4月14日 オライリー本の全冊公開日のお知らせ(もくもく勉強会も同時開催)
ファーエンドテクノロジーが所蔵するオライリー本(全冊)公開日のご案内です。公開日には「もくもく勉強会」も同時開催します。
My Redmine スタンダードプランおよびAdminサポートデスクプランの料金改定のお知らせ【2024年4月ご利用分より】
2024年4月ご利用分より、My Redmine スタンダードプラン(民間企業・個人向け及び官公庁向け)とAdminサポートデスクプランの料金を改定いたします。
My Subversion 一部のプラン・料金改定のお知らせ【2024年4月ご利用分より】
2024年4月ご利用分より、My Redmine スタンダードプラン(民間企業・個人向け及び官公庁向け)とAdminサポートデスクプランの料金を改定いたします。
My Subversion ストレージ容量増量のお知らせ(一部プランを除く)
My Subversionではミディアムプラン以上の各プランのストレージ容量を増量します。
Redmineの最新情報をメールでお知らせする「Redmine News」配信中
新バージョンやセキュリティ修正のリリース情報、そのほか最新情報を迅速にお届け