Weeで継続

InfoQに、Weeという継続ベースのWebアプリケーションフレームワークが紹介されていたので、試してみました。

gemでインストール後、インストールディレクトリ下のexamples/demo.rbを実行すると、自動的にWEBrickが起動します。

Counterというカウントの増減ができるページが表示され、++で1増加、--で1減算となるのですが、途中で戻るボタンを押下し、その後++/--を押下しても戻った地点から計算されます。

Counter

同じことをHTTP Sessionを使ってやると、戻るボタンを押下しても最後にsubmitした値からの加減値となり、挙動が異なります。数値をhiddenに埋め込むとできるので、この例だとメリットを感じづらいところはありますが。

Weeのアプリケーションではaction実行時にbacktrackというメソッドが呼ばれ、そこでステートを保存することができます。

1
2
3
4
  def backtrack(state)
    super
    state.add_ivar(self, :@count, @count)
  end

それがURLのpage-idと関連付けられることで、戻るボタン押下時にステートが戻るようになっているとのこと。

The solution to this problem is to take snapshots of the components state after an action is performed and restoring the state before peforming actions. Each action generates a new state, which is indicated by a so-called page-id within the URL.

次にexamples/continuation.rbを動かしてみました。こちらは、callccメソッドにMessageBoxコンポーネントを引数で渡して呼ぶと、htmlが表示されます。表示されたページにはOK/Cancelボタンがあり、どちらかを押下するかによって次の処理が決まる、というものです。

clickメソッドが1回呼び出されると、その中のcallccメソッドの呼び出し回数分(条件によって2~3回)ページが表示されている、ということになります。

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
$LOAD_PATH.unshift "../lib"
require 'rubygems'
require 'wee'
require 'demo/messagebox'

class MainPage < Wee::Component

  def initialize
    super
    add_decoration(Wee::PageDecoration.new("Test"))
  end

  def click
    if callcc Wee::MessageBox.new('Really quit?')
      callcc Wee::MessageBox.new('You clicked YES')
    else
      callcc Wee::MessageBox.new('You clicked Cancel')
      callcc Wee::MessageBox.new('super')
    end
  end

  def render(r)
    r.anchor.callback_method(:click).with('show')
  end

end

Wee.runcc(MainPage)

あまり継続の良さを理解できていませが、複数のリクエストに跨ってステートフルなコンポーネントを簡単に扱うことができるのがWeeの特徴の1つなのだと思います。

上記ソースのcallccはWeeのメソッドで、Kernel.callccとは異なりますが、その実装は以下のようにKernel.callccを呼び出していました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    #
    # Similar to method #call, but using continuations.
    #
    def callcc(component)
      delegate = Wee::Delegate.new(component)
      answer = Wee::AnswerDecoration.new

      add_decoration(delegate)
      component.add_decoration(answer)

      answ = Kernel.callcc {|cc|
        answer.answer_callback = cc
        session.send_response(nil)
      }

      remove_decoration(delegate)
      component.remove_decoration(answer)
      return *answ.args
    end

jGrowl

jGrowlは、Mac OS XのGrowlをモチーフとしたメッセージを表示してくれるjQueryのプラグインです。 jQuery自体のバージョンは、1.3以上が推奨されてます。

今回はrailsのflash内のメッセージをjGrowlで表示させてみます。

1. ファイルを配置します。

  • $RAILS_ROOT/public/javascripts/jquery.jgrowl.js
  • $RAILS_ROOT/public/stylesheets/jquery.jgrowl.css
  • $RAILS_ROOT/public/stylesheets/jgrowl_custom.css(サンプルから一部切り出したテーマの設定)
  • $RAILS_ROOT/public/images/iphone.png(サンプルにあったiphoneライクなメッセージボードの画像)

2. railsのflashの内容が表示されるよう、.html.erb内にJavaScriptを記述します。
1
2
3
4
5
6
<script type="text/javascript">
$.jGrowl.defaults.position = 'center';
<% if flash[:notice] %>
$.jGrowl('<%= flash[:notice] %>', { header: 'notification', theme: 'iphone', life: 1000 });
<% end %>
</script>

‘life’で表示期間(ミリ秒)を指定します。ユーザが閉じるまで表示させたままにすることも可能です。

メッセージボードの画像とテーマ(iphone)は、jGrowlに付属しているサンプルをそのままjgrowl_custom.cssとして切り出して使っています。また、メッセージボードの表示位置について、デフォルトだとあまり選択肢がない(top-left, top-right, bottom-left, bottom-right, centerの計5種類)ので、同ファイル内で’center’の設定を上書き、表示位置を調整しました。
1
2
3
4
5
body > div.jGrowl.center {
        top:                            250px;
        left:                           250px;
        width:                          0%;
}

あとはflash[:notice]にメッセージを入れると、以下のように表示されます。

jGrowl
















pluginのinit.rbでメソッドを再定義できない理由

日付コントロールを変える」の最後で、プラグインの読み込みをconfig/environment.rbに記述しました。


require 'yads'

その後調べてみたら、vendor/plugin/(プラグイン名)/init.rbで上記コードを記述すれば良いことが分かりました。各プラグインのinit.rbはrailsの初期化プロセスから自動的に呼び出される為、script/plugin install ~ でインストールすれば、フレームワークのメソッド(前回の場合はActionView::Helper::DateHelper)を再定義することができます。

でもinit.rbが自動的に呼び出されるのであれば、最初からinit.rbでメソッドの再定義を行えば良いはず。

ということで、vendor/plugin/yads/lib/yads.rbの中身をvendor/plugin/yads/init.rbに移動して実行したところ、メソッドが再定義されておらず、元々のドロップダウンリストを使った日付コントロールが表示されていました。コードは以下のとおりです。

[vendor/plugins/yads/init.rb]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Include hook code here                 
module ActionView
  module Helpers
    module DateHelper
      def date_select(object_name, method, options = {}, html_options = { })
        options[:size] ||= 12
        html = InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("text", options)
        js = <<EOS

<script> 
$('##{object_name}_#{method}').datepicker({  dateFormat: 'yy-mm-dd'});
</script>
EOS
        html.concat(js)
      end
    end  
  end  
end

init.rbの中でrequireして再定義するのと、init.rbの中で直接再定義することの違いが分からず、webで調べてみたところ、以下のようなページがありました。

Platte daddy: Rails plugins: keep init.rb thin

Rails plugins' initializer script, init.rb, is currently invoked via eval, not require—so it inherits whatever module-space Rails calls it from. If you reopen any classes in init.rb itself (like the will_paginate guys quite reasonably attempted to define Hash#slice), your changes will be made—but to the wrong module. So, to avoid strange gotchas, consider init.rb just a generic hook point to kick things off, and always require in any code that's to do actual work at plugin load time.

「If you reopen any classes in init.rb itself, your changes will be made—but to the wrong module.」と記述されているので、init.rbで再定義してもダメなのはどうも正しいようですが、その理由がわからない…。そもそもプラグインの初期化時にはフレームワークのメソッドが定義されていないのか、と考えましたが、init.rbでrequireされるファイル内で再定義してもタイミングは同じはずだし…。

requireに何か特殊な仕掛け(たとえば遅延評価とか)があるのかと思い、Rubyのrequireメソッドを再定義しているActiveSupport::Dependenciesのログを出力させてみたりしたものの、プラグインの初期化時にはログが出力されていないことから、あまり関係あるようにも思えない、という結論に至り。

プラグインのinit.rbはどのように呼び出されているか調べてみると、以下のようになっていました。

[rails-2.3.2/lib/rails/plugin.rb]
1
2
3
4
5
6
7
8
9
10
def evaluate_init_rb(initializer)
  if has_init_file?
    silence_warnings do
      # Allow plugins to reference the current configuration object
      config = initializer.configuration
      
      eval(IO.read(init_path), binding, init_path)
    end
  end
end 

evalでinit.rbを評価しているので、普通に再定義できそう…と暫く気がつかなかったのですが、evalで評価ということは呼び出し側の名前空間(今回の場合はRails::Plugin)を引き継いでしまうので、それを考慮に入れる必要がありました。つまり、init.rbで以下のように記述すると、

1
2
3
module ActionView
  module Helpers
    module DateHelper
「Rails::Plugin::ActionView::Helpers::DateHelper」を定義していることになるという…。なので、「::ActionView」と書けば再定義することができます。
1
2
3
module ::ActionView
  module Helpers
    module DateHelper

結論としては、プラグインのinit.rbでメソッドの再定義はできるものの、lib下のファイル内で再定義してinit.rbでrequireすべき。

日付コントロールを変える

前回、「scaffoldした際に、引数で指定される各フィールドの型によって自動的に決まるwebのコントロールを、自分の都合の良いように変えてみたい」と思い、scaffoldのコードを見てみました。

その後よく考えてみると、field_typeによってコントロールを表示するメソッドがあるのだから、それ自体を上書きすればよいことに気がつきました。これだとscaffoldするかどうかは関係なく、既存のプロジェクトにも使えます。

ということで、今回は日付コントロールをカスタマイズしてみます。

scaffoldの引数に「date」というsql_typeを指定すると、new/editでは以下のようなコントロールが表示されます。

これを、jQuery UI Datepickerに変えてみます。

scaffoldによって生成されたnew.html.rbで、このコントロールを表示するためのコードは以下になります。
1
2
3
4
  <p>
    <%= f.label :atdate %><br />
    <%= f.date_select :atdate %>
  </p>

「f.date_select」を上書きすれば良いことがわかります。上書きする手段はいくつかあると思いますが、今回はそれ用のプラグインを作成しました。(今回はYADS[=Yet Another DateSelect]という名前のプラグインにしています)

vendor/plugins/yads/lib/yads.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module ActionView
  module Helpers
    module DateHelper
      def date_select(object_name, method, options = {}, html_options = { })
        options[:size] ||= 12
        html = InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("text", options)
        js = <<EOS
                                                                               
<script>                                               
 $('##{object_name}_#{method}').datepicker({dateFormat: 'yy-mm-dd'});  
</script>                                                                      
EOS
        html.concat(js)
      end
    end  
  end  
end
date_selectメソッドではドロップダウンリストが表示されるのですが、それをテキストボックスを表示するように変更しています。datepickerの表示はjavascriptの部分だけです。 javascriptの埋め込みは大分強引ですが、取り合えずということで。 今回はjQueryUIを使う為、datepickerを含むスクリプトを予めダウンロードしておき、スクリプトとスタイルシートを読み込むように設定しておきます。
1
2
3
  <%= stylesheet_link_tag 'scaffold', 'jquery/ui-lightness/jquery-ui-1.7.2.custom.css' %>
  <script type="text/javascript" src="/javascripts/jquery/jquery-1.3.2.min.js"></script>
  <script type="text/javascript" src="/javascripts/jquery/jquery-ui-1.7.2.custom.min.js"></script>
あとは、config/environment.rbの最後で、

require 'yads'
として作ったスクリプトを読むようにし、サーバを起動します。

日付の入力がドロップダウンリストからテキストボックスへと変わり、テキストボックスをクリックするとdatepickerが表示されます。

config/environment.rbでrequireしているのがイマイチなので、時間があれば自動的にロードさせられないか調べてみます。

scaffoldのコードを読む(その1)

scaffoldした際に、引数で指定される各フィールドの型によって自動的に決まるwebのコントロールを、自分の都合の良いように変えてみたいと思います。その為に、scaffoldに関連するコードを読んでみます。

前回railsからコピーしてきたtemplateから、view_edit.html.erbを見てみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<h1>編集 <%= singular_name %></h1>

<%% form_for(@<%= singular_name %>) do |f| %>
  <%%= f.error_messages %>

<% for attribute in attributes -%>
  <p>
    <%%= f.label :<%= attribute.name %> %><br />
    <%%= f.<%= attribute.field_type %> :<%= attribute.name %> %>
  </p>
<% end -%>
  <p>
    <%%= f.submit 'Update' %>
  </p>
<%% end %>

<%%= link_to 'Show', @<%= singular_name %> %> |
<%%= link_to 'Back', <%= plural_name %>_path %>

attribute.field_typeによって、コントロールが決まる仕組みになっています。field_typeは以下のようになっていました。

[rails-2.3.2/lib/rails_generator/generated_attribute.rb]
1
2
3
4
5
6
7
8
9
10
11
12
def field_type
  @field_type ||= case type
    when :integer, :float, :decimal   then :text_field
    when :datetime, :timestamp, :time then :datetime_select
    when :date                        then :date_select
    when :string                      then :text_field
    when :text                        then :text_area
    when :boolean                     then :check_box
    else
      :text_field
  end      
end

変数typeは、script/generate scaffoldの引数に指定する「名前:sql_type」のsql_typeになります。

ちなみに、同ファイルにはdefaultというメソッドが含まれており、以下のようになっています。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def default
  @default ||= case type
    when :integer                     then 1
    when :float                       then 1.5
    when :decimal                     then "9.99"
    when :datetime, :timestamp, :time then Time.now.to_s(:db)
    when :date                        then Date.today.to_s(:db)
    when :string                      then "MyString"
    when :text                        then "MyText"
    when :boolean                     then false
    else
      ""
  end      
end

先ほどのfield_typeメソッドの定義から、scaffoldの引数にoneday:dateと指定すると、「f.date_select :oneday」というコードが生成されます。

このfという変数は何かとform_forやその先のfields_forを辿っていくと、デフォルトではActionView::Base.default_form_builder(=FormBuilder)のインスタンスになります。

[actionpack/lib/action_view/helpers/form_helper.rb]
1
2
3
4
5
def fields_for(record_or_name_or_array, *args, &block)
...
  builder = options[:builder] || ActionView::Base.default_form_builder
  yield builder.new(object_name, object, self, options, block)
end

上記コードより、指定されたsql_typeによって任意のwebのコントロールを配置するには、カスタムFormBuilderを作ってform_forのoptionsに:builderを明示するか、FormBuilderの各メソッドを上書くことにより実現できそうです。

scaffoldのviewのテンプレートをカスタマイズする

昨日の『ソフトウエア開発プロフェッショナル』を読んだ後、エンジニアリングを軽視してはいけないと思いました。本書中にあった簡単に石を運ぶような仕組みというのは、事前準備の賜物でしょう。ということで、railsのscaffoldをカスタマイズしてみることに。 ActiveScaffoldみたいなものを自前で用意できたら便利かと思い、ちょっとやってみました。バージョンはRails 2.3.2です。まずはviewのテンプレートのカスタマイズから。

  1. mkdir -p RAILS_ROOT/lib/generators/my_scaffold/templates (my_scaffoldの部分は任意の名前で良い)
  2. cp RUBY_HOME/lib/ruby/gems/1.8/gems/rails-2.3.2/lib/rails_generator/generators/components/scaffold/scaffold_generator.rb RAILS_ROOT/lib/generators/my_scaffold/my_scaffold_generator.rb
  3. cp RUBY_HOME/lib/ruby/gems/1.8/gems/rails-2.3.2/lib/rails_generator/generators/components/scaffold/templates/* RAILS_ROOT/lib/generators/my_scaffold/templates
  4. RAILS_ROOT/lib/generators/my_scaffold/my_scaffold_generator.rbを開き、クラス名をMyScaffoldGeneratorに変更する
  5. RAILS_ROOT/lib/generators/my_scaffold/templates下のテンプレートファイルを適当に編集する

my_scaffoldを実行します。

  1. cd RAILS_ROOT
  2. script/generate my_scaffold Hoge name:string another_day:datetime

これで編集したテンレプートが適用されていることを確認できます。

Ruby1.8.7+Rails2.1.1→Ruby1.9.1+Rails2.3.2(その1)

これまで作ってきたものを、Ruby1.8.7+Rails2.1.1の環境からRuby1.9.1+Rails2.3.2に移行してみたいと思います。

1. rake rails:update:configsで失敗

1
2
3
4
5
6
masayuki@ubuntu-vm:~/work/rails/shrimp$ rake rails:update:configs
(in /home/masayuki/work/rails/shrimp)
rake aborted!
undefined method `>=' for nil:NilClass
/home/masayuki/work/rails/shrimp/Rakefile:4:in `require'
(See full trace by running task with --trace)
config/boot.rbで、rubygemsのバージョンの取得がうまくできず、失敗しているようでした。 dummyのプロジェクトを作成し、そこからconfig/boot.rbをコピーしてrake rails:update:configsを実行。

2. gettextで失敗

1
2
3
4
5
6
7
masayuki@ubuntu-vm:~/work/rails/shrimp$ rake rails:update:configs
(in /home/masayuki/work/rails/shrimp)
rake aborted!
/usr/local/ruby-1.9.1-p129/lib/ruby/gems/1.9.1/gems/gettext-1.93.0/lib/gettext/iconv.rb:102: invalid multibyte char…
/usr/local/ruby-1.9.1-p129/lib/ruby/gems/1.9.1/gems/gettext-1.93.0/lib/gettext/iconv.rb:102: invalid multibyte char…
/usr/local/ruby-1.9.1-p129/lib/ruby/gems/1.9.1/gems/gettext-1.93.0/lib/gettext/iconv.rb:102: syntax error, 
  puts Iconv.iconv("EUC-JP", "UTF-8", "ほげ").join
gettextは事情があって—version ‘< 2.0.0’としてきたのですが、上記のエラーで動かないので、最新バージョン(2.0.4)をインストール。

3. gettext/rails→gettext_rails


gettext_rails provides the localization for Ruby on Rails-2.3 or later using Ruby-GetText-Package. 
とのこと。

4. config.cache_template_extensions


undefined method `cache_template_extensions=' for ActionView::Base:Class
このメソッドはdeprecatedということで、コメントアウト。

5. app/controllers/application.rb→app/controllers/application_controller.rb

Rails2.3から、application.rbというファイル名が変更になっている為。リリースノートを読むと、rake rails:updateとすれば良かったらしい…。

6. jrailsの更新

jrailsはRuby1.9対応にする必要があります。

7. incompatible character encodings: ASCII-8BIT and UTF-8
  • ASCII-8BIT外の文字を使っているソースの先頭に「#coding: utf-8」をつけた
  • patchをあててみた
  • <%= collection_select … %>で展開される文字がASCII-8BITの外だと「incompatible character encodings: ASCII-8BIT and UTF-8」 ← いまココ

VMware PlayerとUbuntu 8.04 LTS

これまでずっとcoLinuxで開発をしてきました。が、ゲストOSとして使用してきたUbuntu7系のサポートが終わり、また容量もかなりギリギリになってきたので、この機会にUbuntu8に乗り換えようとcoLinuxのサイトに行ってみたところ、ダウンロードページが表示できませんでした・・・。

ということで、これを機にVMwareに乗り換えようと思い、VMware PlayerとUbuntu 8.04 LTSをダウンロード。

NATで外部に接続できるようにする為、ホストOSのネットワーク接続(「ローカルエリア接続)のプロパティ→「共有」タブにて、「VMnet8」(「ローカルエリア接続4」でした…)を選択し、「ネットワークのほかのユーザに、このコンピュータのインターネット接続をとおして接続を許可する」にチェック。この際、VMnet8のIP4のアドレスを書き換えてしまい、ゲストOS側から外に出ることができず大分はまりました。

VMware Playerがインストールされているディレクトリに「vmnetcfg.exe」というファイルがあり、これを実行するとNATサービスの設定の一部がわかるのですが、このサービスのアドレスがVMnet8の書き換える前のアドレスになっていた為、NATサービスに接続できなかったようです。

結局ホストOSとVMnet8のネットワークアダプタのアドレスを元に戻し、ホストOSから外部に接続できることを確認することができました。

heroku.com

herokuというrailsアプリケーションのホスティングサービスを使ってみました。以前はブラウザでrailsアプリが開発できる、ということでしたが、今はheroku gardenという名前になっています。ブラウザを使った開発も面白そうですが、まずはheroku.comを使ってみます。 heroku.comを使用する場合は、自分の環境でrailsアプリを作成したものをgit pushすると、herokuにdeployされます。料金はストレージと通信料、そして各種オプションで決まるようです。ストレージ5Mで通信がほとんど無ければfreeなので、その範囲内で試してみます。

まずはherokuをインストール。

$ gem install heroku
gitのremoteリポジトリを使用する為に、公開鍵をアップします。

$ heroku keys:add
railsアプリを作成し、git commitまでします。
1
2
3
$ rails foobar
$ cd foobar
$ git init && git add . && git commit -m "first commit"
heroku側にアプリケーションのdeploy先を作成します。

$ heroku create
createの後にアプリケーション名を入れないと、変な名前(今回はhollow-ice-37でした)を付けられてしまいます。ちょっと嫌なのでやり直し。
1
2
3
$ heroku destroy hollow-ice-37
$ heroku create foobar(実際はアプリの名前)
$ git push heroku master
git pushすると、自動的にdeployされます。deployされたアプリケーションをブラウザで確認したところ、

Missing the Rails 2.1.1 gem. Please `gem install -v=2.1.1 rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.
と。config/environment.rbの「RAILS_GEM_VERSION」をコメントアウトし、再度git pushしたところ、railsのデフォルトの画面が表示されました。 これだけだとよく分からないので、ローカルでscaffoldして、herokuにアップしてみます。
1
2
3
$ script/generate scaffold task name:string start:datetime end:datetime owner:integer
$ rake db:migrate
$ script/server
普通に動くことを確認し、git push。ただ、これだけだとdeploy先でdb:migrateが実行されないらしく、ブラウザで確認するとエラーが出てました。 deployされたアプリケーションのログを見る為には、heroku logsを実行します。
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
$ heroku logs
==> log/production.log <==
# Logfile created on Thu Jun 18 11:14:08 -0700 2009

Processing TasksController#index (for 59.156.119.10 at 2009-06-18 11:14:17) [GET]

ActiveRecord::StatementInvalid (PGError: ERROR:  relation "tasks" does not exist
: SELECT * FROM "tasks" ):
  app/controllers/tasks_controller.rb:5:in `index'
  /home/heroku_rack/lib/static_assets.rb:9:in `call'
  /home/heroku_rack/lib/last_access.rb:15:in `call'
  thin (1.0.1) lib/thin/connection.rb:80:in `pre_process'
  thin (1.0.1) lib/thin/connection.rb:78:in `catch'
  thin (1.0.1) lib/thin/connection.rb:78:in `pre_process'
  thin (1.0.1) lib/thin/connection.rb:57:in `process'
  thin (1.0.1) lib/thin/connection.rb:42:in `receive_data'
  eventmachine (0.12.6) lib/eventmachine.rb:240:in `run_machine'
  eventmachine (0.12.6) lib/eventmachine.rb:240:in `run'
  thin (1.0.1) lib/thin/backends/base.rb:57:in `start'
  thin (1.0.1) lib/thin/server.rb:150:in `start'
  thin (1.0.1) lib/thin/controllers/controller.rb:80:in `start'
  thin (1.0.1) lib/thin/runner.rb:173:in `send'
  thin (1.0.1) lib/thin/runner.rb:173:in `run_command'
  thin (1.0.1) lib/thin/runner.rb:139:in `run!'
  thin (1.0.1) bin/thin:6
  /usr/local/bin/thin:19:in `load'
  /usr/local/bin/thin:19
PGError…。どうやらPostgreSQLを使っているようです。 deploy先でrakeを実行したい場合はheroku rakeコマンドを使います。

$ heroku rake db:migrate
これでscaffoldした内容を確認できました。

rolerequirementプラグイン

roleを取り入れたいと思い、プラグインを探してみたところ、rolerequirementというプラグインが見つかったので、早速試してみました。


script/plugin install git://github.com/timcharper/role_requirement.git

インストール後、roleを表すRoleクラスを作成します。すでにrestful_authenticationを使用していた為、userというモデルができており、下記を実行後に「rake db:migrate」を行うことにより、roles、roles_usersという2つのテーブルが生成されます。


script/generate roles Role User 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mysql> desc roles;
+-------+--------------+------+-----+---------+----------------+
| Field | Type         | Null | Key | Default | Extra          |
+-------+--------------+------+-----+---------+----------------+
| id    | int(11)      | NO   | PRI | NULL    | auto_increment | 
| name  | varchar(255) | YES  |     | NULL    |                | 
+-------+--------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)

mysql> desc roles_users;
+---------+---------+------+-----+---------+-------+
| Field   | Type    | Null | Key | Default | Extra |
+---------+---------+------+-----+---------+-------+
| role_id | int(11) | YES  | MUL | NULL    |       | 
| user_id | int(11) | YES  | MUL | NULL    |       | 
+---------+---------+------+-----+---------+-------+
2 rows in set (0.00 sec)

あとはroleをuserに割り当てます。今回はroles、role_usersに対してSQLで直接レコードをinsertしました。 次に、各コントローラに対して必要なroleを設定していきます。

1
2
3
4
class RolesController < ApplicationController
  layout "master"
  before_filter :login_required
  require_role "admin"
あとは、roleが付与されたユーザ、付与されていないユーザを使用してテストを行います。権限がない場合は「You don’t have access here.」と表示されます。

移行先にcapistranoでdeploy

さくらで動かしていたアプリケーションを移行しているのですが、capifyされたrailsのアプリケーションについては、capistoranoで移行してみました。

まずは、

$ apt-get install subversion
でsvnを使用できるようにします。 次に、deploy.rbを移行先の環境に合わせて書き換えました。その後、

$ cap deploy:cold
としたところ、
1
2
3
..
 ** [wrap-trap.net :: out] svn: Can't make directory '/opt/shrimp/releases/20090604151717': Permission denied
..
と怒られてしまいました。 ディレクトリの作成で失敗しているようなので調査してみると、確かに権限の問題はあるようですが、そもそも/opt/shrimpというディレクトリ自体も無い…。もうちょっと調べてみると、いきなりdeploy:coldが間違っていたようです。最初はdeploy:setupを行う必要がありました。
1
2
3
4
5
6
7
8
9
10
$ cap deploy:setup
  * executing `deploy:setup'
  * executing "sudo -p 'sudo password: ' mkdir -p /opt/shrimp /opt/shrimp/releases /opt/shrimp/shared
 /opt/shrimp/shared/system /opt/shrimp/shared/log /opt/shrimp/shared/pids && sudo -p 'sudo password: '
 chmod g+w /opt/shrimp /opt/shrimp/releases /opt/shrimp/shared /opt/shrimp/shared/system /opt/shrimp/
shared/log /opt/shrimp/shared/pids"
    servers: ["wrap-trap.net"]
    [wrap-trap.net] executing command
 ** [out :: wrap-trap.net] 
    command finished
その後、再度deploy:coldを実行したものの、やはりmkdirでPermission deniedは変わらず。権限がないということで色々と調べてみたところ、deploy:setup後にchownを実行してディレクトリの所有者を変更すれば良いことがわかりました。deploy.rbに以下を追加して再実行し、ディレクトリの所有者を変更します。
1
2
3
4
5
6
7
set :runner, 'masayuki'
set :group, 'users'
set :use_sudo, true
...
after 'deploy:setup' do
  try_sudo "chown -Rf #{runner}:#{group} #{deploy_to}"
end
「try_sudo」というコマンドは知らなかった。というかcapistranoのコマンドにどんなものがあるか知らない。そういったドキュメントを見たことがないのですが、rdoc見るしかないのかな。

http://lee.hambley.name/capistrano-2.5.0/rdoc/

ちなみに、deploy先の環境に合わせて、after_symlinkでファイルを上書いています。これが普通のやり方なのかわかりませんが。
1
2
3
4
5
6
7
8
task :after_symlink do
  %w{database.yml envrinment.rb}.each do |f|
    run "cp -f #{shared_path}/files/#{f} #{current_path}/config/#{f}"
  end
  %w{production.rb}.each do |f|
    run "cp -f #{shared_path}/files/#{f} #{current_path}/config/environments/#{f}"
  end 
end
passengerとの連携はまだできてないので、後日試してみたいと思います。