Railsでストアドを呼び出して結果を取得する方法

SQL Serverの場合

ストアドプロシージャを用意する。

INの文字列をテーブル登録して、OUT='okaeshi'、888を返す訳分からんプロシージャ。

CREATE PROCEDURE TEST_PROC
  @Param1 varchar(10)
, @Param2 varchar(100) OUTPUT
AS
BEGIN
  INSERT INTO TESTS(COLUMN1) values(@Param1)
  SELECT @Param2 = 'okaeshi'
  RETURN 888
END
SQL Serverのストアド用モデルを用意する。
class SqlServerStoredProcedure < ActiveRecord::Base
  def self.proc_exec(sql)
    find_by_sql(sql)
  end

  establish_connection "sqlsvr"
end
呼び出し方
sql = "declare @rtn int"
sql += ",@Param2 varchar(100);"
sql += "exec @rtn = test_proc"
sql += " @Param1 = N'" + "ACBDEFG" + "'"
sql += ",@Param2 = @Param2 output;"
sql += "select 'rtns' = @rtn"
sql += ",Param2 = @Param2;"
@rtns = SqlServerStoredProcedure.proc_exec(sql)
結果をViewに表示

結果はHashの配列で返されるので・・・

[{"rtns"=>888, "Param2"=>"okaeshi"}]

View側は次のように取り出す。

<p><%=h @rtns.first["rtns"] %></p>
<p><%=h @rtns.first["Param2"] %></p>

呼び出す時にSQLを作る必要があるのだが、もっとすっきりした方法がいいな・・・

Oracleの場合

参考:
http://mikebradford.org/2008/3/11/calling-an-oracle-stored-procedure-in-rails
ActiveRecord, Oracle and stored procedures | Software bits and pieces

ストアドプロシージャを用意する。

INの文字列をOUTで返すプロシージャ。

create or replace procedure testproc(
  instr in char
, outstr out char
)
as
begin
  outstr := instr;
end;
/
Oracleのストアド用モデルを用意する。
class OraSp < ActiveRecord::Base
  def self.proc_exec
    conn = connection.raw_connection
    sql = "DECLARE
            outstr char(100);
          BEGIN
            testproc(:instr, outstr);
            :outstr := outstr;
          END;"
    cursor = conn.parse(sql)
    cursor.bind_param(":instr", "AAAAA")
    cursor.bind_param(":outstr", nil, String, 100)
    cursor.exec
    outstr = cursor[":outstr"]
    cursor.close
    outstr
  end

  establish_connection "oracle"
end
呼び出し方
  @outstr = OraSp.proc_exec
結果をViewに表示
<p><%=h @outstr %></p>

bind_paramで苦労した・・・
OUTパラメータは、プロシージャの引数の位置に設定できないみたい。

ダメだった例:

    sql = "BEGIN
            testproc(:instr, ★:outstr★);
          END;"

OKだった例:

    sql = "DECLARE
            outstr char(100);
          BEGIN
            testproc(:instr, outstr);
            ★:outstr★ := outstr;
          END;"

Redmine1.0.0にアップデート

待ちに待ったRedmine1.0.0が7/18にリリースされた。
早速アップデートしてみる。(trunk -> 1.0.0)

Redmineのバックアップ

  • Redmine本体のバックアップ
cd /var
cp -rp redmine redmine_100719
  • データベース(MySQL)のバックアップ
mysqldump -c redmine | gzip >/home/redmine_100719.sql.gz

trunkから1.0.0へアップデート(切り替え)

cd /var/redmine
svn info
-----------------------------------------------
パス: .
URL: http://redmine.rubyforge.org/svn/trunk
リポジトリのルート: http://redmine.rubyforge.org/svn
リポジトリ UUID: e93f8b46-1217-0410-a6f0-8f06a7374b81
リビジョン: 3768
ノード種別: ディレクトリ
準備中の処理: 特になし
最終変更者: edavis10
最終変更リビジョン: 3768
最終変更日時: 2010-06-10 07:01:21 +0900 (木, 10  6月 2010)
cd /var/redmine
svn switch http://redmine.rubyforge.org/svn/branches/1.0-stable
cd /var/redmine
rake db:migrate RAILS_ENV="production"
  • キャッシュとセッションの消去
cd /var/redmine
rake tmp:cache:clear
rake tmp:sessions:clear
  • リスタート
cd /var/redmine
touch tmp/restart.txt

感想

trunkから1.0.0への切り替えなので、当然ながら特に変化はなかったw
(0.9からは沢山の機能が追加されている。)
個人的にはsubtaskingが正式に採用されたことが一番大きい。
まぁまだリリース候補版なんで気長に付き合おう。


兎に角、1.0.0をリリース出来たことに”おめでとう”と”ありがとう”の意をお伝えしたい。
Jean-Philippe Langさん初め開発者の方々、"Congratulations" and "Thank you".

1.0.0のリリース情報

http://www.redmine.org/versions/show/14

RedmineのPDF文字化けと日付表示と統計表示

PDFの文字化けの対応

完璧ではないらしいが、やってみる。

  • 参考URL

PDFおよびCSVの文字化けを回避する — Redmine.JP
Google グループ
Redmineの文字化けとか勝手にまとめ: これ本番ですか?
徒然さめざめ Redmine Hack! - pdfの文字化け fix編 -
http://www.maruhisa.org/2009/07/redmine_pdf_problem/
徒然さめざめ 続・何回目? Redmine pdf日本語化hack


エンコードSJISに変更する。

  • config/locales/ja.yml
-  general_csv_encoding: CP932
-  general_pdf_encoding: CP932
+  general_csv_encoding: SJIS
+  general_pdf_encoding: SJIS

pdf.rbのMultiCellの引数(文字列)にひたすら.tosjisをかます

-          pdf.MultiCell(155,5, (show_value custom_value),"R")
+          pdf.MultiCell(155,5, (show_value.tosjis custom_value.tosjis),"R")

-        pdf.MultiCell(155,5, @issue.description,"BR")
+        pdf.MultiCell(155,5, @issue.description.tosjis,"BR")

-              pdf.MultiCell(190,5, changeset.comments)
+              pdf.MultiCell(190,5, changeset.comments.tosjis)

-            pdf.MultiCell(190,5, journal.notes)
+            pdf.MultiCell(190,5, journal.notes.tosjis)

履歴に何か書くと次のエラーを吐いて落ちる・・・

Processing IssuesController#show to pdf (for 192.168.24.83 at 2010-07-09 01:10:36) [GET]
  Parameters: {"format"=>"pdf", "action"=>"show", "id"=>"3", "controller"=>"issues"}

TypeError (nil can't be coerced into Fixnum):
  lib/redmine/export/pdf.rb:294:in `issue_to_pdf'
  lib/redmine/export/pdf.rb:283:in `each'
  lib/redmine/export/pdf.rb:283:in `issue_to_pdf'
  app/controllers/issues_controller.rb:128:in `show'
  app/controllers/issues_controller.rb:123:in `show'
  passenger (2.2.11) lib/phusion_passenger/rack/request_handler.rb:92:in `process_request'
  passenger (2.2.11) lib/phusion_passenger/abstract_request_handler.rb:207:in `main_loop'
  passenger (2.2.11) lib/phusion_passenger/railz/application_spawner.rb:418:in `start_request_handler'
  passenger (2.2.11) lib/phusion_passenger/railz/application_spawner.rb:358:in `handle_spawn_application'
  passenger (2.2.11) lib/phusion_passenger/utils.rb:184:in `safe_fork'
  passenger (2.2.11) lib/phusion_passenger/railz/application_spawner.rb:354:in `handle_spawn_application'
  passenger (2.2.11) lib/phusion_passenger/abstract_server.rb:352:in `__send__'
  passenger (2.2.11) lib/phusion_passenger/abstract_server.rb:352:in `main_loop'
  passenger (2.2.11) lib/phusion_passenger/abstract_server.rb:196:in `start_synchronously'
  passenger (2.2.11) lib/phusion_passenger/abstract_server.rb:163:in `start'
  passenger (2.2.11) lib/phusion_passenger/railz/application_spawner.rb:213:in `start'
  passenger (2.2.11) lib/phusion_passenger/spawn_manager.rb:262:in `spawn_rails_application'
  passenger (2.2.11) lib/phusion_passenger/abstract_server_collection.rb:126:in `lookup_or_add'
  passenger (2.2.11) lib/phusion_passenger/spawn_manager.rb:256:in `spawn_rails_application'
  passenger (2.2.11) lib/phusion_passenger/abstract_server_collection.rb:80:in `synchronize'
  passenger (2.2.11) lib/phusion_passenger/abstract_server_collection.rb:79:in `synchronize'
  passenger (2.2.11) lib/phusion_passenger/spawn_manager.rb:255:in `spawn_rails_application'
  passenger (2.2.11) lib/phusion_passenger/spawn_manager.rb:154:in `spawn_application'
  passenger (2.2.11) lib/phusion_passenger/spawn_manager.rb:287:in `handle_spawn_application'
  passenger (2.2.11) lib/phusion_passenger/abstract_server.rb:352:in `__send__'
  passenger (2.2.11) lib/phusion_passenger/abstract_server.rb:352:in `main_loop'
  passenger (2.2.11) lib/phusion_passenger/abstract_server.rb:196:in `start_synchronously'

Rendering /var/redmine/public/500.html (500 Internal Server Error)

とりあえず履歴と関連するリビジョンは印字してもらう必要ないので、その印字処理を削除する。

#        if issue.changesets.any? && User.current.allowed_to?(:view_changesets, issue.project)
#          pdf.SetFontStyle('B',9)
#          pdf.Cell(190,5, l(:label_associated_revisions), "B")
#          pdf.Ln
#          for changeset in issue.changesets
#            pdf.SetFontStyle('B',8)
#            pdf.Cell(190,5, format_time(changeset.committed_on) + " - " + changeset.author.to_s)
#            pdf.Ln
#            unless changeset.comments.blank?
#              pdf.SetFontStyle('',8)
#              pdf.MultiCell(190,5, changeset.comments.tosjis)
#            end 
#            pdf.Ln
#          end
#        end

#        pdf.SetFontStyle('B',9)
#        pdf.Cell(190,5, l(:label_history), "B")
#        pdf.Ln 
#        for journal in issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
#          pdf.SetFontStyle('B',8)
#          pdf.Cell(190,5, format_time(journal.created_on) + " - " + journal.user.name)
#          pdf.Ln
#          pdf.SetFontStyle('I',8)
#          for detail in journal.details
#            pdf.Cell(190,5, "- " + show_detail(detail, true))
#            pdf.Ln
#          end
#          unless journal.notes.blank?
#            pdf.SetFontStyle('',8)
#            pdf.MultiCell(190,5, journal.notes.tosjis)
#          end  
#          pdf.Ln
#        end

ブラウザの機能で印字する場合は、次のツールで余計な部分を取り除くことにしよう。
https://addons.mozilla.org/ja/firefox/addon/193270/

ガントチャートに日付表示

  • 参考URL

http://labo-ss.net/blog/itroom/item_46.html
プロジェクト管理ツールRedMineのガントチャートカスタマイズ - + YOSHIKI & Violet UK FAN SITE -Blind Tears- 管理人の日記 +

  • app/views/gantts/show.html.erb(r3756)
44 headers_height = header_heigth
45 show_weeks = false
46 show_days = false
47 show_day_num = false #★追加★
48 
49 if @gantt.zoom >1
50     show_weeks = true
51     headers_height = 2*header_heigth
52     if @gantt.zoom > 2
53         show_days = true
54         headers_height = 3*header_heigth
55         if @gantt.zoom > 3                 #★追加★
56           show_day_num = true              #★追加★
57           headers_height = 4*header_heigth #★追加★
58         end                                #★追加★
59     end
60 end

150 <!-- ★追加★ start -->
151 <%
152 #
153 # Days headers Num
154 #
155 if show_day_num
156     left = 0
157     height = g_height + header_heigth * 2 - 1
158     wday = @gantt.date_from.cwday
159     day_num = @gantt.date_from
160     (@gantt.date_to - @gantt.date_from + 1).to_i.times do
161     width =  zoom - 1
162     %>
163     <div style="left:<%= left %>px;top:37px;width:<%= width %>px;height:<%= height %>px;font-size:0.7em;<%= "background:#f1f1f1;" if wday > 5 %><%= "color:blue;" if wday == 6 %> <%= "color:red;" if wday == 7 %        >" class="gantt_hdr">
164     <%= day_num.day %>
165     </div>
166     <%
167     left = left + width+1
168     day_num = day_num + 1
169     wday = wday + 1
170     wday = 1 if wday > 7
171     end
172 end %>
173 <!-- ★追加★ end -->

175 <%
176 #
177 # Days headers
178 #
179 if show_days
180         left = 0
181         height = g_height + header_heigth - 1
182         top = (show_day_num ? 55 : 37) #★追加★
183         wday = @gantt.date_from.cwday
184         (@gantt.date_to - @gantt.date_from + 1).to_i.times do
185         width =  zoom - 1
186         %>
187 <!--    <div style="left:<%= left %>px;top:37px;width:<%= width %>px;height:<%= height %>px;font-size:0.7em;<%= "background:#f1f1f1;" if wday > 5 %>" class="gantt_hdr"> -->
188         <div style="left:<%= left %>px;top:<%= top %>px;width:<%= width %>px;height:<%= height %>px;font-size:0.7em;<%= "background:#f1f1f1;" if wday > 5 %><%= "color:blue;" if wday == 6 %><%= "color:red;" if         wday == 7 %>" class="gantt_hdr"> <!-- ★変更★ -->
189         <%= day_name(wday).first %>
190         </div>
191         <%
192         left = left + width+1
193         wday = wday + 1
194         wday = 1 if wday > 7
195         end
196 end %>


最大の大きさの時(拡大+4)に日付を表示してくれる。
なかなかいい感じ。先人の方々に感謝。

統計が表示されない

ruby1.8のバグらしい。
Redmineで統計を表示する: これ本番ですか?
下記の通り修正すれば表示出きるようになった。

  • /usr/lib/ruby/1.8/rexml/document.rb
186 #          if transitive
187           if trans

Redmineの初回アクセスが遅い

前から無通信状態が続くとRailsが落ちるとは思っていたが、どうやらPassengerのデフォルトの設定で2分間アクセスがない場合はRailsを落とす仕様らしい。Passengerの設定を変えることで、このちょっとしたイライラから開放されるらしいのでやってみる。

Passengerの設定変更

Railsをプールする時間を12時間に設定(長すぎるかw)

RailsPoolIdleTime 43200
service httpd restart

毎朝始業前にRedmineにアクセスしておく

次のスクリプトを毎朝4時に起動するcron.dailyに登録

#!/usr/bin/env ruby
require 'open-uri'
open("http://localhost/redmine")
  • 実行権限付与
chmod +x /etc/cron.daily/redmine-open

Subversionのpre-commitフックを強化

前に”コミットに制約を設ける”と題してpre-commitのスクリプトを作成したが、不完全だったので作り直してみた。

コミットに制約を設ける(強化版)

制約内容:

  1. 参照用(refs)のチケット番号が指定されていて、そのチケットがRedmimeに存在すること。
  2. 完了用(fixes)のチケット番号が指定されていた場合は、そのチケットがRedmineに存在して、かつ、完了していないこと。
  • 修正したpre-commit
#!/usr/bin/env ruby

#REPOS="$1"
#TXN="$2"

# Make sure that the log message contains some text.
#SVNLOOK=/usr/bin/svnlook
#$SVNLOOK log -t "$TXN" "$REPOS" | \
#   grep "[a-zA-Z0-9]" > /dev/null || exit 1

# Check that the author of this commit has the rights to perform
# the commit on the files and directories being modified.
#commit-access-control.pl "$REPOS" "$TXN" commit-access-control.cfg || exit 1

#----------------------------------
#デバッグ出力と標準エラー出力の関数
#----------------------------------
def dbg(format, *arg)
  f = open("/tmp/pre-commit.log", "a")
  printf(f, format, *arg)
  f.close
  printf(STDERR, format, *arg)
end

#--------------------------------
#指定チケットの進捗率を求める関数
#--------------------------------
def getTicketRatio(ticketNo)
  redmine = <<-`__REDMINE__` 
#{MYSQL} -s -u root redmine <<__SQL__ 
select 
    issues.done_ratio
from 
    issues 
where
    issues.id = #{ticketNo}
; 
__SQL__
  __REDMINE__

  #数値でない場合は nil を返す
  if redmine.chomp.strip =~ /^[0-9]+$/ then
    return redmine.chomp.strip.to_i
  else 
    return nil
  end
end 

#引数セット
REPOS = ARGV[0] 
TXN = ARGV[1]

#外部プログラムのパスを設定
SVNLOOK = "/usr/bin/svnlook" 
MYSQL = "/usr/bin/mysql"

#デバッグ
dbg("--- %s ---\n", Time.now)
dbg("REPOS=[%s], TXN=[%s]\n", REPOS, TXN)

#チェック用変数初期化
refsCnt = 0
errCnt = 0

#コミットログを取得
log = `#{SVNLOOK} log -t #{TXN} #{REPOS}`
log.each do |item|
  #ログを加工
  item.chomp!                      #改行コードを取り除く
  item.strip!                      #前後の空白を取り除く
  item.downcase!                   #小文字に変換
  item.gsub!("references", "refs") #参照用キーワードを統一
  item.gsub!("IssueID", "refs")    #参照用キーワードを統一
  item.gsub!("closes", "fixes")    #修正用キーワードを統一

  #参照用チケットをチェック
  refs = item.scan(/refs[ ]*[#|0-9| |,]+/)
  refs.each do |ref|
    tickets = ref.scan(/#[0-9]+/)
    tickets.each do |ticket|
      #進捗率が求めれない場合はエラー
      if getTicketRatio(ticket.gsub("#", "")).nil? then
        dbg("Ticket %s is nothing.\n", ticket)
        errCnt += 1
      else
        dbg("Ticket %s is validity.\n", ticket)
        refsCnt += 1
      end
    end
  end

  #修正用チケットをチェック
  fixes = item.scan(/fixes[ ]*[#|0-9| |,]+/)
  fixes.each do |fix|
    tickets = fix.scan(/#[0-9]+/)
    tickets.each do |ticket|
      #進捗率が求めれない場合はエラー
      #進捗率が100%の場合はエラー
      if getTicketRatio(ticket.gsub("#", "")).nil? then
        dbg("Ticket %s is nothing.\n", ticket)
        errCnt += 1
      elsif 100 == getTicketRatio(ticket.gsub("#", "")) then
        dbg("Ticket %s is complite.\n", ticket)
        errCnt += 1
      else 
        dbg("Ticket %s is validity.\n", ticket)
      end
    end
  end
end
  
#参照用チケットがない場合はエラー
if 0 == refsCnt then
  dbg("No refs ticket.(refs #)\n") 
  errCnt += 1
end

#エラーがある場合は exit 1 で終了
if 0 < errCnt then
  dbg("--- NG %s ---\n", Time.now)
  exit 1
end

# All checks passed, so allow the commit.
dbg("--- OK %s ---\n", Time.now)
exit 0

Redmienのリポジトリ情報の自動読み込みとバーンダウンチャート

コミット直後にリポジトリを読み込むようにする

Redmine.JP Blogの通りに設定
小技(0.9): コミットと同時にリポジトリの情報を取得する | Redmine.JP Blog

  • post-commitの内容
#!/bin/sh
REPOS="$1"
REV="$2"

#commit-email.pl "$REPOS" "$REV" commit-watchers@example.org
#log-commit.py --repository "$REPOS" --revision "$REV"

/usr/bin/ruby -ropen-uri -e 'open("http://localhost/redmine/sys/fetch_changesets?key=iIZbaD1eRPoW7Oy6Amue")'

バーンダウンチャートを見れるようにする(Charts pluginを入れる)

公式のWikiの通りにやってみる。

PluginCharts - Redmine


ただ、gitが必要なので先ずはそれをインストールする。

/etc/yum.repos.d/CentOS-Base.repoの末尾に以下の内容を追記する。

[dag]
name=Dag RPM Repository for Redhat EL5
baseurl=http://apt.sw.be/redhat/el$releasever/en/$basearch/dag
gpgcheck=1
enabled=1
gpgkey=http://dag.wieers.com/packages/RPM-GPG-KEY.dag.txt
  • gitをインストール
yum install git
  • OpenFlashChartをインストール
cd /var/redmine
./script/plugin install git://github.com/pullmonkey/open_flash_chart.git
  • Charts pluginをインストール
cd /var/redmine
git clone git://github.com/mszczytowski/redmine_charts.git vendor/plugins/redmine_charts
  • おまじない
rake db:migrate_plugins RAILS_ENV=production
cd /var/redmine
touch tmp/restart.txt
  • [管理]→[プロジェクト]でモジュールに"Chart"を追加する。
  • [管理]→[ロールと権限]で"Chart"の権限を設定する。
  • 感想

まずバーンダウンが良く分かってない。。。が、なんかかっちょいいし、見やすい。
とりあえず、開始日、期日、予定工数、作業時間をキチンと入力することで、進捗の度合いが分かるグラフを見ることができる。
作業時間の入力をキチンと運用できれば確実に使える!


以下、説明文のコピペ(こちらに倣ってredmine_chartsプラグインでバーンダウンチャートは表示可能: プログラマの思索)

バーンダウン
見積、経過および残りの時間を表しています
進捗とバーンダウン
進捗とバーンダウン(残り時間)。 各バージョンにおける作成日から(実際の)完了までのデータを表しています。
記録時間割合
時間はメンバ、チケット、活動、カテゴリごとに分類およびフィルタした記録時間の合計に比例しています
記録時間予定
メンバ、チケット、活動、カテゴリごとに分類およびフィルタして時間経過を表しています
記録時間分布
各チケットの見積時間と記録時間と残り時間の割合を表しています。この割合は100%以下になるべきです。見積時間のあるチケットのみ表示しています。左端に表示しているバーはこのプロジェクトの平均値を表しています。
チケットレート
指定された条件・グループでのチケット数を表しています。

RedmineにおけるSubversionの認証方法

Redmineの"committer"というグループに所属するユーザだけがコミットできるようにする認証方法を考えてみた。
前に特定のプロジェクトに所属するユーザだけコミット可能とする方法を考えてみたが、こっちの方が直かな。

<Location /svn>
   DAV svn
   SVNParentPath /home1/svnrepos
   <LimitExcept GET PROPFIND OPTIONS REPORT>
        AuthMySQLEnable         On
        AuthMySQLSocket         /var/lib/mysql/mysql.sock
        AuthMySQLHost           localhost
        AuthMySQLUser           redmine
        AuthMySQLPassword       redmine
        AuthMySQLDB             redmine
        AuthMySQLUserTable      "users"
        AuthMySQLNameField      "users.login"
        AuthMySQLUserCondition  "users.id in (select groups_users.user_id from users,groups_users where users.type = 'Group' and users.lastname = 'committer' and users.id = groups_users.group_id)"
        AuthMySQLPasswordField  "users.hashed_password"
        AuthMySQLPwEncryption   sha1
        AuthMySQLNoPasswd       Off

        AuthType Basic
        AuthName "Authorization Realm"
        Require valid-user
   </LimitExcept>
</Location>