Rails のコンローラのフィルタのようなものを実装してみる
Rails のコントローラで利用できるフィルターのような機能を Rails ではないアプリに組み込みたく、実装してみることにした。
ちなみに、Rails のコントローラ内で利用できるフィルタには、以下のものがある。
before_filter: アクションの前に実行after_filter: アクションの後に実行around_filter: アクションの前後で実行
あまりよい例を出せないのだが、現在以下のクラスが既に実装されているものとする。
1 2 3 4 5 6 7 8 9 |
class Greeting def initialize(out = STDOUT) @out = out end def hello @out.puts 'hello!' end end |
Rails のフィルターのように上記の hello メソッドの前後で実行するための機能をClass Macroで追加できるようにしたい。
実装イメージとしては、以下のような感じ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class Greeting ... before_filter :before_greeting after_filter :after_greeting ... private def before_greeting @out.puts 'Ya!' end def after_greeting @out.puts 'bye!' end end Greeting.new.hello # >> Ya! # 「hello!」の前に表示される # >> hello! # >> bye! # 「hello!」の後に表示される |
通したいテストは以下のような感じ。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
require 'test/unit' class GreetingTest < Test::Unit::TestCase def setup @out = StringIO.new @greeting = Greeting.new(@out) end def test_running_filters @greeting.hello assert_equal ['Ya!', 'hello!', 'bye!'], @out.string.split("\n") end end |
やることは、2つ。
- 既に実装されている
Greeting#helloメソッドには手を入れないで機能を追加する。 - 前後に呼び出す機能をコールバックとして定義し、実行できるようにする。
全て1から実装するのはシンドイので。。なにかと便利な ActiveSupport を使う。
ActiveSupport には、ActiveSupport::Callbacks というモジュールがあり、Rails のコントローラのフィルタでもこのモジュールが利用されている。拝借する。
まずは、前後に挟む機能を定義するためのフィルタの準備。モジュールで準備する。
1 2 3 4 5 6 7 8 9 10 11 12 |
require 'rubygems' require 'active_support' module Filters def self.included(base) base.class_eval do include ActiveSupport::Callbacks define_callbacks :before_filter, :after_filter end end end |
このモジュールは、利用するクラスでインクルードされると、
ActiveSupport::Callbacksモジュールをインクルードする。ActiveSupport::Callbacksモジュールで定義されているdefine_callbacksメソッドで:before_filter、:after_filterをコールバックメソッドとして定義する。
ということを行う。
これで、Greeting クラスで Filters モジュールをインクルードすれば、before_filter、after_filter でコールバックメソッドが定義できるようになる。
後は、Greeting クラスの中で、それぞれのコールバックされるメソッドを記述し、コールバックメソッドの呼び出しを行なわないといけない。
Open Class で Greeting クラスを再定義し、Around Alias で既にある hello メソッドに機能を追加する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
class Greeting include Filters before_filter :before_greeting after_filter :after_greeting def hello_with_filters run_callbacks(:before_filter) hello_without_filters run_callbacks(:after_filter) end alias_method :hello_without_filters, :hello alias_method :hello, :hello_with_filters private def before_greeting @out.puts 'Ya!' end def after_greeting @out.puts 'bye!' end end |
最終的に1つのファイルに以下のように記述している。
filter_example1.rb:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# 既存のクラス
class Greeting
def initialize(out = STDOUT)
@out = out
end
def hello
@out.puts 'hello!'
end
end
require 'rubygems'
require 'active_support'
module Filters
def self.included(base)
base.class_eval do
include ActiveSupport::Callbacks
define_callbacks :before_filter, :after_filter
end
end
end
# Greetin クラスを再定義
class Greeting
include Filters
before_filter :before_greeting
after_filter :after_greeting
def hello_with_filters
run_callbacks(:before_filter)
hello_without_filters
run_callbacks(:after_filter)
end
# |
テストを実行。
1 2 3 4 5 6 7 |
$ ruby filter_example1.rb Loaded suite filter_example1 Started . Finished in 0.000389 seconds. 1 tests, 1 assertions, 0 failures, 0 errors |
OK だ。
このくらいのことであれば、AspectR を使うこともできるが、より自由な組み込みができそう。
bash コマンドをもっと便利に使う
地味ではあるが、bash が標準でもっている機能だけで、知っておくと結構便利に bash コマンドを利用できるので、メモしておく。
ディレクトリの移動
ディレクトリをスタックして活用する/pushd、popd、dirs
ディレクトリの移動には、pushd、popd、dirs コマンドに慣れると便利。
pushd は移動したディレクトリをディレクトリスタックに追加していく。
popd はその逆で、ディレクトリスタックを削除していく。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ pwd /Users/taka $ pushd ~/Documents/ ~/Documents ~ $ pwd /Users/taka/Documents $ pushd ~/Desktop/ ~/Desktop ~/Documents ~ $ pwd /Users/taka/Desktop $ pushd ../work/radiant/tmp/ ~/work/radiant/tmp ~/Desktop ~/Documents ~ $ pwd /Users/taka/work/radiant/tmp |
追加されたディレクトリは、dirs -v で確認できる。
1 2 3 4 5 |
$ dirs -v 0 ~/work/radiant/tmp 1 ~/Desktop 2 ~/Documents 3 ~ |
上記のディレクトリスタックの状態で、~/Documents に移動したい場合は、pushd +n を使う。n には、上記リストの左側に表示されている番号を使用する。
1 2 3 4 |
$ pushd +2 ~/Documents ~ ~/work/radiant/tmp ~/Desktop $ pwd /Users/taka/Documents |
ちょっとしたことだが、楽になる。
~/Desktop をディレクトリスタックから削除する場合には、
1 2 3 4 5 6 |
$ popd +1 ~/work/radiant/tmp ~/Documents ~ $ dirs -v 0 ~/work/radiant/tmp 1 ~/Documents 2 ~ |
直前のディレクトリに戻る/cd -
直前のディレクトリに戻りたい時は、cd に - の引数を与えればよい。
1 2 3 4 5 6 |
$ cd ~/Documents/ $ cd ~/work/ $ cd - /Users/taka/Documents $ pwd /Users/taka/Documents |
引数の再利用
前回利用したコマンドを再度利用する場合などには、
- 直前のコマンドであれば、
!! - ヒストリからの再利用であれば、
!n。nは、historyコマンドで表示される履歴の番号
などが便利だが、この考え方は、引数にも利用できる。
あるファイルをコピーして、そのファイルを vi などで開く場合、
1 2 |
$ cp a.txt b.txt $ vi b.txt |
これは、以下の方法でいける。
1 2 |
$ cp a.txt b.txt $ vi !:2 |
! を使うのはコマンドの時と同じ。その後に:(コロン)と n(数字)と入力する。数字の部分が前回のコマンドで利用した引数の位置。
上記の場合は、b.txt を指定したいので、前回コマンドの2番目ということで、2 を指定している。
また、最後の引数を使いたい場合には、M-. (ESC キーを押した後に、.)を入力すると、最後に使用したコマンドの最後の引数を自動で入力してくれる。
上記のコマンドであれば、vi と入力して、スペース、M-. と入力すれば、vi b.txt と入力される。
{}(ブレース)を利用したファイルパスの展開
{}(ブレース)による文字列の展開は便利に使える。
1 2 |
$ echo {one,two,three}sheep
onesheep twosheep threesheep |
{}(ブレース)の中で、文字列を並べる際にカンマの後にスペースを入れたくなるが、入れてしまうと展開してくれないので注意。
日常生活でどう使えるか?
ある設定ファイルを編集しようとする時に、その設定ファイルのバックアップファイルを作っておくのはよくやる行為。
{}(ブレース)を使って、以下のように書ける。
$ sudo cp -p /etc/apache2/httpd.conf{,.back} |
ちょっと奇妙に見えるが、結果として、/etc/apache2 ディレクトリに httpd.conf ファイルのコピーである httpd.conf.back ファイルが作成される。
{}(ブレース)の最初のところで、{ の後に,(コンマ)だけというのがミソで、上記のコマンドは、以下のように展開される。
$ sudo cp -p /etc/apache2/httpd.conf /etc/apache2/httpd.conf.back |
編集後、diff を取る時にも同じ要領で便利に使える。
$ diff -u /etc/apache2/httpd.conf{,.back} |
履歴(history)を活用
履歴をインクリメンタル検索/C-r
上下矢印キー、もしくは、C-p(コントロールキーを押しながらp) or C-n でコマンドの履歴を表示できるが、探したいコマンドがある程度予測のついている場合には、C-r が便利。
C-r を入力後、過去に入力していたはずで、今回使用したいコマンドの文字列を入力すると、部分一致でインクリメンタルサーチとなる。入力一文字一文字で過去の履歴の中から検索してくれる。
history から検索しているので、コマンドそのものでなくても、引数として預けた文字列でも探してくれる。
希望のものが見つかったらそのまま Enter で即実行だが、引数などをちょっと変更したいときには、C-fなり、C-bなどをタイプし、C-r から抜けると、それまで部分一致で表示されていたコマンドがそのままプロプトに表示されるので、好きに編集する。
履歴の残し方をカスタマイズ
連続して入力した同じコマンドを全て履歴として残すことにはあまり必要性を感じないなぁという場合には、連続して入力された同じコマンドは残さないようにすることができる。
$ export HISTCONTROL=ignoreboth |
コマンドが発行された時間を残しておきたい場合には、以下のようにする。
1 2 3 4 5 6 7 8 9 |
$ export HISTTIMEFORMAT='%Y-%m-%d %T ' $ history ... 496 2009-12-19 21:56:37 history 497 2009-12-19 21:56:40 cp -p a.txt c.txt 498 2009-12-19 21:56:45 cp -p a.txt d.txt 499 2009-12-19 21:56:49 history 500 2009-12-19 21:58:50 exit 501 2009-12-19 21:58:57 history |
Ruby YAML ライブラリでの UTF-8 な日本語の扱い
日本発の言語でありながら、Ruby の YAML 標準ライブラリには弱点がある。。
Ruby の YAML ライブラリは、日本語を正しく扱ってくれない。
具体的には、Object#to_yaml を使用した際に、UTF-8 な日本語が binary 扱いになってしまう。
1 2 3 4 5 6 7 8 9 |
$ irb --prompt simple >> $KCODE => "NONE" >> $KCODE = 'u' => "u" >> str_ja = '日本語' => "日本語" >> str_ja.to_yaml => "--- !binary |\n5pel5pys6Kqe\n\n" |
この問題を回避するためのライブラリが幾つかある。
ここでは、yaml_waml を使ってみる。
yaml_waml を使う準備
gem で提供されているので、インストールしていない場合はインストールしておく。
なお、gemcutter に移行されているので、gemcutter をまだ使っていない場合は、先に準備しておく。
1 2 3 4 5 6 7 8 |
$ gem install gemcutter $ gem tumble $ gem sources *** CURRENT SOURCES *** http://gemcutter.org http://gems.rubyforge.org/ http://gems.github.com |
yaml_waml のインストール。
1 2 3 4 5 |
$ gem install yaml_waml Successfully installed yaml_waml-0.3.0 1 gem installed Installing ri documentation for yaml_waml-0.3.0... Installing RDoc documentation for yaml_waml-0.3.0... |
yaml_waml を使ってみる
先程の irb のプロンプトに引き続き実験。
1 2 3 4 |
>> require 'yaml_waml' => true >> str_ja.to_yaml => "--- "日本語"\n" |
OK だ。
Rails で使用する
Rails で対応する場合には、config/environment.rb で gem の指定しておく。
1 2 3 4 5 |
Radiant::Initializer.run do |config| ... config.gem 'yaml_waml', :version => '~> 0.3', :source => 'http://gemcutter.org' ... end |
のような感じ。
ブラウザのテキスト入力を好みのエディタで
"It's All Text!" を使ってみることに
最近になってこんな便利なものがあることに気づいた。。
Web アプリケーションはホントに便利になり、大半のことはブラウザで行えてしまう。
ただ・・・wiki、ブログ、tracking system などテキストを入力する機会が多いアプリを扱う際には、ブラウザのテキストボックスの入力欄ではちょっとストレスが溜まる。
最近はそれを補うワードプロセッサーのような機能を提供するものもあるが、好みのエディタで編集したいという欲望は抑え切れない。
と、Radiant CMS の screencast を見ていた時にテキストボックスの編集を TextMate で行っているものがあった。そういうツールが既にあるんだ!ということでちょっと調べてみた。
という Firefox のアドオンもあるようだ。
他にも、textserver というものがある。
好んで使用しているのは Mac OS なのだが、Windows も使う必要がある。両方でうまく動作したのが It's All Text! の方だったので、しばらくこちらを使っていくことにした。
ブラウザのテキストエリアを Emacs で編集
エディタは Emacs である必要はないのだが、大半のこと Emacs でやってしまっている身としては、Emacs が使えると非常にありがたい。
他エディタでも同様のことは可能だが、テキストボックスに入力するテキストの入力フォーマットに合わせて Emacs 側で編集モードをかえて編集できるのは便利だ。
Markdown であれば、Emacs markdown-mode、HTML であれば nXhtml - an Emacs mode for Web Development などという感じ。非常に快適だ。
インストールと設定
Firefox へのインストールは、下記から通常の手順で実施する。
アドオンの設定には、以下の設定を行う。

上記は、Mac OS X の Carbon Emacs をエディタとして使用する場合の設定。上記のエディタのパスは汎用的なものではない。理由については後述する。
既に起動している Emacs のフレームのバッファを使用する
Carbon Emacs のパスは、/Applications/Emacs.app/Contents/MacOS/Emacs となるので、これを指定してあげればいいのだが、これだと "It's All Text!" を使う度に、新たにフレームから立ち上げてしまう。Emacs は常に立ち上っているので、編集の度にフレームが複数上がるのはメンドい。既に起動している Emacs のバッファに表示させるようする。
こういう時には、Emacs Server/Client を使えばよいようだ。
エディタのパスには、
/Applications/Emacs.app/Contents/MacOS/bin/emacsclient
を指定しておいて、Emacs 側では、Emacs Server を起動しておく。
M-x server-start |
常に起動させるようにするには、.emacs なりに、
(server-start) |
を記入しておく。
ただ、1つ問題が・・・
何故か設定画面にて、emacsclient のフルパスが保存されない。理由はよくわからないが。
仕方ないので、自身のホームディレクトリ配下の bin 配下に上記のシンボリックリンクを用意して、それを参照させる。
1 2 |
$ cd ~/bin $ ln -s /Applications/Emacs.app/Contents/MacOS/bin/emacsclient emacsclient |
これで、最初の設定画面のエディタのパスに /Users/taka/bin/emacsclient が入ることになった。
Emacs Client からバッファを開いた場合には、C-x # でそのバッファを終了させるのがお行儀のよいやり方。
使い方
テキストボックスにカーソルを持っていくと、デフォルトでは右下(設定で変更可能)に "編集" ボタンがでるので、それをクリック。設定したエディタがアクティブになる。
マウスでわざわざクリックしなくても、ショトカットキーも割り合てることもできる。こちらも設定画面にて設定。

以下は、エディタ(Emacs)側で編集を行っている画面。エディタ(Emacs)で保存を行えば、それがブラウザのテキストボックスに反映される。

もっと早く知っておけばよかった。。
Twitter で Unfollowers (アンフォローをした方)をチェックする簡易スクリプト
スクリプトを準備した経緯
Twitter を始めて早2ヶ月が経とうとしている。
「Google Reader なんかもういらない!」(Rest in Peace, RSS)という記事に触発されて始めてみたのだが、自分自身には相変わらず Google Reader は必需品だ。。(用途の棲み分けはできている)
ただ、これまで触れ合うことはなかった方々と触れ合えたり、Google Reader と合わせて貴重な情報源となっており、今では Twitter も必需品だ。
そんな中、「Twitter で Unfollowers (アンフォローをした方)を調べるにはどうしたらいいの?」という質問を受けた。
現状身近な方には特に知らせることも無く、こっそり静かに Twitter を使っているのだが、自身の follower さんも少しづつ増えてきている。
この follower さん、増えるだけでは無く、当然ながら減ることもある。フォローされた場合は、メール通知の設定をしておけば、メールで知ることができるが、確かに、一度フォローした後、そのフォローを解除している方を知るための機能は標準では無さげだ。(きっと)
標準では無さそうだが、同様のことをしてくれるサービスは既にあるだろう。車輪の再開発だが、Twitter のサードパティのサービス/アプリは多すぎて、、、簡単なものであれば自分で準備した方が手軽だ。
しかし、もしかすると、これからやろうとしていることはパンドラの箱なのかもしれない。。
例えば、妻のアカウントからフォローされていたのに、ある日突然そのフォローが外れていたら、どう対応すればよいのだろう・・・知らぬが仏のような気もする。。
Unfollowers をチェックするスクリプト
まずは、Twitter API とやり取りを行うベースとなる簡易 Twitter クライアントを用意する。適当な名前付けでSimpleTwitterとする。
SimpleTwitter
- 標準ライブラリ以外の原材料
httpartygem (jnunemaker's httparty at master - GitHub)
Twitter クライアントに特化した Ruby Twitter Gem などもあるが、 汎用的なものを使いたくてこちらを選択。
- 使用している Twitter API
httparty gem がインストールされていない場合には、事前にインストール。
1 2 3 4 5 6 |
$ sudo gem install httparty $ gem list httparty *** LOCAL GEMS *** httparty (0.4.3) |
今回の目的からは外れるが、Timeline の表示、Mention の表示も一応入れておく。
simple_twitter.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
require 'time' require 'forwardable' require 'rubygems' require 'httparty' class SimpleTwitter extend Forwardable include HTTParty base_uri 'https://twitter.com' # SSL で def_delegator :"#{self}", :get, :get def initialize(user, password, options = {}) if options.key? :http_proxy options[:http_proxy_port] ||= 8080 self.class.http_proxy options[:http_proxy], options[:http_proxy_port].to_i end @auth_info = { :username => user, :password => password } end def timeline(which = :friends, options = {}) auth_info_merge! options get("/statuses/#{which}_timeline.json", options) end def mentions(options = {}) auth_info_merge! options get("/statuses/mentions.json", options) end def followers(options = {}) auth_info_merge! options options[:query] ||= {} options[:query].merge!({ :cursor => -1 }) unless options[:query].key? :cursor get("/statuses/followers.json", options) end class << self # 日付表示のフォーマット def format_time(time, format = '%Y/%m/%d %H:%M:%S') (time.is_a?(String) ? Time.parse(time) : time).strftime(format) end end private def auth_info_merge!(options) options.merge!({ :basic_auth => @auth_info }) end end |
タイムラインの表示などには、以下のような感じで使える。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
require 'simple_twitter' # 'user', 'password' には Twitter のログインアカウントとパスワードを指定 twitter = SimpleTwitter.new('user', 'password') # Proxy を経由する必要がある場合 # twitter = SimpleTwitter.new('user', 'password', # { :http_proxy => 'proxy_server', :http_proxy_port => 8080 }) # query count のデフォルトは 20 twitter.timeline(:friends, { :query => { :count => 5 } }).each do |twit| puts "#{twit['user']['screen_name']} [#{SimpleTwitter.format_time(twit['created_at'])}](#{twit['id'].to_s}):\n" + " #{twit['text']}" end |
表示は以下のような感じ。
1 2 3 4 5 6 7 8 9 10 |
# >> delicious50 [2009/11/07 15:47:58](5501187604): # >> 地球人ネットワークを創るアルク:スペースアルク http://bit.ly/lcfEp japan # >> delicious50 [2009/11/07 15:47:56](5501187311): # >> findability.org - by Peter Morville http://bit.ly/15KKUP findability ia # >> GuyKawasaki [2009/11/07 15:46:52](5501173816): # >> Guess who is a Glamour woman of the year? http://om.ly/bTqX # >> tek_news [2009/11/07 15:46:49](5501173203): # >> HNews: Someone actually created a pyramid scheme for Twitter popularity. http://bit.ly/20gVa5 # >> GuyKawasaki [2009/11/07 15:46:46](5501172663): # >> The official rules of Shotgun http://om.ly/bTqU |
Unfollowers をチェックするスクリプト
- 標準ライブラリ以外の原材料
simple_twitter.rb
上記にあるスクリプト
follows_unfollowers_check.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
require 'simple_twitter' FOLLOWERS_CSV = 'followers.csv' INDENT = ' ' * 2 def get_follower_user_list(twitter, cursor = -1) users = [] twit_followers = twitter.followers(:query => { :cursor => cursor }) twit_followers['users'].each do |follower| users << follower['screen_name'] end next_cursor = (twit_followers['next_cursor'] ||= '0') unless next_cursor.to_i == 0 users.concat(get_follower_user_list(twitter, next_cursor)) end users end # 'user', 'password' には Twitter のログインアカウントとパスワードを指定 twitter = SimpleTwitter.new('user', 'password') # Proxy を経由する必要がある場合 # twitter = SimpleTwitter.new('user', 'password', # { :http_proxy => 'proxy_server', :http_proxy_port => 8008 }) current_users = get_follower_user_list(twitter) previous_users = [] if File.exist? FOLLOWERS_CSV begin previous_users = File.open(FOLLOWERS_CSV).read.split(',') rescue puts $! exit(9) end end unfollowers = previous_users - current_users new_follow_users = current_users - previous_users puts "Follow User Stats at #{SimpleTwitter.format_time(Time.now)}" puts '=' * 50 puts "Current Follow Users count: %s" % current_users.size.to_s puts "Unfollowers count: %s" % unfollowers.size.to_s unfollowers.each do |user| puts "#{INDENT}#{user}\t#{SimpleTwitter.base_uri}/#{user}" end puts "New follow Users count: %s" % new_follow_users.size.to_s new_follow_users.each do |user| puts "#{INDENT}#{user}\t#{SimpleTwitter.base_uri}/#{user}" end File.open(FOLLOWERS_CSV, 'w') { |f| f.write current_users * ',' } |
結果は以下のような出力になる。(Emacs 上で rcodetools を利用した際の表示)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# >> Follow User Stats at 2009/11/07 15:03:01 # >> ================================================== # >> Current Follow Users count: 65 # >> Unfollowers count: 7 # >> SavvyExplorer https://twitter.com/SavvyExplorer # >> talyaalove https://twitter.com/talyaalove # >> 59minlearning https://twitter.com/59minlearning # >> lifefulfillment https://twitter.com/lifefulfillment # >> follow4profit https://twitter.com/follow4profit # >> Forex_Blog https://twitter.com/Forex_Blog # >> VirtualTotte https://twitter.com/VirtualTotte # >> New follow Users count: 6 # >> RecognitionApps https://twitter.com/RecognitionApps # >> kazuyo_k https://twitter.com/kazuyo_k # >> Dewina_Olivera https://twitter.com/Dewina_Olivera # >> HP_AppSecurity https://twitter.com/HP_AppSecurity # >> videotodaynews https://twitter.com/videotodaynews # >> devtube https://twitter.com/devtube |
実行は、simple_twitter.rb と follows_unfollowers_check.rb を同じディレクトリにおいておいて、
$ ruby ./follows_unfollowers_check.rb |
前回スクリプトを動かした時との差分が表示される。
正確にやろうと思うと、細目にこのスクリプトを動かしておかないといけない。前回動作時との間に follow/unfollow の両方を行ったアカウントは捉えられないので。
また、結果の出力は標準出力以外のもの、メールでの送信などにしておくのもよいかもしれない。
Issue/Bug トラッキングシステム/Git リポジトリ Unfuddle を試してみる
Issue/Bug トラッキングシステムであり、Git/Subversion リポジトリの管理(Viewer だけではない)も行えるサービス。
Issue/Bug トラッキングシステムとソースリポジトリの連携は今では無くてはならない環境と(個人的には)なっている。
チケット駆動型の開発に慣れてしまうと、チケットを作成せずにソースに手を入れるというのは何となく心許ない。。
このような Issue 管理システムを使わずとも、リポジトリだけでもコメントは残せて、何故そのような commit を残したのかは記録され、振り返りは行える。
だが、その commit を残すまでの経緯、その commit を残すことになるトリガーを Issue 管理システム側のチケットを利用して記録し、経過を管理しておくことで、作業の漏れが少なくなり、一緒に作業を行っているメンバ間での途中経過の情報共有も行え、時間の節約に繋がる。
また、あれやこれやと空いた時間で手をつけている自分のような状況の者にとってすると、時間が経過した後に以前の作業に戻り易くなる。
Redmine は愛用しているのだが、現在内部ネットワークの閉じた環境で使用している。
- 「あちら側」(クラウド)へ Issue 管理システムとソースリポジトリを移行したい
- システムの運用管理はサービス提供者側にお任せしたい
(もの作り、プロジェクトの運用に専念したい)
という思いがあり、Unfuddle を試してみることにした。
何故 Unfuddle なのか?は以下の記事に感化され、「agile reporting!」をやってみたくなったのもある。。
他にも同様のサービスはあるので、もしかするともっと素敵なサービスがあるかもしれない。現在他サービスの検証までは行っていない。
ここでは、
- Issue/Bug トラッキングシステム Unfuddle のアカウント開設
- Git リポジトリの登録
- "Agile Reporting" のお試し (結局、次回の投稿に回すことに)
までをやってみる。
Unfuddle でアカウントを作成する
まずは Unfuddle でアカウントの作成を行う。

Unfuddle: Subversion Hosting, Git Hosting, Bug and Issue Tracking のトップページ "Start Unfuddling Now!" からアカウントを作成する。

まずはお試しなので "Private FREE" で。本格的に使うとした場合、最低"Compact" のパッケージにはしたい。月 24 ドルか・・・。

Unfuddle で利用する自身のサイトの URI は、上記で指定する "Subdomain" になる。必要項目を入力して"Continue"。アカウントが作成される。

以下はアカウントのトップページになる Dashboard と、プロジェクト単位の Dashboard。


Trac、Redmine などを使ったことのある方などは、さくさくと使い込なせるのではないかと思う。
利用方法については、本家のサイトのツアーを見て頂くのがよいかと思う。
Git リポジトリの登録と利用
Unfuddle のリポジトリには、Git、Subversion が利用できる。
ここでは Git を使用する。
Unfuddle にリモートリポジトリを作成し、ローカルリポジトリとの連携は、通常通り git コマンドを使用して行うことになる。 作成したリモートリポジトリとローカルリポジトリとのネットワークプロトコルは SSH になる。
手順としては、git でリモートリポジトリを利用する方法と同様。リモートリポジトリの管理部分だけ Unfuddle の UI を使って実施することになる。 ざっくりとした流れは以下の通り。
- リモートリポジトリを作成 - Unfuddle のサイトで
- SSH で利用する鍵ペアの作成 (作成していない場合) - ローカル環境
- ユーザアカウントに SSH 公開鍵を登録する - Unfuddle のサイトで
- ローカルリポジトリの準備 (作成していない場合) と反映する状態の FIX - ローカル環境
- ローカルリポジトリに Unfuddle のリモートリポジトリを追加 - ローカル環境
- ローカルリポジトリをリモートリポジトリに反映(push) - ローカル環境
アクセス管理を含めたリモートリポジトリの管理の手間が減るのが自前でやるのと比べてうれしい。
1. Unfuddle のサイトでリモートリポジトリを作成
Account、もしくは、Project の "Repositories" メニューから "Repositories" のページを開く。 "New Repository" をクリックし、新規のリポジトリ情報を入力する。

"Create Repository" を実行し、リポジトリを作成する。

2. SSH で利用する鍵ペアの作成 (作成していない場合)
まだ作成していない場合には、SSH の鍵ペアの作成を行う。SSH の鍵ペアの生成方法については、Unfuddle でドキュメントを用意してくれている。
3. ユーザアカウントに SSH 公開鍵を登録する
Unfuddle のサイトで 2. で作成した SSH 公開鍵(~/.ssh/id_rsa.pub)をユーザアカウントに紐付ける。
"People" タブで "Personal Settings" ページを開き、ユーザアカウントを選択する。

該当ユーザアカウントの"Edit"クリックし、"Edit Person" ページを開く。ページの下の方に"Public Keys"という項目があるので、"New Public Key.." をクリックする。

上記 2. で作成した SSH 公開鍵のファイル(通常は、~/.ssh/id_rsa.pub)を開き、その内容を"Value"にコピペする。

"Add" をクリックし、公開鍵を登録する。

4. ローカルリポジトリの準備 (作成していない場合) と反映する状態の FIX
管理するリポジトリをまだ準備していない場合には、準備しておく。
Unfuddle 固有の話ではなく、Git に関する話なのでここでは割愛する。以下の Unfuddle の提供しているドキュメントが参考になると思う。
5. ローカルリポジトリに Unfuddle のリモートリポジトリを追加
Unfuddle のリモートリポジトリの情報をローカルリポジトリに追加する。
まずは、ローカルの環境にて以下を実施。
<subdomain>と<abbreviation>は、以下の情報に置き換える。
<subdomain>: アカウントのサブドメイン<abbreviation>: 1. でリモートリポジトリを作成した際に登録を行った "Avvreviation" の情報。
反映するローカルリポジトリのディレクトリに移動して実施。
1 2 3 |
$ cd /path/to/repository $ git remote add unfuddle git@<subdomain>.unfuddle.com:<subdomain>/<abbreviation>.git $ git config remote.unfuddle.push refs/heads/master:refs/heads/master |
Git の設定情報に以下が追加される。
1 2 3 4 5 |
$ git config --list ... remote.unfuddle.url=git@<subdomain>.unfuddle.com:<subdomain>/<abbreviation>.git remote.unfuddle.fetch=+refs/heads/*:refs/remotes/unfuddle/* remote.unfuddle.push=refs/heads/master:refs/heads/master |
6. ローカルリポジトリをリモートリポジトリに反映(push)
準備は整ったので、ローカルリポジトリをリモートリポジトリに反映(push)する。
1 2 3 4 5 6 7 8 9 10 11 |
$ git push unfuddle master The authenticity of host '<subdomain>.unfuddle.com (xxx.xxx.xxx.xxx)' can't be established. RSA key fingerprint is xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added '<subdomain>.unfuddle.com,xxx.xxx.xxx.xxx' (RSA) to the list of known hosts. Counting objects: 307, done. Compressing objects: 100% (282/282), done. Writing objects: 100% (307/307), 214.83 KiB, done. Total 307 (delta 111), reused 0 (delta 0) To git@<subdomain>.unfuddle.com:<subdomain>/<abbreviation>.git * [new branch] master -> master |
Unfuddle のサイトで確認してみる。

リモートリポジトリからの clone は、以下の通りとなる。
$ git clone git@<subdomain>.unfuddle.com:<subdomein>/<abbreviation>.git |
感想
感想の前に、、
「"Agile Reporting" のお試し」を誌面(?)の都合でまとめて書くことができなかったので、別の投稿に分けて記載する。
まだ感想を書けるまでは使い込なしていないが、現在使用している Redmine が持っている機能的なところでの比較としては、
- リポジトリは Issue 管理システムからの参照ではなく、Issue 管理システム内で管理される
- プロジェクトとリポジトリを M対N で紐付けることができる(できそう)
というところがうれしい。
Ruby gem 名称を指定して Emacs の Dired でそのライブラリのディレクトリを即座に開く
やりたいことはタイトルの通りなのだが、Emacs を使用中に Ruby の gem 名称でそのライブラリのディレクトリを Dired で開くこと。
Ruby には素敵な gem が多くあるが、 付属の Readme、RDoc だけでは使い方がよくわからない、 どうやったらこのような素敵なことができるのかソースを見てみたい、 などの理由でソースを読んでみたくなる。
@bguthrie さんが Textmate でやっているのを見て、Emacs にも流用できるなと。
上記のソースで gem which <gem name> なんて便利なコマンドが使えるのを初めて知った。。
Emacs のディレクトリ操作には標準で Dired が利用できる。
C-x C-f(find-file) で該当する gem のディレクトリを開けばよいのだが、
ちょっとしたことではあるが、タブ補完が効くとは言え、そのパスを入力するのは少々面倒臭い。
また、仕事の関係上、Mac、Win と併用して使っているので、異なる環境でパスを思い出すのもメンドイ。。
gem の名称だけでわかれば直接そして即座に開けるようだと、メモリ容量が減ってしまっている自分の頭にもやさしい。
elisp は全くの初心者なので、かなり無理やり感も漂い、もっといい方法もありそうなのだが、とりあえず以下のような感じでいけそう。
1 2 3 4 5 6 |
(defun search-gem-path (name)
(shell-command-to-string
(format "gem which %s | ruby -n -e 'if /lib/; print $_[0, $_.rindex(%%[lib])]; end'" name)))
(defun find-ruby-gem (name)
(interactive "sRuby gem libraray name: ")
(find-file (search-gem-path name))) |
上記スクリプトを .emacs.el なりに書いて M-x find-ruby-gem <gem 名称> とやることで、gem 名称に指定した gem のルートディレクトリが Dired で開かれる。
例えば、twitter gem を開く場合、M-x find-ruby-gem twitter として、

Enter!

ホントちょっとしたことではあるが、うれしい。。
ちなみに、標準ライブラリのソースを開く場合には、
devel-which ライブラリ(http://raa.ruby-lang.org/list.rhtml?name=devel-which)を使うとよい。
1 2 3 4 5 |
$ irb --prompt simple >> require 'devel/which' => true >> which_library 'net/http' => "/opt/local/lib/ruby/1.8/net/http.rb" |
Ruby ActiveRecord の(超)簡易実装で学ぶメタプログラミング
しっかり抑えておきたいと思うメタプログラミング
Ruby のメタプロミングは奥が深くて楽しいが、やり過ぎると暗黒面に堕ちてしまう。。
効果的な使い方を心掛けていきたい。
Rails でもよく利用されているように、CoC (Convention over Configuration) と組み合わせることで更に効果的となる。
なかなかとっつきにいところもあり、コンパクトにまとまった文書がないかと探していた。
上記サイトなどはコンパクトによくまとまっているように思う。
メタプログラミングに必要となる、通常はあまり使用しないメソッドをメタプログラミングツールボックスというかたちでリストアップし、その説明はドキュメントへのレファレンスで構成している。 最後にメタプログラミングの適用例として ActiveRecord の簡易実装をサンプルとして掲載している。
記載されているツールボックスの利用の前に、基本となる、
- Ruby のクラス階層
Object クラス、Module クラス、Class クラスと任意のクラスとの関連。 - メソッド参照のルール
もしっかり抑えておく必要があるようには思う。その方が混乱が少なくて済む。。
個人的にはメタプログミングと言えば、why の Metaid が思い付くのだが、why はもう姿を現してくれないのだろうか・・・
"why's (poignant) guide to ruby" が以下のサイトで再掲されているようだ。
時間を見つけて以下のサイトを読み込んでみるのも良さそうだ。
また、"Metaprogramming Ruby" は非常に楽しみしている書籍で、まだベータブックではあるが、The Pragmatic Bookshelf | Metaprogramming Ruby を購入して読んでみようと思う。
と、今回のこのメモの中では、先に挙げた Ruby's Metaprogramming Toolbox で掲載されている ActiveRecord の簡易実装サンプルを SQLite 版で書いてみて、使用されているツールボックスのツールを確認してみたいと思う。
ActiveRecord の簡易実装 (ちょっとした検索だけ)
Ruby's Metaprogramming Toolbox では最後にメタプログミングの適用例として、
ActiveRecord の簡易実装を記載している。
これが MySQL 用のものであるので、もっと手軽に試せる SQLite 版にしてみた。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
require 'rubygems' require 'sqlite3' # SQLite バージョン # AR の CoC はとりあえず無視 class PoorManWithSqlite # クラスインスタンス変数に生成されたクラスのリストを保持する class << self; attr_reader :generated_classes; end @generated_classes = [] def initialize(attributes = nil) if attributes attributes.each_pair do |key, value| instance_variable_set('@' + key, value) end end end def self.connect(database = 'data.db') @@db = SQLite3::Database.new(database) sql = <<-SQL select tbl_name from sqlite_master where type = 'table' and tbl_name != 'sqlite_sequence' order by tbl_name; SQL @@db.execute(sql).flatten.each do |table_name| class_name = table_name.split('_').map { |word| word.capitalize }.join # Module#const_set を使用してテーブル用の新しいクラスを作成する @generated_classes << klass = Object.const_set(class_name, Class.new(PoorManWithSqlite)) klass.module_eval do @@fields = [] @@table_name = table_name def fields; @@fields; end end # テーブルのフィールドのリストを収集し、 # それらに対する getter/setter を用意する。 @@db.table_info(table_name) do |field| # getter/setter の追加 klass.send :attr_accessor, field['name'].to_sym # field セットに追加 klass.module_eval { @@fields << field['name'] } end end @@db end def self.close @@db.close end # id でレコードを取得する def self.find(id) # SQLite3::Database#execute2 は、最初の行としてカラム名称の配列を返す。 result = @@db.execute2("select * from #{@@table_name} where id = #{id} limit 1") attributes = Hash[*result.transpose.flatten] new(attributes) if attributes end # 全てのレコードを取得する def self.all results = @@db.execute2("select * from #{@@table_name}") fields = results.shift records = [] results.each do |result| records << new(Hash[*fields.zip(result).flatten]) end results end end PoorManWithSqlite.connect table_classes = PoorManWithSqlite.generated_classes # => [Departments, Employees] table_classes[0].superclass # => PoorManWithSqlite employee = Employees.find(1) # => #<Employees:0x11f20d8 @department_id=nil, @name="taka", @id="1"> employee.name # => "taka" employees = Employees.all # => [["1", "taka", nil], ["2", "goro", nil]] PoorManWithSqlite.close # => true |
上記の実行にあたっては、簡単な DB を用意しておく必要がある。
create-table.sql
ファイルの内容は以下の通り。
1 2 3 4 5 6 7 8 9 |
create table departments ( id integer PRIMARY KEY AUTOINCREMENT NOT NULL, name varchar(255) ); create table employees ( id integer PRIMARY KEY AUTOINCREMENT NOT NULL, name varchar(255), department_id integer ); |
data.db を作成し、上記 DDL 文を流し込む。
$ sqlite3 data.db < creat-table.sql |
ポイントメモ
幾つかポイントをメモ。
- 動的なクラスの生成(しかもサブクラス)
- 動的なアクセサの生成とコンストラクタによる値のセット
- クラスインスタンス変数の利用とそのアクセサの生成
Object#sendを使った private メソッドへのアクセス
1. 動的なクラスの生成(しかもサブクラス)
Module#const_set を利用して、テーブルの名前から動的に PoorManWithSqlite クラスのサブクラスとなる各テーブルのクラスを生成している。
1 2 3 |
# Module#const_set を使用してテーブル用の新しいクラスを作成する @generated_classes << klass = Object.const_set(class_name, Class.new(PoorManWithSqlite)) |
Module#const_set(name, value) -> object
モジュールに name で指定された名前の定数を value という値に定義し、value を返す。
1 2 3 |
C = Object.const_set('Con', 10) Con # => 10 C # => 10 |
Ruby の場合、クラス名は全て定数となる。name にテーブル名から生成した名称のクラス名を指定し、
value には、PoorManWithSqlite のサブクラスを指定している。
その value の指定に使用されているのは Class::new メソッド。Class の new メソッドには、インスタンスメソッドとクラスメソッドがある。
上記で利用しているのはクラスメソッド(Class::new)の方になる。
Class::new(superclass = Object) -> Class
Class::new(superclass = Object) {|klass| ... } -> Class
新しく superclass の無名のサブクラスを生成する。
1 2 3 4 5 |
class SuperAClass; end sub_k = Class.new(SuperAClass) # => #<Class:0x2533c> sub_k.class # => Class sub_k.superclass # => SuperAClass |
コードブロックを預けてメソッドを定義することもできる。
1 2 3 4 5 6 7 8 |
k = Class.new(SuperAClass) do |klass| def hoge 'hoge hoge' end end o = k.new # => #<#<Class:0x273e4>:0x273a8> o.hoge # => "hoge hoge" |
各テーブルのクラスを PoorManWithSqlite クラスのサブクラスとすることで、各テーブルクラスにおいて PoorManWithSqlite の機能が利用できる。
2. 動的なアクセサの生成とコンストラクタによる値のセット
動的なアクセサを用意しているところ。
1 2 3 4 5 6 7 |
# テーブルのフィールドのリストを収集し、それらに対する getter/setter を用意する。 @@db.table_info(table_name) do |field| # getter/setter の追加 klass.send :attr_accessor, field['name'].to_sym # field セットに追加 klass.module_eval { @@fields << field['name'] } end |
テーブルのフィールドをイテレートし、そのテーブルクラスの読み書き両用のインスタンス変数を生成している。
次に値をセットしているところ。
1 2 3 4 5 6 7 |
def initialize(attributes = nil) if attributes attributes.each_pair do |key, value| instance_variable_set('@' + key, value) end end end |
コンストラクタで引数にフィールド名を key、そのフィールドの値を value とした Hash を受け取り、
Object#instance_variable_set で値のセットを行う。'@' を文字列連結しているところはちょっとカッコ悪いが致し方ない。
上記 initialize メソッドが利用される実際に値をセットしているところは以下の箇所となる。
1 2 3 |
results.each do |result| records << new(Hash[*fields.zip(result).flatten]) end |
Hash[*fields.zip(result).flatten] は検索結果の配列からフィールド名を key、そのフィールドの値を value とした Hash を生成している。
3. クラスインスタンス変数の利用とそのアクセサの生成
各テーブルクラスは、PoorManWithSqlite クラスを継承する。
継承の際のクラス変数の扱いには注意が必要だ。
1.8 系まではクラス変数は継承される(1.9 はまだ触っていない。。)。
Database への接続の参照は唯一のインスタンスとして持たせたいので、クラス変数が適している。
ただ、作成されたテーブルクラスの管理情報を各テーブル毎に持たせても仕方ない。このサンプルでは PoorManWithSqlite クラスのクラスインスタンス変数にその情報を保持している。
1 2 3 |
# クラスインスタンス変数に生成されたクラスのリストを保持する class << self; attr_reader :generated_classes; end @generated_classes = [] |
アクセサは、PoorManWithSqlite クラスオブジェクトの特異クラスで記述する必要がある。
4. Object#send を使った private メソッドへのアクセス
1 2 3 4 5 6 7 |
# テーブルのフィールドのリストを収集し、それらに対する getter/setter を用意する。 @@db.table_info(table_name) do |field| # getter/setter の追加 klass.send :attr_accessor, field['name'].to_sym # field セットに追加 klass.module_eval { @@fields << field['name'] } end |
klass というのは、PoorManWithSqlite を継承したテーブル毎のクラスなのだが、このクラスの attr_accessor メソッドには、klass をレシーバとしてはアクセスできない。
Object#send を使うことにより、アクセスコントロールをバイパスしている。
例示のため使っているのだと思うが、その下にある Module#module_eval のブロックの中で一緒に書いてもよいと思う。
1 2 |
# getter/setter の追加とfield セットの追加 klass.module_eval { attr_accessor field['name'].to_sym; @@fields << field['name'] } |
Twitter を始めてみた
Twitter を3日ほど使ってみた感想
最近、Twitter を使い始めてみた。
まずは、Twitter って何?というところで、以下のサイトにざっと目を通すと概要が掴める。
- Twitter(ツイッター) をはじめよう! - GreenSpace
- Twitter - Wikipedia
- 連載:先取り! Twitter使いへの道 AtoZ|gihyo.jp … 技術評論社
- Twitter - TwitterまとめWiki
呟くだけで何が楽しいのか?と斜に構え、敬遠していた Twitter だが、始めるきっかけになったのが以下の記事。
Feedburner の共同ファウンダー兼 CEO のDick Costoloが、Twitterの新 COO になったことに関する記事なのだが、この記事のタイトルは、Steve Gillmor 氏の以下の記事に発端がある。
It’s time to get completely off RSS and switch to Twitter.
...
All my RSS feeds are in Google Reader. I don’t go there any more. Since all my feeds are in Google Reader and I don’t go there, I don’t use RSS anymore.
自分にとってみたら、RSS Reader は今や生活の一部(?)になっているほど。それが Twitter にとって変わる? ちょっと飛ばし過ぎの記事にも思えたのだが、まぁそこまで言うのであれば・・・と使ってみる気になった。
使って3日程経過したが、使ってみた感想としては、RSS Reader を捨てるまでにはいかないが、なかなかどうして、
- リアルタイム性
- 気になる人(企業)の発言を気軽に追っかけることができる
- 思いもしなった情報が入ってくる
というところはよかった。感覚的には、ラジオを"見る"感覚に近い気がした。
また、能動的な使い方としては、ちょっとしたメモを含めた自身のライフログ的なものを Twitter に集約できることがわかった。これまでその手の目的には、ChangeLog などにメモを書き殴っていたのだが、Twitter でいいかもしれない。
個人的な利用ではこんなものだが、企業が使うとした場合、カスタマーサービスでの利用に効果がありそうな気がする。
ただ、まだ3日。まだまだ使い込なせていないが、Twitter 自体は非常にシンプルな設計になっているので、それを使う人の使い方によってはもっと面白い効果を出すことができそうだ。
まずはどういうものか知りたいため、この数日は、なるべく Twitter に触れる時間を作っていたが、時間は有限。。やるべき作業も他にもあるので、今後はバランス良くお付き合いしていきたい。。
フォローする人を探す
誰をフォローするかで、Twitter の楽しさは変わってくると思う。
以下のようなまとめサイトがある。
- フォローすべきTwitterアカウント一覧 - IDEA*PAD
- 最近Twitterを始めた人向けの便利な bot 厳選まとめ : ロケスタ社長日記
- 30 Japanese geeks you should follow on Twitter | hemiolia.com
- ツイッターのポータルサイト「ワイワイツイッター」〜YYtwitter.com〜
Twitter への企業の取り組み
まだまだ情報収集できていないが、ここ数日だけでも以下のような内容が耳に入ってきた。
- Twitterを全社導入して気づいたこと - EC studio 社長ブログ
- Twitterは企業と顧客をダイレクトに結ぶツールとなる、SalesforceがTwitterに対応 - Blog on Publickey
- 今度はKaaS:Salesforce.com、「Service Cloud 2」でTwitter連係強化 - ITmedia エンタープライズ
- Everything You Need To Know About Salesforce’s Service Cloud 2
- Twitterマーケティングの効果測定を考える:特集 - CNET Japan
Salesforce の以下のデモは興味深い。
関連ツールいろいろ
Twitter には関連ツールがいろいろあるようだ。ここ数日使っていて便利だったものをメモしておく。
Twitter クライアント
Web サイトで完結できるのだが、便利なクライアントツールがある。
自分の場合、Emacs をメインで使っているので、elisp のクライアントが欲しかった。以下のものが使い勝手がよかった。

ただ、ダイレクトメッセージが見られないのが玉に瑕。
Emacs 何それ?・・・という方には、
などが評価が高い模様。Adobe AIR で作成されているので、プラットフォームは選ばない。iPhone でも利用可能なようだ。
確かに、カラム機能とフィルータ機能がすばらしい。
デフォルトでは日本語の呟きが表示されないようなので、[Settings]->[Colors/Font] で 'Original Font' から 'International Font/TwitterKey' へ変更を行っておく。

Twitter のつぶやきをブログ形式で保存してくれる - Twilog
Twitter の過去ログはブラウザで全て見ることはできない。Twilog は Twitter のつぶやきをブログ形式で保存してくれる。
ライフログ的な使い方を考えていたので、これは助かる。
保存されたブログは、http://twilog.org/twitterのアカウント で公開される。
ブログの更新を Twitter に流す
twitterfeed
ブログの更新を一定間隔で Twitter に流してくれるサービス。
まとめて配信された RSS を twitterfeed で流すという方法もあるようだ。
- N ブログの1日分の更新をまとめてツイッターに流す方法
デイリーフィードはサイトの更新を翌日まとめてRSSでお届けします。
twitbackr
更新 Ping を送ってやることで、Twitter へ更新情報をポストする。
その他いろいろ
関連するツールは他にもかなりの数があるので、以下のサイトなどで確認してみるのもよいかと思う。
Ruby で REST リソースにアクセスするための REST Client
Ruby の場合、組み込みライブラリの net/http を使用すれば、如何ようにでもできるのだが、もう少し扱い易くシンプルなインターフェイスで操作したい。これまで、open-uri を拡張した rest-open-uri を使用していたのだが、heroku のクライアントで使われていた REST Client を使ってみることにした。
A simple REST client for Ruby, inspired by the Sinatra‘s microframework style of specifying actions: get, put, post, delete.
gem でインストールできる。
1 2 3 4 5 6 |
$ sudo gem install rest-client Password: Successfully installed rest-client-1.0.3 1 gem installed Installing ri documentation for rest-client-1.0.3... Installing RDoc documentation for rest-client-1.0.3... |
REST リソースとのやりとり
REST リソースを提供するサーバの準備
まずは、サーバ側の準備をしておく。Rails プロジェクトで作成しておく。今回は、認証部分は考慮していない。
Rails プロジェクトを作成。
1 2 |
$ rails -d sqlite3 rest-test $ cd rest-test |
Scaffold を使って簡単な記事の投稿、表示、変更、削除ができるアプリケーションの雛形を作成する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
$ script/generate scaffold Article title:string content:text published_at:datetime exists app/models/ exists app/controllers/ exists app/helpers/ create app/views/articles exists app/views/layouts/ exists test/functional/ exists test/unit/ create test/unit/helpers/ exists public/stylesheets/ create app/views/articles/index.html.erb create app/views/articles/show.html.erb create app/views/articles/new.html.erb create app/views/articles/edit.html.erb create app/views/layouts/articles.html.erb create public/stylesheets/scaffold.css create app/controllers/articles_controller.rb create test/functional/articles_controller_test.rb create app/helpers/articles_helper.rb create test/unit/helpers/articles_helper_test.rb route map.resources :articles dependency model exists app/models/ exists test/unit/ exists test/fixtures/ create app/models/article.rb create test/unit/article_test.rb create test/fixtures/articles.yml create db/migrate create db/migrate/20090906091207_create_articles.rb |
マイグレーションを行い、スキーマを生成しておく。
1 2 3 4 5 6 |
$ rake db:migrate (in /Users/taka/work/rest-test) == CreateArticles: migrating ================================================= -- create_table(:articles) -> 0.0032s == CreateArticles: migrated (0.0035s) ======================================== |
サーバの起動。
1 2 3 4 5 |
$ script/server => Booting Mongrel => Rails 2.3.2 application starting on http://0.0.0.0:3000 => Call with -d to detach => Ctrl-C to shutdown server |
ブラウザから記事を2つほど登録しておく。

リソースを取得する
REST Client の RestClient.get で Article の一覧を取得する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
$ irb >> require 'rubygems' => true >> require 'rest_client' => true >> RestClient.get 'http://localhost:3000/articles' => "<?xml version="1.0" encoding="UTF-8"?>\n <articles type="array">\n <article>\n <content>This is the first article!!</content>\n <created-at type="datetime">2009-09-06T09:13:46+09:00</created-at>\n <id type="integer">1</id>\n <published-at type="datetime">2009-09-06T09:13:00+09:00</published-at>\n <title>First Article</title>\n <updated-at type="datetime">2009-09-06T09:13:46+09:00</updated-at>\n </article>\n <article>\n <content>This is the second article!\r\n</content>\n <created-at type="datetime">2009-09-06T18:17:35+09:00</created-at>\n <id type="integer">2</id>\n <published-at type="datetime">2009-09-06T18:17:00+09:00</published-at>\n <title>Second Article</title>\n <updated-at type="datetime">2009-09-06T18:17:35+09:00</updated-at>\n </article>\n </articles>\n" |
レスポンスとなっている XML の解析には、REXML を使用する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
>> require 'rexml/document' => true >> doc_root = REXML::Document.new(RestClient.get('http://localhost:3000/articles')).root => articles typearray .... >> doc_root.elements.each('//article') do |article| ?> puts [ ?> article.elements['id'].text, ?> article.elements['title'].text, ?> article.elements['content'].text >> ].join(':') >> end 1:First Article:This is the first article!! 2:Second Article:This is the second article! => [article .... , article .... ] |
この後、いろいろ試してみるにあたり、投稿一覧を取ってくるメソッドがあると便利なので、定義しておく。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
>> require 'ostruct' # OpenStruct を使用するため => true >> require 'time' # Time.parse を使用するため => true >> def get_article_list >> doc_root = REXML::Document.new(RestClient.get('http://localhost:3000/articles')).root >> articles = [] >> doc_root.elements.each('//article') do |article| ?> art = OpenStruct.new >> art.resource_id = article.elements['id'].text.to_i >> art.title = article.elements['title'].text >> art.content = article.elements['content'].text >> art.published_at = Time.parse article.elements['published-at'].text >> articles << art >> end >> articles >> end |
get_article_list で投稿一覧を取得する。
1 2 3 |
>> get_article_list => [#<OpenStruct published_at=Sun Sep 06 09:13:00 +0900 2009, resource_id=1, content="This is the first article!!", title="First Article">, #<OpenStruct published_at=Sun Sep 06 18:17:00 +0900 2009, resource_id=2, content="This is the second article!\n", title="Second Article">] |
リソースを更新する
RestClient.put を使って2番目の投稿の本文を更新してみる。
1 2 3 4 5 6 |
>> article2 = get_article_list[1] => #<OpenStruct published_at=Sun Sep 06 18:17:00 +0900 2009, resource_id=2, content="This is the second article!\n", title="Second Article"> >> RestClient.put( ?> "http://localhost:3000/articles/#{article2.resource_id.to_s}", ?> :article => { :content => "#{article2.content} Changed" }) RestClient::RequestFailed: HTTP status code 422 |
エラーコード 422 は、Unprocessable Entity になる。サーバ側のログを覗いてみると、
1 2 3 4 |
Processing ArticlesController#update (for 127.0.0.1 at 2009-09-07 00:13:06) [PUT] Parameters: {"article"=>{"content"=>" Changed"}, "id"=>"2"} ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken): |
送信したリクエストの中に、CSRF 対策のために Rails で使用されている AuthenticityToken が無いためだ。
今回は、AuthenticityToken のトークンチェックは行わずに、検証を続ける。
app/controllers/articles_controller.rb に以下の記述を追加し、トークンチェックを省略する。
1 2 |
class ArticlesController < ApplicationController skip_before_filter :verify_authenticity_token |
再度クライアントから更新を行う。
1 2 3 4 5 6 7 |
>> RestClient.put( ?> "http://localhost:3000/articles/#{article2.resource_id.to_s}", ?> :article => { :content => "#{article2.content} Changed" }) => " " >> get_article_list => [#<OpenStruct published_at=Sun Sep 06 09:13:00 +0900 2009, resource_id=1, content="This is the first article!!", title="First Article">, #<OpenStruct published_at=Sun Sep 06 18:17:00 +0900 2009, resource_id=2, content="This is the second article!\n Changed", title="Second Article">] |
更新されたようだ。
サーバ側のログも確認してみる。
1 2 3 4 5 6 |
Processing ArticlesController#update (for 127.0.0.1 at 2009-09-07 00:22:03) [PUT] Parameters: {"article"=>{"content"=>"This is the second article!\n Changed"}, "id"=>"2"} Article Load (0.2ms) SELECT * FROM "articles" WHERE ("articles"."id" = 2) Article Update (0.7ms) UPDATE "articles" SET "content" = 'This is the second article! Changed', "updated_at" = '2009-09-07 00:22:03' WHERE "id" = 2 Completed in 12ms (View: 1, DB: 1) | 200 OK [http://localhost/articles/2] |
リソースを新規に追加する
RestClient.post を使って新規に投稿する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
>> RestClient.post( ?> "http://localhost:3000/articles", ?> :article => { :title => "Third Article", :content => "This is third article!!", ?> :'published_at(1i)' => "2009", :'published_at(2i)'=> "9", :'published_at(3i)'=> "6", ?> :'published_at(4i)' => "22", :'published_at(5i)' => "10"}) => "<?xml version="1.0" encoding="UTF-8"?>\n <article>\n <content>This is third article!!</content>\n <created-at type="datetime">2009-09-07T00:33:11+09:00</created-at>\n <id type="integer">3</id>\n <published-at type="datetime">2009-09-06T22:10:00+09:00</published-at>\n <title>Third Article</title>\n <updated-at type="datetime">2009-09-07T00:33:11+09:00</updated-at>\n </article>\n" |
新規に追加されたようだ。DB の id も新規に 3 が採番されている。
サーバ側のログも確認してみる。
1 2 3 4 |
Processing ArticlesController#create (for 127.0.0.1 at 2009-09-07 00:33:11) [POST] Parameters: {"article"=>{"title"=>"Third Article", "published_at(1i)"=>"2009", "content"=>"This is third article!!", "published_at(2i)"=>"9", "published_at(3i)"=>"6", "published_at(4i)"=>"22", "published_at(5i)"=>"10"}} Article Create (37.3ms) INSERT INTO "articles" ("updated_at", "title", "content", "published_at", "created_at") VALUES('2009-09-07 00:33:11', 'Third Article', 'This is third article!!', '2009-09-06 22:10:00', '2009-09-07 00:33:11') Completed in 51ms (View: 2, DB: 37) | 201 Created [http://localhost/articles] |
当然ながら、ブラウザで確認してみても、これまでの作業の内容が確認できる。

リソースを削除する
RestClient.delete を使って投稿を削除する。
1番目に投稿したリソースの削除を行う。
1 2 3 4 5 6 7 |
>> article1 = get_article_list[0] => #<OpenStruct published_at=Sun Sep 06 09:13:00 +0900 2009, resource_id=1, content="This is the first article!!", title="First Article"> >> RestClient.delete "http://localhost:3000/articles/#{article1.resource_id.to_s}" => " " >> get_article_list => [#<OpenStruct published_at=Sun Sep 06 18:17:00 +0900 2009, resource_id=2, content="This is the second article!\n Changed", title="Second Article">, #<OpenStruct published_at=Sun Sep 06 22:10:00 +0900 2009, resource_id=3, content="This is third article!!", title="Third Article">] |
確かに削除されている。
サーバ側のログも確認する。
1 2 3 4 5 |
Processing ArticlesController#destroy (for 127.0.0.1 at 2009-09-07 00:47:24) [DELETE] Parameters: {"id"=>"1"} Article Load (0.2ms) SELECT * FROM "articles" WHERE ("articles"."id" = 1) Article Destroy (0.6ms) DELETE FROM "articles" WHERE "id" = 1 Completed in 10ms (View: 1, DB: 1) | 200 OK [http://localhost/articles/1] |
REST Client のそれ以外の特徴
HTTP のベーシック認証
HTTP のベーシック認証にも対応している。リソースの前に <account id>:<password>@ を追加してあげればよい。
以下は、delicious で最後にブックマークを追加した時間を取得する API を叩いた例。
1 2 3 4 |
>> RestClient.get 'https://taro:password@api.del.icio.us/v1/posts/update' => "<?xml version="1.0" encoding="UTF-8"?>\n <update time="2009-09-06T07:23:43Z" inboxnew="0"/>\n <!-- fe10.feeds.del.ac4.yahoo.net compressed/chunked Sun Sep 6 09:09:45 PDT 2009 -->\n" |
ロギング
RestClient.log= にて、ログのファイル名、または stdout、stderr も指定できる。
以下は、stdout を指定した例。
1 2 3 4 5 6 7 8 |
>> RestClient.log = 'stdout' => "stdout" >> RestClient.get 'https://taro:password@api.del.icio.us/v1/posts/update' RestClient.get "https://drtaka:fastone13@api.del.icio.us/v1/posts/update" # => 200 OK | text/xml 179 bytes => "<?xml version="1.0" encoding="UTF-8"?>\n <update time="2009-09-06T07:23:43Z" inboxnew="0"/>\n <!-- fe10.feeds.del.ac4.yahoo.net compressed/chunked Sun Sep 6 09:09:45 PDT 2009 -->\n" |
ヘッダーの取得
RestClient::Response#headers でレスポンスの HTTP ヘッダーの取得が可能。
1 2 3 4 5 6 7 8 |
>> (RestClient.get 'http://localhost:3000/articles').headers => {:etag=>""33ccfb63496b854f2bd158a91962a48a"", :x_runtime=>"13", :content_type=>"application/xml; charset=utf-8", :cache_control=>"private, max-age=0, must-revalidate", :content_length=>"793", :connection=>"close", :date=>"Sun, 06 Sep 2009 16:17:11 GMT"} |
その他
それ以外にも、プロキシの指定、restclient コマンドでの操作、など扱い易いインターフェイスを揃えてくれている。以下の readme を参照。
参考サイト
Emacs で Cucumber の Feature を編集し易くする feature-mode.el
Cucumber の Feature の記述を楽にしてくれる elisp。
インストールと設定の例は以下の通り。
($HOME/.emacs.d 配下に追加の elisp は突っ込んでいる。)
1 2 |
$ cd ~/.emacs.d $ git clone git://github.com/michaelklishin/cucumber.el.git cucumber |
.emacs には以下を追加。
1 2 3 |
(add-to-list 'load-path "~/.emacs.d/cucumber") (autoload 'feature-mode "feature-mode" "Mode for editing cucumber files" t) (add-to-list 'auto-mode-alist '("\\.feature$" . feature-mode)) |
git clone したディレクトリの中に snippets も用意されているので、snipet の laod directory にパスを追加しておく。
yasnippet の設定は、下記のページなどが参考になる。
HotPads の Amazon EC2/S3 を利用したサービスの実際のコスト
"クラウド"という言葉はちょっと前まではあまり好きではなかった。 その言葉に対しての実体がイマイチイメージがつき辛く、ボンヤリしたものであったためだ。
(なんちゃって)コンサルタントの方々がその言葉を口にして、予算を握っている方々に、 "クラウド" という言葉通りのボンヤリとした絵の書かかれたスライドを片手に、 もっともらしい話をしているのを各所で聞いていた影響もあるのかもしれない。。
ただ、最近はサービスの実用化が進んでおり、個人的にも興味を惹かれ始めていることもあり、現実的な話がアンテナにひっかかってくるようになった。
HotPads が示した AWS の具体的な利用例とそのコスト
この記事は結構インパクトがあった。
上記のスライドの中で、サービス規模、システム構成、実際にかかっている月々のコストが具体的に示されている。
運用しているサービスの規模としては、
- 800,000 訪問者/月
- 4.5 百万ページビュー/月
- 日々アップデートされる 3.5 百万件の不動産情報
となっている。めちゃくちゃ大規模というものではないが、かといって小規模という枠でもない。
上記スライドの内容をうまくまとめてくれているコラムが以下のコラム。
上記からコストの詳細部分を引用する。
Costs
・EC2 - $7400/month - run 20 of various size instances at anyone time. Most work is in the background processing of images, not web serving.
- $150: 2 Small HAProxy Load Balancers - 2 for failover, these have the elastic IPs, round robin DNS point at the elastic IPs.
- $1,200: 3-5 Large Tomcat Web Servers - an array of 3 run at night and 5 during the day.
- $1,500: 5 Large Tomcat Job Servers
- $900: 1 X-Large 1 Large Index Server - used to power property search and have several GB of RAM for the JVM
- $1,200: 1 X-Large 2 Large MySQL masters
- $1,200: 1 X-Large 2 Large MySQL slaves
- $300: 1 Large Messaging Server ActiveMQ - will be replaced with SQS
- $300: 1 Large Map tile creation servers Tilecache
- $600: Development/testing/migration/ servers
・S3 - $1500/month - few hundred million objects for files for maps and real-estate listing photos. 4TB of database backup stored as EBS diffs ($600/month).
・Elastic Block Storage - $500/month
・CloudFront - $460/month - is used to serve static files and map files throughout the world. It serves static files, map tiles, and listing photos.
・Elastic IP Addresses - $8/month
・RightScale - $500/month - used for management and deployment.
現在 100円 を切っているが、1ドルを100円計算とした場合、約110万/月で上記のサービス規模、システムを稼動させている。
金額だけの観点からいくと、以前プロバイダに支払っていた金額と同じ金額だということだ。このコストについて、同じコラムで Todd Hoff 氏は以下の感想を述べている。
I found this is a little surprising as I thought the cloud would be more expensive, but they only pay for what they need instead of having to over provision for transient uses like testing. And some servers aren't necessary anymore as EBS handles backups so database slave servers are no longer required.
私にとってはこのことはちょっとした驚きだった。クラウドはもっとコストのかかるものだと思っていたのだが、テスト環境のような一時的な利用における備えをしておく必要はなく、それが必要な時に必要な金額だけを払えばいいのだ。また、(実際の運用では必要になる)幾つかのサーバはもはや必要ないのだ。例えば、EBS がバックアップは行ってくれるので、スレーブのデータベースサーバはもはや必要なくなっている。
(括弧内の言葉は自分の方で補記した。)
この HotPads の例から学んだこととして、"Lessons Learned" の項の中で Todd Hoff 氏は幾つか挙げている。印象に残ったところを。
・Overall cost is about the same as with previous hosting site but the overall speed of development and ease of management is night and day different. Getting more servers and lots more flexibility.
・全体のコストは、以前のホスティングサイトとほぼ同じ。だが、開発のスピードと管理の容易さは、夜と昼ほどの違いがある。より多くのサーバを得られて、より多くの柔軟性を得る。
インフラのスペシャリスト達が作ったプラットフォームで、そのプラットフォームの運用は任せられる。 ソフトウェアと同様、インフラ部分においても大変なところは初期の構築はもちろんだが、その後の運用。 当然ながらコストの中には"もの"だけではなく、人件費(その人間力/技術力含めて)のコストが含まれる。
セキュリティ、もろもろの規定含めて考えないといけないところはあるかと思うが、PaaS (Platform as a Service) という"クラウドサービス"はとても魅了的に映る。
これまでのように、3年後、5年後にはシステム開発のあり方はまた変化しているのだろう・・・と思わざるを得ない。その手法だけでなく、業界の構造としても。
参考サイト
Radiant CMS - フレキシブルな Ruby On Rails ベースの CMS
これまで Radiant に何度か触れながら、そもそも Radiant って何?というメモを残していなかったので、改めて。
Radiant とは?
Radiant is a no-fluff, open source content management system designed for small teams. It is similar to Textpattern or MovableType, but is a general purpose content management system (not just a blogging engine).
"no-fluff" という表現は時々見かけるのだが、どのようなニュアンスなのだろう。。
Radiant は Ruby On Rails をベースに作成されたオープンソースの CMS (Content Management System) アプリケーションになる。
Radiant が提供する管理画面を利用してブラウザからコンテンツの作成、編集などのコンテンツ管理が行える。
コンテンツの作成には、HTML 以外に Radiant タグという便利なタグが使用できるようになっており、条件文、コンテンツの繰り返し処理など、静的なページだけでなく動的なページの作成も可能となる。
また、必要に応じてエクステンションという拡張モジュールを導入することで追加のタグや機能が利用可能となり、フレキシブルなサイト構築が可能となる。
必要とする機能がエクステンションとして提供されていなければ、エクステンションを自分で作成してしまえばよい。 Ruby On Rails を扱ったことがあれば、そこまで難しい話ではない。
Radiant の概要を理解するのは、デモを使ってみるのが手っ取り早い。
現在 2009/06/14 にリリースアナウンスが行われた v.0.8.0 が最新バージョンであるが、Radiant 自体は 2006年(確か)から開発が続いており、歴史もあり、完成度も高い。
海外では結構実績のあるアプリケーションになるのだが、日本では Ruby 本家のサイトで採用されていたりはするのだが、メジャーな存在だとはいい難い。。その理由については、思いつくところをこのメモの後ろ方で考えてみた。
Radiant の特徴
上記ページにて 10 個の特徴が挙げられている。
シンプルな管理者サイトのインターフェイス
Built from the ground up to be as simple as possible, Radiant features an elegant administrative interface that centers around three key components: pages, snippets, and layouts.
可能な限りシンプルに構築されている。3つのキーコンポーネント (ページ、スニペット、レイアウト)を軸としたエレガントな管理者サイトのインターフェイスに特徴がある。
ページ (Pages)
Pages contain the meat of the content for a Web site and may use Markdown, Textile, or plain HTML. Pages are composed of multiple parts such as a body and sidebar.
ページは、Web サイトのコンテンツを意味する。その記述には、Markdown、Textile、プレーンな HTML の利用が可能となる。ページは、body、sidebar といった複合的なパート(multiple part)によって構成される。
スニペット (Snippets)
Content that is used in multiple places can be stored in a snippet. Snippets are very similar to PHP includes or Rails partials.
いろいろなところで利用されるコンテンツは、スニペットの中にストアしておく。スニペットは、PHP の includesや Rails の partials によく似ている。
レイアウト (Layouts)
Layouts generally contain most of the HTML for a page's design. Layouts can render pages parts in any way they choose. One layout could choose to render the body and sidebar of a page, while another layout (a print layout) could render only the body.
レイアウトは、通常、ページのデザインのための HTML の大半を含む。レイアウトは、レイアウトに記載されるページのパーツをレンダリングする。あるレイアウトでは、body と sidebar をレンダリングし、別のレイアウト(印刷用レイアウト)には、body だけをレンダリングさせることができる。
フレキシブルなサイト構成
Unlike many other blogging engines Radiant allows you to arrange pages according to any hierarchy. A Weblog in Radiant can be as simple as a collection of child pages underneath a parent page.
多くの他のブログエンジンとは異なり、Radiant は階層構造によってページの管理を行える。Radiant におけるウェブログは、"親ページ"の下に位置する単純な"子ページ"のコレクションとなる。
Radius テンプレート言語
Radiant has a special macro language (similar to HTML) called Radius which makes it easy to include content from other pages, iterate over page children, and display content conditionally. Radius tags are available in pages, snippets, and layouts.
Radiant は、Radius という特別なマクロ言語(HTML に似ている)を使用できる。そのマクロ言語は、他のページからのコンテンツを含めることや、"子ページ" をイテレートすることや、条件に合わせてコンテンツを表示する、といったことを容易に可能とする。Radius タグは、ページ、スニペット、レイアウトで利用可能。
カスタムテキストフィルター
Radiant ships with support for Markdown and Textile, but developers can easily create text filters for any markup language such as RDoc, BBCode, or Structured Text.
Radiant は Markdown と Textile のサポートを標準装備としている。とはいえ、開発者は RDoc、BBCode、構造化されたテキストのようなマークアップ言語のテキストフィルターを容易に作成するこができる。
インテリジェントなページキャシング
Radiant includes an intelligent caching mechanism which allows content to be cached for a maximum of 5 minutes. This ensures that content is always fresh and provides an optimal level of performance.
Radiant はコンテンツを最大5分間キャッシングするインテリジェントなキャッシュの仕組みを持っている。このことは、コンテンツがいつも新鮮であることを保証しつつ、最適なレベルのパフォーマンスを提供する。
Ruby On Rails で構築されている
Radiant is built using Ruby on Rails. This means it’s easy for developers to extend Radiant because it’s built on a widely accepted (and understood) Web application development platform.
Radiant は Ruby On Rails を使用して構築されている。このことは、開発者にとっては Radiant を容易に拡張できることを意味する。なぜなら、Ruby On Rails は、広く受け入れられ(かつ理解されている) Web アプリケーション開発プラットフォームだから。
MIT ライセンス
Radiant is licensed under the MIT License. This means that Radiant is free for commercial and non-profit use. It also means that you are free to modify and distribute Radiant as long as you don't remove the appropriate notices from the source code.
Radiant は MIT ライセンスのもと、ライセンスされている。このことは、Radiant が商用、非営利の利用に対して自由に利用できることを意味する。また、ソースコードからしかるべき条項(著作権表示、許諾表示など)を削除しない限り、Radiant を変更することや、配布することも自由であることを意味する。
Radiant のインストール
gem コマンドで一発だ。
$ sudo gem install radiant |
Radiant プロジェクトの作成
radiant コマンドを使用して、Radiant プロジェクトの作成を行う。radiant コマンドの詳細は、ヘルプを参照。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
$ radiant -v Radiant 0.8.0 $ radiant -h Usage: /opt/local/bin/radiant /path/to/radiant/app [options] Options: -r, --ruby=path Path to the Ruby binary of your choice (otherwise scripts use env, dispatchers current path). Default: /opt/local/bin/ruby -d, --database=name Preconfigure for selected database (options: mysql, postgresql, sqlite3, sqlserver). Default: mysql Radiant Info: -v, --version Show the Radiant version number and quit. -h, --help Show this help message and quit. General Options: -p, --pretend Run but do not make any changes. -f, --force Overwrite files that already exist. -s, --skip Skip files that already exist. -q, --quiet Suppress normal output. -t, --backtrace Debugging: show backtrace on errors. -c, --svn Modify files with subversion. (Note: svn must be in path) |
新しい Radiant アプリケーションの作成、そして、初期設定を行う。
以下は、データベースに sqlite3、Radiant アプリケーションの名前として my-app を指定して Radiant アプリケーションの新規作成を行っている。
$ radiant -d sqlite3 my-app |
アプリケーションディレクトリに移動して、初期設定を行う。途中何度か入力の必要がある。
1 2 3 4 5 6 7 8 9 10 11 |
$ cd my-app $ rake production db:bootstrap (in /Users/hoge/work/radiant/my-app) This task will destroy any data in the database. Are you sure you want to continue? [yn] y == CreateRadiantTables: migrating ============================================ ... Create the admin user (press enter for defaults). Name (Administrator): Username (admin): Password (radiant): |
管理者の情報を入力する。() 内がデフォルトの値となる。Name はそのままでよいだろう。Username、Password は管理サイトにログインする際に必要になる。
1 2 3 4 5 6 7 8 |
Initializing configuration.........OK Select a database template: 1. Empty 2. Roasters (a coffee-themed blog / brochure) 3. Simple Blog 4. Styled Blog [1-4]: 2 |
テンプレート(サンプル)となるページを選択する。
1 はその名の通りテンプレートは何もなく全くの白紙からページを作っていくことになる。
これを選択した場合、おそらく管理サイトを見た瞬間、手が止まるだろう。。
初めて Radiant プロジェクトを作成する際には、2 〜 4 のテンプレートを選択し、実際にどのようにコンテンツ管理を行っているのか、参考にするのがよいと思う。
1 2 3 4 5 6 |
Creating Snippets....OK Creating Pages....OK Creating Layouts....OK Creating Page parts....OK Finished. |
以上で Radiant プロジェクトが作成されている。
起動する。
1 2 3 4 5 |
$ script/server -e production => Booting Mongrel => Rails 2.3.2 application starting on http://0.0.0.0:3000 => Call with -d to detach => Ctrl-C to shutdown server |
http://localhost:3000/ にアクセスしてみる。
以下は、"Select a database template" の選択の際に、" 2. Roasters (a coffee-themed blog / brochure)" を選択した場合のテンプレートになる。

管理サイトには、http://localhost:3000/admin でアクセスする。

ログイン後の管理サイトのトップページ。

上記は、Mongrel サーバを使って簡易的に動作させている。
本格的に運用する場合には、Ruby On Rails を稼動させる環境の選択肢は幾つかあるが、Apache のモジュールとして連動させることができる Passenger が設定も比較的容易でお勧めだ。
(蛇足) CMS として、Radiant が日本でなぜブレイクしないのか
個人的には非常によいアプリケーションだと思っているのだが、しつこいが、日本では流行っていないように感じる。。
理由はいろいろあると思うのだが、以下思い付く範囲で。
- Ruby On Rails がベースになっている (日本に限った話ではない。。)
- 日本語による情報の不足 + 国際化対応の遅れ
- Ruby On Rails を十分なパフォーマンスで稼動させる手軽な環境(サービス)の不足
- Radiant コアがシンプルすぎる? (日本に限った話ではない。。)
- 開発者でない方には少々扱いが難しい? (日本に限った話ではない。。)
「日本」に限った話ではなくなっている。。
Ruby On Rails がベースになっている
「日本」に限った話ではない。
Ruby On Rails は非常によく考えられたフレームワークで、Ruby の世界だけでなく、他言語の世界にもインパクトを与えた。 Ruby On Rails をベースとしていることはメリットではあるのだが、Ruby On Rails を触ったことのない方には敷居になる。
Radiant はある程度の環境を作りあげてしまえば言語の知識など必要なくなるのだが、そのある程度の環境にもっていくまでに多少の Ruby On Rails の知識を要する。
と言っても、それ程専門的な知識ではないのだが、Ruby On Rails を触ったことのない方にとってはちょっとだけ学習コストがかかる。
ちなみに、Radiant にインスパイアされた PHP 版 Radiant である Frog CMS というものもある。
日本語による情報の不足 + 国際化対応の遅れ
のような精力的な活動もあり、最近 Radiant に関する書籍も出版されたようなので、徐々に解消されていくのではないかと思っている。
だが、5つ目の理由を含めて日本語の情報の充実だけでは難しい面もあるように感じている。
国際化対応については、コンテンツの作成に関しては、日本語を使用しても問題ないのだが、管理画面は手を入れないと表示が英語のままとなる。
GitHub のブランチで i18n 対応は進められているが、v0.8.0 のリリースには、残念ながら含まれなかった。
Ruby On Rails を十分なパフォーマンスで稼動させる手軽な環境(サービス)の不足
専用サーバ、VPS 以外で手軽にレンタルサーバを借りて稼動することは難しい。海外だと、
- Heroku
- Joyent
- RailsPlayground.com Ruby On Rails Hosting
- AVLUX Hosting :: Ruby Rails Radiant :: Subversion Trac Redmine :: Dedicated Servers :: Domain Names - Home
- Radiant Machine
など Ruby On Rails のアプリケーションを十分なパフォーマンスで稼動させることができるサービスがあるのだが、国内では敷居が高い状況が続いている。
Radiant コアがシンプルすぎる?
「日本」に限った話ではない。
それがよい面でもあり、ポリシー的には個人的にはよいと思っているのだが、例えば画像をアップロードしたいと思っても、Radiant コアにはその機能は無い。。
メジャーな CMS では最初から使えるような機能が、エクステンション形式での提供になっている。メジャーな CMS に備わっている機能を全て扱えるようにしようと思った場合に、幾つかのエクステンションを導入するという一手間がかかる。
これは、要件にマッチする、または、良質なエクステンションを自由に選択できるという利点でもあるが、エクステンションの数も半端ではないため、その選択に迷うことになるかもしれない。
自分自身そうだったため、今後エクステンションに関するメモを残しておこうと思っている。
また、この件については、Radiant のメーリングリストにおいても最近また議論がされており、Radiant More という感じで、よく使用されるエクステンションはそれを含めた状態での配布、もしくは、ほとんど手間なく導入できる仕組みが考えられている。
開発者でない方には少々扱いが難しい?
「日本」に限った話ではない。
先にも書いたが、ある程度の環境を整えるまでにエンジニア的な知識が必要となる。
ある程度の環境ができあがっていたとしても、WordPress や MovableType などのようなツールとは若干赴きを異にしていることもあり、「ちょっと更新お願い!」という感じであまり IT 系に詳しくない方に頼むのは難しいかもしれない。
開発者、特に Ruby、Ruby On Rails に触れあっている方にとっては非常に扱い易いものだと思うのだが、運用には一工夫必要かなと思う。
Heroku を利用して新規 Sinatra アプリを5分くらいで公開する
誇張表現でもなく、サービスの利用開始の手続から公開まで、本当に5分以内でできてしまう。。
Heroku は、"instant ruby platform" と言われるもので、ホスティングサービスではあるが、"準備不要なデプロイ環境" である。 ローカルで作成した Ruby アプリケーション(Rails、Sinatra、Merb etc)をコマンド一発で公開できるプラットフォームを提供する。
そのインターフェイスも素晴らしいが、サービスの公開にあたり、サーバ側のシステム管理作業を行う必要はなく、また、自動的にスケールしてくれる仕組みも提供している点が素晴らしい。
存在自体は以前小耳に挟んでいたのだが、
で触れられていたの読み、試してみることにした。
Heroku は長くベータ版として運用されていたが、2009/04/24 に商用サービスのスタートがアナウンスされた。
商用サービスがスタートした後も、無料で利用可能なサービスを残している。ちょっとしたアプリの公開、ステージング環境、テスト環境などとして利用できる。
ここでは、その公開手順のメモを残す。
Heroku サービスのサインナップ
サインナップを行う。
メールアカウントの入力を行うと、入力したアカウントにメールが送られてくる。
Heroku is a platform for instant deployment of Ruby apps. Develop your app using your local tools, then deploy via Git and the Heroku gem. Follow the link to activate your account:
http://heroku.com/signup/accept/oio2oo2o2o2
To learn more about deploying apps on Heroku, check out the docs:
http://docs.heroku.com
Have fun, and don't hesitate to contact us with your feedback.
- The Heroku Team http://heroku.com/
指示に従い、http://heroku.com/signup/accept/oio2oo2o2o2 (最後の数値は左記の通りではない) にアクセスする。
開いた画面にて、パスワードの登録を行い、アクティベートする。
Welcome 画面が開き、この後の作業ステップが書かれている。

これで Heroku サービスの利用が可能となる。
ローカル(開発環境)側の環境準備
gem で heroku コマンドをインストールする。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
$ sudo gem install heroku Password: Building native extensions. This could take a while... Successfully installed rest-client-1.0.3 Successfully installed configuration-0.0.5 Successfully installed launchy-0.3.3 Successfully installed json-1.1.7 Successfully installed heroku-1.0 5 gems installed Installing ri documentation for rest-client-1.0.3... Installing ri documentation for configuration-0.0.5... Installing ri documentation for launchy-0.3.3... Installing ri documentation for json-1.1.7... Installing ri documentation for heroku-1.0... Installing RDoc documentation for rest-client-1.0.3... Installing RDoc documentation for configuration-0.0.5... Installing RDoc documentation for launchy-0.3.3... Installing RDoc documentation for json-1.1.7... Installing RDoc documentation for heroku-1.0... |
お試しなので、デプロイするアプリは何でもよいのだが、ここでは、GitHub にある Sinatra アプリの Heroku へのデプロイサンプルを使ってみる。
"Congradulations! You're running a Sinatra application on Heroku!" とだけ表示する非常にシンプルなアプリケーションだ。。
1 2 3 4 5 6 7 8 9 |
$ cd ~/work/ $ git clone git://github.com/sinatra/heroku-sinatra-app dr-heroku-sinatra-test Initialized empty Git repository in /Users/hoge/work/dr-heroku-sinatra-test/.git/ remote: Counting objects: 38, done. remote: Compressing objects: 100% (32/32), done. remote: Total 38 (delta 8), reused 0 (delta 0) Receiving objects: 100% (38/38), 5.58 KiB, done. Resolving deltas: 100% (8/8), done. $ cd dr-heroku-sinatra-test/ |
アプリケーションを Heroku に配置する
heroku コマンドを使って、ローカルにあるアプリを Heroku にデプロイできる状態にする。
この作業を実施するにあたっては、
- アカウント作成時に登録を行ったメールアドレスとパスワードの準備
- SSH で使用する鍵
- Git の利用環境
上記3点が用意されている必要がある。
heroku create を利用する。引数には、任意のアプリケーション名称を与える。
1 2 3 4 5 6 7 |
$ heroku create dr-heroku-sinatra-test Enter your Heroku credentials. Email: hoge@designrecipe.jp Password: Uploading ssh public key /Users/hoge/.ssh/id_rsa.pub Created http://dr-heroku-sinatra-test.heroku.com/ | git@heroku.com:dr-heroku-sinatra-test.git Git remote heroku added |
上記コマンドの中で、SSH で利用する公開鍵の登録も行っている。
また、git の設定情報が変更されている。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$ git config --list user.name=Hoge Fuga user.email=hoge@designrecipe.jp color.ui=auto core.repositoryformatversion=0 core.filemode=true core.bare=false core.logallrefupdates=true core.ignorecase=true remote.origin.url=git://github.com/sinatra/heroku-sinatra-app remote.origin.fetch=+refs/heads/*:refs/remotes/origin/* branch.master.remote=origin branch.master.merge=refs/heads/master remote.heroku.url=git@heroku.com:dr-heroku-sinatra-test.git remote.heroku.fetch=+refs/heads/*:refs/remotes/heroku/* |
remote の情報が追加されているようだ。
Heroku の環境に配置する。と言っても、通常の Git コマンドを利用して push しているだけだ。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
$ git push heroku master The authenticity of host 'heroku.com (75.101.145.87)' can't be established. RSA key fingerprint is 8b:48:5e:67:0e:c9:16:47:32:f2:87:0c:1f:c8:60:ad. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added 'heroku.com,75.101.145.87' (RSA) to the list of known hosts. Counting objects: 38, done. Compressing objects: 100% (24/24), done. Writing objects: 100% (38/38), 5.72 KiB, done. Total 38 (delta 8), reused 38 (delta 8) -----> Heroku receiving push -----> Sinatra app detected Compiled slug size is 4K -----> Launching...... done http://dr-heroku-sinatra-test.heroku.com deployed to Heroku To git@heroku.com:dr-heroku-sinatra-test.git * [new branch] master -> master |
以上で完了となる。
デフォルトでは、heroku.com のサブドメインとして、<app_name>.heroku.comで公開される。
<app_name> は、heroku create を使用した際に命名したアプリケーション名になる。
上記手順の場合、http://dr-heroku-sinatra-test.heroku.com/ になる。

当然ながら、自ドメインも利用することは可能。
参考サイト
- Heroku | Heroku Quickstart Guide
- Heroku | Deploying Rack-based apps
Rack ベースのアプリ(Sinatra、Merb、Ramaze など)のデプロイ方法。 - InfoQ: Herokuの準備不要なRailsアプリケーションホスティングは革新的
Sinatra ベースの軽量アプリいろいろ Scanty 編
Sinatra ベースのアプリを調べており、その一環として、あちこちで名前を聞く手頃なアプリを見てみることにする。
とってもシンプルなブログツール Scanty
「スキャンティ」と片仮名で書いてしまうと、若干いやらしくも響くが、とてもシンプルな Sinatra ベースのブログエンジンだ。
作者本人もこのツールを利用してブログを書いているようだ。
と・・・この作者の方って、Heroku の人なんだ。。
Scanty の特徴としてはサイトの記載から拾ってみると、以下のような内容になっている。
特徴
- ブログ"エンジン"とまでは言えないけれど、コンパクトでカスタマイズを入れるのは容易。
ブログツールの基礎として使える。 - ポストできる!(特徴かな。。)
- タグ付け
- 入力に使用できる記法: Markdown
- コメント機能: Disqus
- Atom feed
作者も書いているように、これを土台に自分色に染めてね(この通りには作者は言っていない)、というものである。
コメント機能に関しは、ツールの中に抱えないで、DISQUS を使用するようにしている。
原材料
- Web フレームワーク: Sinatra
- Markdown -> HTML への変換: Maruku
- コードシンタックスハイライト(Only Ruby): Syntax
- ORM: Sequel
ライセンスは、MIT License だ。
軽く動作を見てみて、ちょっと気になるところには手を入れてみることにする。
インストールと設定、そして起動
Sinatra がもしインストールされていないのであれば先に Sinatra を入れておく。
$ gem sudo install sinatra |
Scanty を GitHub から clone し、自分用のブランチを用意しておく。
1 2 3 4 5 6 7 |
$ cd ~/work $ git clone git://github.com/adamwiggins/scanty.git $ cd scanty $ git checkout -b taka-blog $ git branch master * taka-blog |
main.rb に書いてあるデフォルトの設定を変更しておく。
1 2 3 4 5 6 7 8 9 |
Blog = OpenStruct.new( :title => 'a scanty blog', :author => 'John Doe', :url_base => 'http://localhost:4567/', :admin_password => 'changeme', :admin_cookie_key => 'scanty_admin', :admin_cookie_value => '51d6d976913ace58', :disqus_shortname => nil ) |
とりあえずローカルでの動作確認レベルであればこのままでもよいが、外部公開するのであれば、
:admin_password:admin_cookie_key:admin_cookie_value
の変更が必須だろう。cookie value はランダムな値で。
起動する。
$ ruby main.rb |
先に設定した main.rb の :usr_base のアドレスにアクセスする。
また、rake による起動も可能。
1 2 3 4 |
$ rake -T (in /Users/taka/work/sinatra/scanty) rake start # Start the app server rake stop # Stop the app server |
Rakefile に start、stop タスクが定義してある。port 番号の定義がデフォルトで 3030 になっているので、適切なポート番号に変更。
投稿してみる
サーバを起動し、初めてアクセスを行うと以下の画面が表示される。

まずは、"Log in" をクリックしログインする。
(次回以降のログインの際には、

パスワードが合っていれば、最初に開いた画面に戻るので、今度は、"create a post" をクリックする。

投稿画面は非常にシンプル。項目のラベルが何もないが、上から、タイトル、タグ、本文の入力項目となっている。
タグはスペース区切りで複数指定できるようになっている。
"save" ボタンで投稿する。

続けて投稿してみて、トップページを見てみる。

2回目の投稿に、Ruby のソースを含めてみたのだが、少々表示がおかしいようだ。 また、入力の必須チェックなどが全く入っていない、日本語を含んだタイトルはまともに扱えない、というところがあるので、手を入れてみる。
オリジナルに変更を入れておく
起動してちょっと見ていただけで、とりあえず手を入れておきたいところが数点あった。手始めに以下の3点の変更を入れておくことにする。
- 入力項目の必須チェック
- タイトルで日本語の利用を可能に
- ソースコードの表示部分の変更
入力項目の必須チェックぐらいは入れておく
全く入っていないので入れておく。
この Scanty は、ORM に Sequel を使用している。Sequel は初体験で全く知識がない。ActiveRecord に似てるところもあるので、今回は深く調査せず、勘だけで書いてみる。。
検証ロジックは、ActiveRecord 同様、Model の validate メソッドに入れておいて、エラーのコレクションを追加してやればよさそうだ。
検証失敗時のハンドルは、Sequel::Model#save 時の validation チェックにが raise する Sequel::ValidationFailed の例外でハンドルしようと思ったのだが、Sequel::ValidationFailed がないと言われる。。
Scanty は、Sequel を vendor ディレクトリ配下に持っているのだが、取得したリポジトリに含まれているものはバージョン的に古そうだ。gem で最新のものをインストールし、そちらを使うようにする。自前で持っている vendor 配下への load path の追加は消しておく。
1 2 3 4 5 |
$ gem list sequel *** LOCAL GEMS *** sequel (3.2.0) |
サーバの再起動をかけると、
1 2 |
$ ruby main.rb ./lib/post.rb:7: undefined method `table_exists?' for Post:Class (NoMethodError) |
と怒られる。
新しいバージョンではお作法が変わったようだ。Sequel::Model.plugin(:schema) を main.rb の configure メソッドのコードブロックに追加しておく。
main.rb:
1 2 3 |
configure do Sequel::Model.plugin(:schema) # 追加 Sequel.connect(ENV['DATABASE_URL'] || 'sqlite://blog.db') |
(なんとも手探りな状態だ。。)
気を取り直して Model にバリデーションを入れておく。
lib/post.rb:
1 2 3 4 5 6 7 |
class Post < Sequel::Model ... # validate メソッドの追加 def validate errors[:title] << "can't be empty" if title.empty? errors[:body] << "can't be empty" if body.empty? end |
そのハンドリングは、
main.rb:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
post '/posts' do auth post = Post.new( :title => params[:title], :tags => params[:tags], :body => params[:body], :created_at => Time.now, ) # begin - resucue - end を追加 begin post.save redirect post.url rescue Sequel::ValidationFailed erb :edit, :locals => { :post => post, :url => '/posts' } end end |
としておく。
エラーがあった時の表示だが、さすがに、ActionView::Helpers::ActiveRecordHelper#error_messages_for のようなものは無い。。
適当に作っておく。
main.rb:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
helpers do ... # なんちゃって error_message_for を追加 def error_message_for(model) unless model.errors.size == 0 html = '' html << '<ul>' model.errors.each do |k, v| html << "<li>#{k.to_s.capitalize} #{v.first}.</li>\n" end html << '</ul>' else '' end end end |
view に追加しておく。
lib/edit.erb:
1 2 |
<%# error_message_for の呼び出しを追加 %> <%= error_message_for(post) %> |
一応、これで必須チェックは入るようになる。

タイトルで日本語の表示を可能に
最近の舶来物の場合、permalink の生成に以下のようなロジックを入れてくる。
lib/post.rb:
1 2 3 |
def self.make_slug(title) title.downcase.gsub(/ /, '_').gsub(/[^a-z0-9_]/, '').squeeze('_') end |
これだと、マルチバイト文字は空文字になってしまう。。それがタイトルに日本語を使えない理由。
URL エンコードするのは嫌なので、対応としては、タイトルはタイトルとして入力値をそのまま残し、permalink を構成する slug 部分は、投稿者に自分で入れてもらうことにする。
- view に入力用の slug フィールドの追加
- Post インスタンス生成時のパラメータに直接 slug フィールドの入力項目を設定
- slug フィールドも必須チェック対象に
とすることにする。
1つ目の対応。
lib/edit.erb:
1 2 3 4 |
<div class="postzoom"> <h2 class="title"><input type="text" name="title" value="<%= post.title %>" /></h2> <p class="meta"><input type="text" name="slug" value="<%= post.slug %>" /></p> <!-- 追加 --> <p class="meta"><input type="text" name="tags" value="<%= post.tags %>" /></p> |
2つ目の対応。
main.rb:
1 2 3 4 5 6 7 8 9 |
post '/posts' do auth post = Post.new( :title => params[:title], :tags => params[:tags], :body => params[:body], :created_at => Time.now, :slug => params[:slug] # 追加 ) |
3つ目の対応。
lib/post.rb:
1 2 3 4 5 6 7 |
class Post < Sequel::Model ... def validate errors[:title] << "can't be empty" if title.empty? errors[:slug] << "can't be empty" if slug.empty? # 追加 errors[:body] << "can't be empty" if body.empty? end |
以上で OK。
投稿画面にラベルが無いと流石にわかり難くなってきたので、ラベルをついでにつけておく。

ソースコードの表示部分の変更
投稿された本文は、Markdown のパーサ(Maruku) を通って HTML 化され、さらに Syntax を通ってコードハイライト用のタグが補足される。
ソースの該当箇所は、以下のようになっている。
lib/post.rb:
1 2 3 4 5 6 7 8 9 10 11 |
class Post < Sequel::Model .... def to_html(markdown) h = Maruku.new(markdown).to_html h.gsub(/<code>([^<]+)<\/code>/m) do convertor = Syntax::Convertors::HTML.for_syntax "ruby" highlighted = convertor.convert($1) "<code>#{highlighted}</code>" end end .... |
で、表示は以下の通り。

まず、ソースコードの枠の部分が2重になっているのは、<pre>...</pre> の中に、<code><pre>...</pre><pre>...</pre></code> と <pre> タグが入れ子になってしまうからのようだ。作者のブログのページ を見てみたが、そうはなっていない。HTML の表記として何が正しいのかをキチンと確認する必要もあるが、
<pre>が入れ子になっている- ソースコード部分での実体参照への勝手な置き換えが行なわれている
という点に修正を入れないと表示がおかしくなってしまう。
まずは現在のソースでどのように動作するか、状況を把握する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
$ irb >> require 'rubygems' => true >> require 'maruku' => true >> require 'syntax/convertors/html' => true >> str = File.read('t.md') => "second!\n\nclass Hello\n def hello\n puts 'hello!'\n end\nend\n" >> org_reg = Regexp.new('<code>([^<]+)</code>', Regexp::MULTILINE) => code[]codem >> org_syn_convert_proc = lambda { "<code>#{Syntax::Convertors::HTML.for_syntax('ruby').convert($1)}</code>" } => #<Proc:0x0110169c@(irb):8> >> org_h = Maruku.new(str).to_html => "<p>second!</p>\n\n <pre><code>class Hello\n def hello\n puts 'hello!'\n end\nend</code></pre>" >> org_h.gsub(org_reg, &org_syn_convert_proc) => "<p>second!</p>\n\n <pre> <code> <pre> <span class="keyword">class </span> <span class="class">Hello</span>\n <span class="keyword">def </span> <span class="method">hello</span>\n <span class="ident">puts</span> <span class="punct">&</span> <span class="comment">#39;hello!&#39;</span>\n <span class="keyword">end</span>\n <span class="keyword">end</span> </pre> </code> </pre>" |
確かに問題となっている状況の通りだ。
この状況に対して、
- シンタックスハイライトを適用させるための Convert 処理を変更する
<pre>が入れ子になっている件への対応
- Markdown から HTML への変換には、Maruku の代わりに、BlueCloth を使う
- ソースコード部分での実体参照への勝手な置き換えが行なわれている件への対応
の2点を行ってみた。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
>> require 'bluecloth' => true >> changed_reg = Regexp.new('<pre><code>([^<]+)</code></pre>', Regexp::MULTILINE) => precode[]codeprem >> changed_syn_convert_proc = lambda { Syntax::Convertors::HTML.for_syntax('ruby').convert($1) } => #<Proc:0x0108f27c@(irb):15> >> b_h = BlueCloth.new(str).to_html => "<p>second!</p>\n\n<pre><code>class Hello\n def hello\n puts 'hello!'\n end\nend\n</code></pre>" >> b_h.gsub(changed_reg, &changed_syn_convert_proc) => "<p>second!</p>\n\n <pre> <span class="keyword">class </span> <span class="class">Hello</span>\n <span class="keyword">def </span> <span class="method">hello</span>\n <span class="ident">puts</span> <span class="punct">'</span> <span class="string">hello!</span> <span class="punct">'</span>\n <span class="keyword">end</span>\n <span class="keyword">end</span>\n </pre>" |
一応、これで表示上は問題なくなる。
(後で、もう少しちゃんと考えよう。)

最終的な Post#to_html の変更箇所は以下の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
感想
- 機能が最小限ということもあるが、Sinatra ベースのアプリは非常にシンプルでわかり易い。
- 「HTTP メソッド + パス」がダイレクトに処理コードに紐付くのは気持ちいい。
- Sequel って結構いいかも。
Sinatra の作者が言うように Sinatra はフレームワークというよりは、ライブラリ的なものだ。 Rack に対して非常にセンスのよいデコレーションを行っている。
Rails はフルセットのフレームワークであり、非常に有用で、その設計は勉強にもなる。 ただ、その内部は複雑化しており、何か問題のあった時などには知っておくべきことが多すぎて、思わぬところで時間をとられる場合がある。
適材適所で、シンプルで扱い易い Sinatra を使ってみたいと思えてくる。