sou's blog

落ち着いた華やかさがあり、上品に明るく陽気なさまを表す。

ruby2.0とrails4.0をrvmでインストールした

最近は、とてもとても忙しくてなかなかお家で開発作業を進めることができなかったけど、
手がけていたアプリがとりあえずリリースできて少しだけ落ち着いたので早速rubyを導入する。
ということで、ruby2.0、ails4.0(beta1)をインストールしてみた。

opensslのバージョンが古いとダメっぽいのでそこのupdateから始める。
(環境はmaxos10.7.5)
全部で結構時間かかったけどとりあえずできた。

brewのupdate

$ brew update 
$ brew install openssl


rvmのupdateからのruby2.0インストール

$ rvm get stable
$ rvm reload
$ rvm install 2.0.0
$ rvm use 2.0.0 --default

rails4.0 beta1のインストール

$ gem install rails -v 4.0.0.beta1

rails の seeds.rbの使い方について

連休だったので、久しぶりにC#から離れてRailsのお勉強。
もうすぐRuby2.0&Rails4.0がリリースされるみたいなので、もう一度参入するチャンス!
で、今回はRailsのseeds.rbの簡単な使い方をまとめる。

概要

  • マスタデータの投入。つまり初期データの投入に使える。

コマンド

rake db:seed

使い方

seeds.rbの編集

コマンドを呼び出すたびにseeds.rbの中身が追加されてしまうので、一回全部消去してidのautoincrementもクリアして入れなおす。
例えばBlogというモデルがあった場合は、一回delete_allを読んで


Blog.delete_all # 一回全部消して
Blog.connection.execute("delete from sqlite_sequence where name='blogs'") # autoincrementをクリアして
Blog.create(:name => "hoge blog") # データ投入

sqlite3で確認

コマンド呼び出したら実際にデータが投入されているか確認する

sqlite3 db/development.sqlite3

> select * from blogs;

注意点

日本語を扱う場合はファイルの先頭に以下を忘れないようにする。

# encoding: utf-8

依存関係がある場合は親のインスタンスを渡す

blog1 = Blog.create(:name => "hoge blog")
Entry.create(:blog => blog1, context => 'hogehogehoge')

Windowsアプリケーションで矢印キーとEnterでのフォーカス移動

というトンデモな仕様を実装しろとのこと。
明らかにWindows標準APIを無視した設計だったので、
仕様の再検討を!!ってまずお願いしたけど、一応調べてみた。

TextBoxなどでは、文字列にカーソル当たっている状態では左右キーは文字間を移動するのに使われる。
その為、キャレット位置が先頭のときに←を押すと前のコントロールへフォーカス
キャレット位置が最終のときに→を押すと次のコントロールへフォーカスという動きを考えた。
TextBoxには、SelectionStartというプロパティがあって簡単にキャレット位置を判定できたが、
こまったのはそれ以外のコントロールについて。
例えば、ComboBoxなんかも任意文字列が入力できるのでキャレット位置を取得したい。
TextBoxにあったSelectionStartプロパティが使えたらなーっと思ってたらこっちにもあった。

DRYじゃねぇ・・・。

とおもった。
TextBoxBaseクラスを継承してたり、Interfaceとかで提供されてて欲しかった。

一応コード
TextBoxのみ文字間移動を対応。その他については考慮していない
あと、実装したのは共通で利用するForm。(KeyPreviewプロパティをTrueにしておく)
共通で利用するFormを継承したFormで自動的に適用される。

        private void CommonForm_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Left)
            {
                if (this.ActiveControl is TextBox)
                {
                    TextBox textBox = (TextBox)this.ActiveControl;
                    if (textBox.SelectionStart != 0)
                    {
                        return;
                    }   
                }

                this.SelectNextControl(this.ActiveControl, false, true, true, true);
            }

            if (e.KeyCode == Keys.Right)
            {
                if (this.ActiveControl is TextBox)
                {
                    TextBox textBox = (TextBox)this.ActiveControl;
                    if (textBox.SelectionStart != textBox.Text.Length)
                    {
                        return;
                    }
                }

                this.SelectNextControl(this.ActiveControl, true, true, true, true);
            }

            if (e.KeyCode == Keys.Enter)
            {
                bool forward = e.Modifiers != Keys.Shift;
                this.SelectNextControl(this.ActiveControl, forward, true, true, true);
            }
        }

SVNリポジトリの使用方法について

Subversionの各リポジトリ(trunk、tags、branches)の運用方法について記載します。

次期バージョンの開発を行なう場合

次期バージョンの開発(新規機能追加や既存機能改修など)を行なう場合は trunk にて作業を行ないます。
その為、trunkは常に最も新しい状態(開発版)となっています。(不具合がある可能性も高い)

開発を終えたバージョンをリリースしたい

trunkでの開発を終えたバージョンをリリースする場合、 branchesにバージョン番号のディレクトリを作成 し、枝分かれさせます。
リリース作業はbranchesに作成したリポジトリで作業を行ないます。

(例)
sample_appシステムの場合、「sample_app/branches/2.1」といった具合

リリース後、不具合が発生したので対応したい

リリース後の不具合対応はbranchesで管理している該当のバージョンのリポジトリに対して修正を行ないます。
その為、 branchesでの修正内容は状況に応じてtrunkにマージ する必要があります。
(次期バージョンでは前のバージョンの不具合修正を反映させなくても良いなどのケースではマージする必要はないが、原則は常にbranchesの修正内容はマージすること。)

安定版として保存しておきたい

リリース後branchesでの不具合が収束し、安定版として保存しておきたい場合はbranchesからtagsへコピーします。
大幅な改修などメジャーバージョン番号が変わるような改修が入った場合、不測の事態に備えていつでも元に戻れるように前のバージョンを保存しておく必要があります。

(例)
大幅な業務変更のため、メジャーバージョン番号を1.Xから2.Xに変更する。
その際、1.Xの最終安定版のバージョンである1.9をtagsへスナップショットとして保存しておく。
万が一、2.X系リリース後に大きな不具合が発生し、1.X系に差し戻す場合はtagsで保存しておいた1.X系の最終安定版の1.9に置き換える。
sample_appシステムの場合、「sample_app/tags/1.0」といった具合。

proxy環境下でユーザ毎にrvmをインストールする

概要

proxy環境下でユーザ単位のrvmをインストールする。

curlのインストール

rvmインストール時に必要っぽいのでcurlが無い場合はインストールしておく。
念のためapt-getをupdateしておく。

$ sudo apt-get update
$ sudo apt-get install curl

proxyの設定

ユーザ毎に有効なの環境変数を.bash_profileに記載する。
curlのmanを見るとhttp_proxyのみ小文字という制限があったので
メンドクサイからhttp、httpsftpの使われそうなプロトコルに関して小文字、大文字でproxyを設定しておく

$ cd $HOME
$ vim ~/.bash_profile

.bash_profile

export http_proxy=":"
export https_proxy=":"
export ftp_proxy=":"
export HTTP_PROXY=":"
export HTTPS_PROXY=":"
export FTP_PROXY=":"

編集した.bash_profileをリロードする

$ source ~/.bash_profile

※.bash_profileがなければ作る

rvmのインストール

rvmのホームページを参考にコマンドを実行していく。
RVMのインストール

$ bash -s stable < <(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer)

.bash_profileのリロード

$ source ~/.bash_profile

rvmでrubyをインストールするのに必要なライブラリが表示されるので指示に従ってインストールする。

$ rvm requirements

Additional Dependencies:
# For Ruby / Ruby HEAD (MRI, Rubinius, & REE), install the following:
  ruby: /usr/bin/apt-get install build-essential openssl libreadline6 libreadlin e6-dev curl git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-0 libsqlite3-dev sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev ncurses-dev automake libtool bison subversion
$ sudo apt-get install build-essential openssl libreadline6 libreadlin e6-dev curl git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-0 libsqlite3-dev sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev ncurses-dev automake libtool bison subversion

ruby1.9.3のインストール

$ rvm install 1.9.3

rvmでrubyを使用する

デフォルトで使用するrubyをインストールした1.9.3にする

$ ruby -v
ruby 1.8.7 (2010-08-16 patchlevel 302) [i686-linux]
$ rvm use 1.9.3 --default
$ ruby -v
ruby 1.9.3p125 (2012-02-16 revision 34643) [i686-linux]

システムのrubyに戻す場合

$ rvm use system

rvmにインストールされているバージョンを確認する

$ rvm list

Redmineのplugin開発

今回、Redmineのプラグインを開発してみてHookやPlugin用の設定ページの作成や独自rakeコマンド、Mail送信機能拡張など様々な機能を触ってみて思ったよりも便利に開発が出来るんだということがわかった。
できたものは超オレオレプログラムのごり押し機能だけど、勉強になったしとりあえず動くしまぁいいやと思った。

要望

チケットの開始時間単位のリマインダー機能。
Redmineには、リマインダー機能は既に存在するがチケット何時から開始とかいう場合に1時間前にリマインダーを送りたいとかができない。

ソース

GitHubに登録しています。

環境

  • Redmine1.2
  • Rails2.3.11

機能

まず、カスタムフィールドで開始時間を定義する。
ここを参考に「開始時間」のカスタムフィールドを定義しておく。
そして、それに関連するトラッカーを決める。
例えば「イベント」トラッカーにカスタムフィールド「開始時間」をひも付けておく。
そして、定義したトラッカーでチケットを作成した時に、OSのタスク機能を利用して開始時間の○時間前にメール送信用のタスクを登録する。
チケットが完了や却下など、完了を示すステータスで更新されたら登録しておいたタスクを削除する。
かなりごり押しのテクニックなのであまり参考になるようなところはないかもしれない。

実装

本体を傷つけないように、プラグインとして開発する。
まず、プラグインのスケルトンを作成する。

$ cd RAILS_ROOT
$ ruby script/generate redmine_plugin ReminderTicket

これで雛形が作成されるので、個別に実装していく。
まずは、Modelを作成する。
既存のMailerモデルを継承して今回用にRemindMailerクラスとしてカスタマイズする。

self.reminder_ticket

このメソッドはrakeコマンドより呼び出される。
引数はチケットID(issue.id)を受け取るようにしておく。

reminder_time

このメソッドがメール送信時に呼び出されるメソッド。
呼び出し元はself.reminder_ticketでdeliver_をつけて呼び出している。
deliver_を頭に付けるとメールが送信されるみたいで、送信先(TOやCC)や件名、本文などを定義する。
render_multipartが呼び出されたところで、Viewに渡される。

app/models/remind_mailer.rb

class RemindMailer < Mailer
  def self.reminder_ticket(options={})
    issue = Issue.find(options[:issue_id])
    deliver_reminder_time(issue.assigned_to, issue) if issue.assigned_to && issue.assigned_to.active?
  end

  def reminder_time(user, issue)
    set_language_if_valid user.language
    recipients issue.recipients
    cc(issue.watcher_recipients - @recipients)
    subject l(:mail_subject_reminder_ticket, :start_time => issue.custom_field_values[Setting.plugin_redmine_reminder_ticket['target_custome_field_value_id'].to_i], :count => Setting.plugin_redmine_reminder_ticket['diff_time'].to_i / 3600.0)
    body :issue => issue,
      :start_time => issue.custom_field_values[Setting.plugin_redmine_reminder_ticket['target_custome_field_value_id'].to_i],
      :count => Setting.plugin_redmine_reminder_ticket['diff_time'].to_i / 3600.0,
      :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue.id)
    render_multipart('reminder', body)
  end
end

次にメール送信時のViewと設定用のViewを作成する。
app/views/remind_mailerディレクトリに以下の2つのテンプレートを作成する。(このディレクトリ名はRailsの規約通りModel名と結びつけておく)
メール送信設定でテキスト形式とHTML形式が設定できると思うが、それら2つに対応させておく。
ファイル名はModelのrender_multipartの第一引数で定義した名前.text.html.rhtml | text.plain.rhtml としておく。
View内で使用できるインスタンス変数(@付きの変数)はModel内のbodyでシンボル定義した値となる。

app/views/remind_mailer/reminder.text.html.rhtml

<p><%= l(:mail_body_reminder_ticket, :start_time => @start_time, :count => @count) %></p>

<ul>
  <li><%=h @issue.project %> - <%= "#{@issue.tracker} ##{@issue.id}"%>: <%=h @issue.subject %></li>
</ul>

<p><%= link_to l(:label_issue), @issue_url %></p>

app/veiws/remind_mailer/remainder.text.plain.rhtml

<%= l(:mail_body_reminder_ticket, :start_time => @start_time, :count => @count) %>:

* <%= "#{@issue.project} - #{@issue.tracker} ##{@issue.id}: #{@issue.subject}" %>

<%= @issue_url %>
2012.03.06追記

※Redmine1.3以降はテンプレート名が変更されているので注意!!
 reminder.text.html.rhtml -> reminder.html.erb
 reminder.text.plain.rhtml -> reminder.text.erb

設定用Viewを作成する。
プラグイン内で簡潔する設定内容を編集できる頁を作成する。
これは、Redmineの管理メニュー > プラグインでプラグインの一覧から個別に設定画面へ飛ぶことのできるものである。
app/views/settings/_redmine_reminder_ticket_settings.rhtml

<p>
<table> 
  <tr><td> Target Tracker ID </td><td>
      <%= text_field_tag('settings[target_tracker_id]', @settings['target_tracker_id'])%>
  </td></tr>
  <tr><td> Target Custome Field Value ID </td><td>
      <%= text_field_tag('settings[target_custome_field_value_id]', @settings['target_custome_field_value_id'])%>
  </td></tr>
  <tr><td> Diff Time </td><td>
      <%= text_field_tag('settings[diff_time]', @settings['diff_time'])%>
  </td></tr>
  <tr><td> SC </td><td>
      <%= text_field_tag('settings[sc]', @settings['sc'])%>
  </td></tr>
  <tr><td> RU </td><td>
      <%= text_field_tag('settings[ru]', @settings['ru'])%>
  </td></tr>
  <tr><td> RP </td><td>
      <%= text_field_tag('settings[rp]', @settings['rp'])%>
  </td></tr>
</table>
</p>

設定できる内容は、以下の通りとした。
target_tracker_id

  • 対象となるトラッカーのID

target_custome_field_value_id

  • 対象となる開始時間を定義したカスタムフィールドのID

diff_time

  • 何時間前に送られるか

sc

  • schtasksのオプション

ru

  • schtasksのオプション

rp

  • schtasksのオプション

一応多言語対応用にja.ymlを作成しておく。
日本語しか対応させていないけど。

ja:
  mail_subject_reminder_ticket: "本日%{start_time}から開始されるチケットが%{count}時間後に到来します"
  mail_body_reminder_ticket: "本日%{start_time}から開始されるチケットが%{count}時間後に到来します"

チケット作成時にタスクを登録して、そのタスクから呼び出されるバッチファイルを作成する。
どこに作成したらいいのかわからない&環境依存なのでもっといい方法があるはず。
rakeコマンド呼び出し用のバッチファイル(Windows専用)。

lib/bat/reminder_ticket.bat

cd C:\redmine
rake redmine:send_reminder_ticket issue_id=%1 RAILS_ENV=production

次は要となる部分。
既存のチケット登録、更新処理の後に実行させたい機能をHookというのを使って実現する。
これは、既存コントローラで既に埋め込まれてるHookスクリプトを奪い取って(?)実行してしまう。という機能。
本体に影響を与えずに機能追加したいという場合に有効。

今回は、更新時(edit_after_save)と新規登録時(new_after_save)のHookを利用して
更新時に、完了ステータスなら(schtasksコマンドで)タスク削除
それ以外ならタスク削除&作成で更新処理
新規登録時に、タスク作成処理を実現した。

lib/reminder_ticket/hooks/controller_issues_edit_after_save_hook.rb

module ReminderTicket
  module Hooks
    class ControllerIssuesEditAfterSaveHook < Redmine::Hook::ViewListener
      def controller_issues_edit_after_save(context={})
        begin
          issue = context[:issue]
          if issue.tracker_id == Setting.plugin_redmine_reminder_ticket['target_tracker_id'].to_i
            if issue.closing? or issue.closed?
              Schtasks.delete_from_issue(issue)
            else
              Schtasks.delete_from_issue(issue)
              Schtasks.create_from_issue(issue)
            end
          end
        rescue
          false
        end
      end
    end
  end
end

lib/reminder_ticket/hooks/controller_issues_new_after_save_hook.rb

module ReminderTicket
  module Hooks
    class ControllerIssuesNewAfterSaveHooks < Redmine::Hook::ViewListener
      def controller_issues_new_after_save(context={})
        begin
          issue = context[:issue]
          if issue.tracker_id == Setting.plugin_redmine_reminder_ticket['target_tracker_id'].to_i
            Schtasks.create_from_issue(issue)
          end
        rescue
          false
        end
      end
    end
  end
end

rakeタスクファイルを作成する。
こいつがメール送信をするrakeコマンドを提供する。
バッチファイルで定義した以下のコマンドが打てるようになる。
引数はENV['引数名']で受け取れる。
Modelで定義したRemindMailerに投げてメールが送信される。

rake redmine:send_reminder_ticket issue_id=[issue_id] RAILS_ENV=production

lib/tasks/reminder_ticket.rake

namespace :redmine do
  task :send_reminder_ticket => :environment do
    options = {}
    options[:issue_id] = ENV['issue_id'].to_i if ENV['issue_id']
    RemindMailer.reminder_ticket(options)
  end
end

Windowsのタスク機能を提供する「schtasks」を呼び出すモジュールを定義する。
Hook時に呼び出されている。

lib/schtasks.rb

module Schtasks
  def self.create_from_issue(issue)
    options = {}
    options[:tn] = issue.id
    options[:tr] = "#{RAILS_ROOT}/vendor/plugins/redmine_reminder_ticket/lib/bat/reminder_ticket.bat #{issue.id}"
    options[:sc] = Setting.plugin_redmine_reminder_ticket['sc']
    options[:sd] = issue.start_date.strftime("%Y/%m/%d")
    options[:st] = (Time.parse("#{options[:sd]} #{issue.custom_field_values[Setting.plugin_redmine_reminder_ticket['target_custome_field_value_id'].to_i]}") - Setting.plugin_redmine_reminder_ticket['diff_time'].to_i).strftime("%H:%M")
    options[:ru] = Setting.plugin_redmine_reminder_ticket['ru']
    options[:rp] = Setting.plugin_redmine_reminder_ticket['rp']
    create(options)
  end

  def self.create(options)
    return unless options
    system "schtasks /create /tn \"#{options[:tn]}\" /tr \"#{options[:tr]}\" /sc \"#{options[:sc]}\" /sd \"#{options[:sd]}\" /st \"#{options[:st]}\" /ru \"#{options[:ru]}\" /rp \"#{options[:rp]}\""
  end

  def self.delete_from_issue(issue)
    options = {}
    options[:tn] = issue.id
    delete(options)
  end

  def self.delete(options)
    return unless options
    system "schtasks /delete /f /tn #{options[:tn]}"
  end

end

大事なinit.rbファイル。
settingsで定義した:defaultが設定内容となる。
:partialは設定用Viewを指す。
settingsは@settingsとして設定用View内でインスタンス変数としてアクセスできる。
ただし、そのほかのスコープでは、Setting.plugin_redmine_reminder_ticket['設定項目名']でアクセスする必要がある。
命名規則は、Setting.plugin_[プラグイン名]となる。
設定用のModelとかは作成する必要がなく、init.rbでsettingsとして定義した内容が設定内容として保持されるというところに感動した。便利。
最初は個別に設定内容を定数として別ファイルに書こうかと思っていたがその必要がなくなった。

init.rb

require 'redmine'
require 'time'
require_dependency 'schtasks'
require_dependency 'reminder_ticket/hooks/controller_issues_new_after_save_hook'
require_dependency 'reminder_ticket/hooks/controller_issues_edit_after_save_hook'

Redmine::Plugin.register :redmine_reminder_ticket do
  name 'Redmine Reminder Ticket plugin'
  author 'Souichi Saitou'
  description 'This is a plugin for Redmine'
  version '0.0.1'
  url ''
  author_url 'http://souichi.heroku.com'
  settings :default => {'target_tracker_id' => 4, 'target_custome_field_value_id' => 0, 'diff_time' => 3600, 'sc' => 'ONCE', 'ru' => nil, 'rp' => nil}, :partial => 'settings/redmine_reminder_ticket_settings'
end

利用する場合

  • とりあえず、Windowsでしか機能しない。
  • バッチファイルのRAILS_ROOTやRAILS_ENVは独自に直さないといけない。
  • DBなどは使用していないので、githubから落としたファイルコピペでOK(ディレクトリ名はredmine_reminder_ticket)
  • 起動したら設定ページでトラッカーIDとカスタムフィールドIDをひもづける。
  • schtasksコマンド実行時に「ru」と「rp」が必要かもしれない。
  • チケットの一括更新などには対応していない。

2012年はどんなサービスが流行るのかなぁ

2011年はソーシャルサービスブームがすごかったという印象を受けるが、
2012年はどのようなサービスが登場していくのだろうかというのを少し考えてみる。

2012年からはキュレーションサービスがもっと出てくるのではないかと思う。
ソーシャルサービスのお陰でネットをしている時間でもいつでも友人や家族に繋がることができるようになった。
そして、FaceBookTwitterなど「タイムライン」で今、誰がどのような活動をしているのか知ることができるようになった。
また、スマートフォンなどの使い方にも慣れて自分の好きな情報も集められるようになってきた。
これまで「良いBlog」を探すことが多かったが、そこから一歩進んで「良い記事」をオンラインブックマークやお気に入り機能で集めるようになる。(もしくは、必要に応じてキュレーションサービスでまとめたり、カスタマイズしたり)
そうすると、「その人が集めている情報」から「その人がどんな趣味趣向を持っているか」がわかるようになる。
SNSとキュレーションサービスの普及でより自分に合わせた情報をリアルタイムで受け取ることができるようになる。
2012年はそうしたサービスがもっと盛り上がるのではないかぁ??というか盛り上がって欲しい。
もっと自分が欲しい情報をガンガン集められるようになりたい。