VoiceXMLとRuby on Rails 続編

3月にVoiceXMLとRuby on Railsという記事を書きましたが、Ruby on Rails 2.0.2 で作り直してみました。

# routes.rb に追加 -> http://localhost:3000/weather/state1.vxml
  map.connect ':controller/:action.:format'
# 001_create_region.rb
class CreateRegions < ActiveRecord::Migration
  def self.up
    create_table :regions do |t|
      t.string :name
      t.string :yomi
      t.string :weather
      t.timestamps
    end
  end
  def self.down
    drop_table :regions
  end
end

# 002_add_test_data.rb
class AddTestData < ActiveRecord::Migration
  def self.up
    Region.delete_all
    Region.create :name => '東京', :yomi => 'とうきょう', :weather => '晴れ'
    Region.create :name => '横浜', :yomi => 'よこはま',   :weather => '曇り'
    Region.create :name => '大阪', :yomi => 'おおさか',   :weather => ''
    Region.create :name => '京都', :yomi => 'きょうと',   :weather => '晴れ時々曇り'
    Region.create :name => '広島', :yomi => 'ひろしま',   :weather => '曇りのち雨'
  end
  def self.down
    Region.delete_all
  end
end

class Region < ActiveRecord::Base
end
class WeatherController < ApplicationController
  def state1
     # counter をセッションで記憶する 
     if !session[:counter]
       session[:counter] = 1
     else
       session[:counter] += 1
     end
     @regions = Region.find(:all)
   end
   
   def state2
     if params[:region]
       @region = params[:region]
       @weather = Region.find_by_name(@region).weather
     end
   end
end
[state1.vxml.erb]
<?xml version="1.0" encoding="UTF-8"?>
<vxml version="2.0" xml:lang="ja">
  <form id='ask'>
    <field name='region'>
      <prompt timeout='20s'>
        <%= session[:counter] %>番です。
        天気を聞きたい地域を、
        <% @regions.each do |item| -%>
          <%=h item.name %><% end -%>
        から選んでください。
      </prompt>
      <grammar version='1.0' root='#region_rule'>
        <rule id='region_rule'>
        <one-of>
          <% @regions.each do |item| %>
          <item> <token sym="<%=h item.yomi %>" slot='region'> <%=h item.name %> </token> </item>
          <% end %>
          <item> <token sym='まいくてすと'>マイクテスト</token> </item>
        </one-of>
        </rule>
      </grammar>
    </field>
    <block>
      <submit next='<%= url_for(:action=>"state2") %>.vxml'/>
    </block>
  </form>
</vxml>

[state2.vxml.erb]
<?xml version="1.0" encoding="UTF-8"?>
<vxml version="2.0" xml:lang="ja">
  <form id='answer'>
    <block id='answer'>
      <log>地域=<%=h @region %>:天気=<%=h @weather %></log>
      <prompt> <%=h @region %>の天気は<%=h @weather %>です。<break /> </prompt>
    </block>
    <block>
      <goto next='<%= url_for(:action=>"state1") %>.vxml'/>
    </block>
  </form>
</vxml>

以前は rxml で VoiceXML を生成してみたのですが、やはり vxml.erb の方がすっきりしました。
そして興味深いことに、同じコントローラーで html.erb も動かすことができそうです。

[state1.html.erb]
<html>
<body>
<p> <%= session[:counter] %>番です。 </p>
<% form_tag :action => 'state2' do %>
天気を聞きたい地域:
<select name="region">
<% @regions.each do |item| -%>
  <option value="<%=h item.name %>"><%=h item.name %></option>
<% end -%>
</select>
<%= submit_tag "実行" -%>
<% end -%>
</body>
</html>

[state2.html.erb]
<html>
<body>
<p> <%=h @region %>の天気は<%=h @weather %>です。</p>
<%= button_to '戻る', :action => 'state1' %>
</block>
</body>
</html>

これまでISTCのマルチモーダル対話記述ワーキンググループで提案してきたアーキテクチャ階層:

  • 西本 卓也: "マルチモーダル対話システムのためのアーキテクチャ階層化," FIT2006イベント企画「音声・マルチモーダル対話記述とその標準化」予稿集, Sep 2006. (PDF)

と対応させてみると、

  • 6層「データモデル」が ActiveRecord
  • 5層「タスク間制御」が Controller のアクション
  • 4層「タスク内制御」以下が View (html.erb または vxml.erb)

になっており、モダリティごとの View を統一的に記述する方法を見つけることが、4層と3層を分離して記述するためのステップになりそうです。ヘルパーメソッドやパーシャルなどの機能がその部品になるでしょう。また、コントローラーのモダリティ独立性という観点からも RESTful であることが重要だ思います。