最後のエントリにする

気づいたら約1ヶ月間があいてしまった。

突然ですが、この度会社を辞めることになりました。

退社するまではまだ間がある(来年2月頃)ので、ここ最近はいろいろと身の振り方を考えていましたが、転職するのは年齢的に実質無理だろうし、フリーというか、当面は派遣形態で仕事をして行くことになると思います。

そうなると、仕事先で自分のこと知ってもらうのにBlogとかは手っ取り早い手段なのですが、本Blogのタイトルはあんまりなので、本日で更新をやめることにしました。
(別にタイトルを変えればいいのですが、気分を一新したいこともあるので...)

正直、今となってはなんでこんなタイトルつけたのかわからないんスよ...
始めた当時は余程卑屈になっていたのかなぁ...

あまり役に立つことは書けませんでしたが、コメント、トラックバックや、ブクマしてくれた皆さん、有難うございました。
ここの更新は終わりにしますが、近いうちに、遅くとも年明けにはどこかでまた始めたいと考えています。

それでは、おしまい。

[trac]勤務状況をレポートしてみる

「規則正しい生活をしよう」などと、ひと月程前に書いたような記憶もあるが...
ブログにはりつけている「早起き生活」のグラフを見ればわかるとおり、ここ一ヶ月は惨澹たる状況でした。

うちの会社はプロジェクトごとに収益管理を行っていて、2週ごとに各プロジェクトに関わった工数を申告する仕組みになっている。
一つのプロジェクトに集中できれば「労働時間=工数」となり問題はないが、複数のプロジェクトに関わっていたりすると、申告のときにどのくらいの比率で関わったか、とか、いろいろ調整するのが面倒だったりするので、今月から労働時間をtracに実績時間として入力するようにしてみた*1

で、tracでレポート出力するとこんなイメージになる。

上から4,5番めが僕の本来の業務、ここ二週間は他のプロジェクトサポートが多かったことがわかる。
ちなみに、この業界、普通一週間は40Hだよね?

このレポートを出力するSQLを以下に示す(PostgreSQL用)。5:00を一日の区切りとしてある(爆)。変更するには「3600*5」の部分を修正してください。また、長くなるため横軸の週数は3週にしてあるので、必要であれば同じ要領でカラムを増やしてくださいな(interval 'n week'の部分を変更する)。
あと、以前のエントリで書いたように複数リポジトリ参照可能としてあるので、単一リポジトリの場合は以下の箇所を変更する必要があります。

    • all_ticket, all_ticket_change のかわりに ticket, ticket_changeを使用
    • schemaカラムを無視
SELECT
  author as __group__,
  schema as "リポジトリ",
  component as "JOB",
  TO_CHAR(SUM(CASE WHEN date_trunc('week', to_timestamp(tbl.time - 3600*5))
              = date_trunc('week', CURRENT_DATE)
           THEN hours ELSE 0 END),'90.00') as "今週",
  TO_CHAR(SUM(CASE WHEN date_trunc('week', to_timestamp(tbl.time - 3600*5))
              = date_trunc('week', CURRENT_DATE) - interval '1 week'
           THEN hours ELSE 0 END),'90.00') as "前週",
  TO_CHAR(SUM(CASE WHEN date_trunc('week', to_timestamp(tbl.time - 3600*5))
              = date_trunc('week', CURRENT_DATE) - interval '2 week'
           THEN hours ELSE 0 END),'90.00') as "2週前"
FROM(
  SELECT author, 
     c.time,
     t.schema, t.component,
     to_number(
       case newvalue
         when '' then '0'
         else newvalue
       end,'99.99') as hours
  FROM all_ticket_change c
    INNER JOIN all_ticket t ON c.schema = t.schema AND c.id = t.id
  WHERE field = 'hours'
)  as tbl
GROUP BY author, schema, component
ORDER BY author, schema, component

それでは。また来月からがんばる。

*1:ていうか、入力し忘れたときはDBを直接操作して強引に勤怠の労働時間にあわせた。ちゃんと工数管理するにはここら辺のリカバー手段を設ける必要がありますね。

[trac]会社のtrac環境

火を噴いたプロジェクトも収束に向かってきており、ようやく少し余裕が出てきた。
前のエントリでも少し触れたが、会社のtrac環境を実際どう構築しているか書いてみよう。

データベース環境

PostgreSQLを使用している。データベースは "trac_db" という名称で1つ作り、リポジトリごとにユーザ/スキーマを作成する方針としている。
以下のスクリプトは現在の環境をpgAdminで覗いて書き出してみた。(固有情報は適当に書き換えてあります)

実はPostgresの権限とか、仕組みがよくわかっていないため、そこらへんの指定の仕方は行き当たりばったりでとりあえず使えるようにした感じなので、あまり参考にしないほうがいいかもしれません。
うまい権限設定ポリシーとかあればコメントいただけるとうれしいです。

CREATE TABLESPACE trac_space
  OWNER postgres
  LOCATION '表領域を置く物理ディレクトリ';

CREATE ROLE trac_group
  NOSUPERUSER INHERIT CREATEDB NOCREATEROLE;

CREATE DATABASE trac_db
  WITH OWNER = postgres
       ENCODING = 'UTF8'
       TABLESPACE = trac_space;
GRANT TEMPORARY ON DATABASE trac_db TO public;
GRANT ALL ON DATABASE trac_db TO trac_group;

新しいリポジトリを作成する際は、以下のようなスクリプトスキーマとそこに接続するユーザを作成する。(xxxxを作成するリポジトリの名称にあわせて書換)

CREATE ROLE trac_xxxx LOGIN PASSWORD 'trac_xxxx'
  CREATEDB
   VALID UNTIL 'infinity'
   IN ROLE trac_group;
	
CREATE SCHEMA trac_xxxx
  AUTHORIZATION trac_xxxx;
GRANT ALL ON SCHEMA trac_xxxx TO GROUP trac_xxxx;
GRANT ALL ON SCHEMA trac_xxxx TO GROUP trac_group;

PostgreSQLで複数スキーマをまたぐ参照

tracリポジトリが複数存在するため、管理する立場からすると全体を見渡せる仕組みがあるとうれしいので、全スキーマのticketテーブルをUNIONするビューを作成する。
ただし普通に作成すると、リポジトリを追加するたびにビューを修正しなければならず煩わしいので、以下のようなプロシージャを作成した。
この関数にselect文を渡すと、存在する全スキーマに対してそのSQLを実行し、結果をUNIONして返す。
tracに限らず、PostgreSQLで複数スキーマに対する任意のクエリの実行結果を得たいときには使えると思う。

CREATE OR REPLACE FUNCTION query_all_schema(text) RETURNS SETOF record AS
$BODY$
DECLARE
    ref information_schema.schemata%rowtype;
    cur refcursor;
    rec record;
    esql text;
BEGIN
    FOR ref IN select * from information_schema.schemata where schema_owner <> 'postgres'
    LOOP
        esql := replace($1, '$$', ref.schema_name);

        OPEN cur FOR EXECUTE esql ;
        LOOP
            FETCH cur INTO rec;
            EXIT WHEN NOT FOUND;
            RETURN NEXT rec;
        END LOOP;
        CLOSE cur;

    END LOOP;
    RETURN;
END;
$BODY$
LANGUAGE 'plpgsql' STABLE;

[trac]システム開発の現場にtracを

社内の他のプロジェクトが火を噴いた。

例によって私を含むメンバーが集められ、対策のため打合せをしたが、相変わらずExcel に課題入力して、メンバーに状況を聞いてメンテナンスするということをリーダーが1人でやってる。
せっかくtracを使える環境を作ったのに...

2年ほど前に自分のプロジェクトが火を噴き、他の仕事している社員をみんな集め、助けてもらったことがある。

当時、残課題の管理をExcelで行っていたが、忙しさが尋常ではなかった為、状況の確認のため全員を集めて打合せをしたり、みんなに状況を聞きまわって管理表のメンテナンスをするのが煩わしく、同じような表フォーマットをAccessでつくり、ファイルサーバに置いて対応状況を入れてもらうようにした。

メンテナンスが煩わしいということもあったが、もっと問題だったのは、Excelを使った管理表では、

    • あとどのくらい残課題があるか
    • あとどのくらい工数がかかるか
    • 着手中の課題について、どこまで対応したのか

といった、必要な情報を得られなかったり、情報を得るために加工が必要だったこと。

Accessにしたことにより、残課題や工数もクエリ一発で計算できるようになったし、課題ごとに子レコードを作って対応履歴も入れられるようにしたため、管理はすごくやりやすくなった。
一応社内に「こんなん作りました」ってことでお知らせをしたが、無反応。まぁ、フォームとか作らずにテーブルに直接データを入れる形だったから、多少使いにくかったかもしれないけど...
それよりも、自分にとっては上に挙げたようなメリットのほうが大きかった。

そんなわけで、昨年tracの存在を知った時は衝撃だった。

「俺のやりたいことがほとんどできてるじゃん」
「去年(火噴いたとき)知っていたらどれほど楽だったろう」

自宅でのみながらセットアップを始め、「こいつはすげー」と1人で盛り上がって酒が進んだ挙句、次の日会社を休んでしまった...

それからほぼ1年、ネットでもわりと情報を見掛けるようになってきており、Webサービスや個人プログラマの開発環境としては当り前に使われるようになってきたが、上述のようにまだまだわれわれのような業務システム開発の現場では一般的ではないみたい。
そこで、自分が今の会社に導入する上で気を付けたポイントや、使ってきて気づいたことなどを書いてみる。

環境構築

  • DBにPostgreSQLを使う
    • tracのデフォルトはSQLiteだが、DBデータを取り出していろいろ加工したりできた方がよいと思い、PostgreSQLを選択。
  • TimingAndEstimationプラグイン
    • 工数の見積/実績管理を行うために導入。
  • ScrumBurndownプラグイン
    • TimingAndEstimationとあわせ、残工数の減り具合から期日までに作業が完了するのかの見極めに使用。
  • リポジトリは顧客単位に複数作成、その他全体を見渡せる管理リポジトリを作成
    • 複数プロジェクトを扱えないので、このようにした。新規リポジトリを簡単に行えるバッチなどつくれば、プロジェクト単位でリポジトリ作成したほうが良いと思う。
    • リポジトリのチケットを見渡すビューなど作成した。

運用

  • 保守業務や仕様変更など、既存システムに対する保守、改修などには本来の使いかたで威力を発揮した。
  • 新規開発を経験したが、正直あまり効果的には使えなかった。

新規開発については、まぁ、慣習的にどうしてもウォーターフォール的な進め方をしてしまうので、もともとあまりそぐわないかもしれないが、うまい活用ができないか、これからもいろいろ試してみようと思う。

ホンダのコンセプトモデル「PUYO(プヨ)」

東京モーターショーに出展されるらしいけど「やわらか戦車」を意識してるのかなあ?
色も白だし、形もなんとなくそれっぽいんだよな。

[Rails]TopicPath(パンくずリスト)を作る

プラグインや実装例が結構あるのかと思いきや、あまり見当たらなかったので考えてみた。
以下のような方向で作ってみる。

  • 履歴はセッションに保存する。
  • パスには現ページまでの履歴を表示し、直前のページまではリンクとして表示する。
  • TopicPathのリンクからページにとんだ場合は、それ以降の履歴をクリアする。

パス構築のメソッド

必要なメソッドは、以下の2つ。

  • 履歴の初期化
  • 履歴の追加

履歴はURL情報を保時する必要があるため、コントローラ、アクション、パラメータのハッシュ配列で保持することにする。作成した配列はセッション中に:topicというキーで保存する。
という訳で、以下のようなコード。

  def init_topic(label)
     item = { :label => label,
              :controller => controller_name,
              :action => @action_name,
              :params => {} }
     session[:topic] = Array[item]
  end

  def put_topic(label)
    item = { :label => label,
             :controller => controller_name,
             :action => @action_name,
             :params => params }
    session[:topic].push(item)
  end

引数のlabelはTopicPathに表示される文字列となる。
これらは、作成した各コントローラのアクションメソッドの中から呼ぶ必要があるため、ApplicationControllerクラスに定義する。
(ApplicationControllerは全てのコントローラクラスの基底クラスになる)

TopicPathの表示

Viewの中からは@session[:topic]で履歴情報が参照可能なため、以下のようなコードで表示できる。

<%  if @session[:topic] != nil then
      index = 0
      for topic in @session[:topic] %>
        <%= ' > ' unless index == 0 %>
<%      if index == @session[:topic].size - 1 then %>
        <%= topic[:label] %>
<%      else %>
        <%= link_to topic[:label], :action => 'topic', :id => index %>
<%      end
        index += 1
      end
    end %>

正直、あんまりきれいなコードではないなぁ...
ループの中でやりたいことは、

  • 要素間に ' > ' を出力する (-> 先頭要素を除き、要素の前に' > 'を出力する)
  • 最後の要素はリンクにしない

この2つなので、要素がコレクションの先頭/最後かを判別できればそのほうがいいんだけど...
安易にindexを用いて判断してしまった。

TopicPathからのジャンプ

上のコードで何気にリンク先アクションを'topic'と書いているが、そのアクションを実行するコードはApplicationController内に書いている。そうすることによって、現在のコントローラを意識せずに実行される。
topicアクションのコードは以下のとおり。

  def topic
    index = params[:id].to_i
    topiclist = session[:topic]
    item = topiclist[index]
    session[:topic] = topiclist[0..index-1]
    redirect_to :controller => item[:controller],
                :action => item[:action],
                :params => item[:params]
  end

パラメータとして渡されたインディクスに相当するリンク先情報をセッションから取得し、以降の履歴をクリアしてリンク先にredirectしている。

アプリケーションへの組込み

以上がTopicPath実装の仕組み。実際に作成するアプリケーション内で上記メソッドを呼ぶが、基本的に以下の2つを行えばよい。

  • ログイン時に履歴初期化 (init_topic呼出)
  • 履歴保存したいアクション内で put_topic を呼び出す
# ログイン時に初期化
  def login
    〜
    init_topic('Home')
    〜
  end

# アクション実行時にput_topic呼出
  def list
    〜
    put_topic('List')
    〜
  end

課題

とりあえず以上の形で実装できたが、TopicPathの大きさに制限を設けていないので、リンクをぐるぐるたどっているとエラいことになる。
ここら辺は割り切って、直近いくらかを表示するとか、繰り返しを排除するとか、いろいろやりかたが考えられるが、アプリケーションによっていろいろポリシーも変わってくると思う。