ITコンサルの日常

ITコンサル会社に勤務する普通のITエンジニアの日常です。

「RailsによるアジャイルWebアプリケーション開発」23章読了

Web2.0と題して、Prototype、Script.aculo.us、RJSテンプレートが取り上げられています。

Ajaxでdiv要素の更新

ページ中のpタグで囲ったところだけ、Ajaxで更新するというサンプルです。
ここでは、四つの方法で実現してみました。

  • ハイパーリンクをクリックすると更新される
  • フォームボタンをクリックすると更新される
  • divで括られたテキストをクリックすると更新される(イベントハンドラ)
  • 5秒置きに自動的に更新される(疑似プッシュ技術的な)

さっそく解説をば。


まずは、prototype.jsが必要になるので、JavaScriptライブラリの取り込みを行うため、レイアウトファイルを修正します。

app/views/layouts/products.html.erb

<head>
  <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
  <title>Products: <%= controller.action_name %></title>
  <%= stylesheet_link_tag 'scaffold' %>

  <!-- add -->
  <%= javascript_include_tag :defaults %>
</head>

headタグ内に追加しました。
:defaultsにすると、Prototype、Script.aculo.us、application.jsが取り込まれますが、個別に取り込むことも可能です。


次にコントローラ。
app/controllers/products_controller.rb

  def ajaxtest
  end
  
  def ajaxtest_partial
    @dateTime = Time.now
    render(:partial => 'ajaxtest')
  end

Ajaxテスト用ページを表示するアクションajaxtestと、Ajaxリクエストに対してレスポンスを返すajaxtest_partialのアクションを定義しました。


次にビュー。
まずは、Ajaxレスポンスを描画するビューを作ります。
app/views/products/_ajaxtest.html.erb

<%= @dateTime %>

コントローラで取得した日時を、ただ表示しているだけですね。


次に、Ajaxレスポンスを取り込む本体のビューです。
app/views/products/ajaxtest.html.erb

<h3>Ajaxのテスト</h3>

<!-- ajaxリクエストを行うハイパーリンク -->
<%= link_to_remote "日時を更新リンク",
                   :url => {:action => :ajaxtest_partial},
                   :update => 'datetime1' %>

<!-- ajaxリクエストを行うフォームボタン -->
<% form_remote_tag :url => {:action => :ajaxtest_partial},
                    :update => 'datetime1' do %>
  <%= submit_tag "日時を更新ボタン" %>
<% end %>

<!-- ajaxリクエストを行うHTML要素 -->
<div onclick = "<%= remote_function(:update => 'datetime1',
                                    :url => {:action => :ajaxtest_partial}) %>">
日時を更新テキスト(クリックしてね)
</div>

<!-- n秒置きに自動更新 -->
<%= periodically_call_remote :url => {:action => :ajaxtest_partial}, 
                             :update => 'datetime1',
                             :frequency => 5
 %>

<p id='datetime1'/>

ちと長いですが、冒頭で書いた4つの部分に分かれていることが分かれば、理解は容易かと思います。


でもって、
http://localhost:3000/products/ajaxtest/1
へアクセス。


すると、こんなページが表示されます。

どれかをクリック or 5秒間待つことで、ページ下部に現在の日時が表示されます。
簡単にできちゃいますね。

オートコンプリート

Google Suggest(http://www.google.com/webhp?hl=ja&complete=1)的な検索ボックスが実現できる機能らしいです。


とりあえずRails2.xではプラグインになったようなので、プラグインをインストールしてみる。

taka@taka-desktop:~/test$ ruby script/plugin install auto_complete
+ ./README
+ ./Rakefile
+ ./init.rb
+ ./lib/auto_complete.rb
+ ./lib/auto_complete_macros_helper.rb
+ ./test/auto_complete_test.rb


で、書籍の内容は役たたずになったようなので、このインストールされたREADMEを参考にコントローラとビューを作ってみる。


まずは、コントローラ。
app/controllers/products_controller.rb

auto_complete_for :post, :autocomplete_favorite_language

  def autocomplete_demo
  end


  def auto_complete_for_post_user_favorite_language
    puts(params[:post][:user_favorite_language])
    render(:text=>'<ul><li>Ruby</li><li>Java</li><li>Haskell</li><li>C#</li></ul>')
  end

テキストフィールドに入れられた内容は、params[:post][:user_favorite_language]で取得できますが、
ここでは簡略のため、固定のリストを返すようにしています。


次にビュー。
app/views/products/autocomplete_demo.html.erb

<p><label for="user_favorite_language">お気に入りの言語</label><br/>
<%= text_field_with_auto_complete :post, :user_favorite_language %>

text_field_with_auto_completeでオートコンプリート用テキストフィールドを生成しています。


で、
http://localhost:3000/products/autocomplete_demo/1
にアクセス。
と、NoMethodErrorで怒られる。


しばらく悩んだのち、
http://koress.jp/2007/08/ruby_on_rails.html
のサイトで、WEBrickを再起動する必要があることを知りました。。
で、再起動でとりあえずNoMethodErrorは解決。


次に、フォームが表示されたものの、
一向にオートコンプリートされない。
POSTだからダメなのかと思い、GETにしてみても同じくダメ。


試しにPOSTで直接アクセスしてみたらどうなるのかと思い、
こんなHTMLを書いてみることに。
public/test.html

<html>
<form method="POST" action="/products/auto_complete_for_post_user_favorite_langu
age">
<input type = "submit" value = "push">
</form>

で、http://localhost:3000/test.htmlにアクセスし、pushボタンをクリック。


すると、

 ActionController::InvalidAuthenticityToken in ProductsController#auto_complete_for_post_user_favorite_language

が出てました。
これは、
http://brass.to/blog/rails_2_0_auto_complete.html
でも書かれている問題で、このページからリンクされている、
http://d.hatena.ne.jp/jakei/20080305/1204764230
のサイトのパッチを当てることで回避できました。(念のためWEBrickは再起動)


ちなみにですが、WEBrickのコンソールログにステータスコード422ってのが表示されてて、
なんだろうと思ってHTTP/1.1の仕様(RFC2616; http://www.ietf.org/rfc/rfc2616.txt)を見たのですが載ってませんでした。
これはWebDAVのために拡張されたもので、
RFC4918; http://www.ietf.org/rfc/rfc4918.txt
の11.2章に載ってますね。

「RailsによるアジャイルWebアプリケーション開発」24章読了

Action Mailerのお話し。

とりあえずメールを送ってみる。

サンプルとして、SMTPによるメール送信を試みる。
サーバはこういう時に僕がいつも使っているJamesを利用。(Jamesの設定は割愛します。どこかに載ってますので。)


まずは、SMTPの設定。
config/environments/developement.rbを編集する。

# Don't care if the mailer can't send
#config.action_mailer.raise_delivery_errors = false
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
  :address => "localhost",
  :port    => 25,
  :domain  => "localhost",
  :authentication => :plain
}
config.action_mailer.perform_deliveries = true
config.action_mailer.raise_delivery_errors = true
config.action_mailer.default_charset = "utf-8"

元々の
config.action_mailer.raise_delivery_errors = false
は、エラーが起きてもシカトする設定なので、これをコメントアウトして、各行追記する。
4行目のところ、server_settingsではなく、smtp_settingsにしないと、mailerをgenerateするときに、

taka@taka-desktop:~/test$ ruby script/generate mailer OrderMailer confirm sent
/usr/lib/ruby/gems/1.8/gems/actionmailer-2.1.0/lib/action_mailer/base.rb:385:in `method_missing': undefined method `server_settings=' for ActionMailer::Base:Class (NoMethodError)
        from /usr/lib/ruby/gems/1.8/gems/rails-2.1.0/lib/initializer.rb:455:in `send'
        from /usr/lib/ruby/gems/1.8/gems/rails-2.1.0/lib/initializer.rb:455:in `initialize_framework_settings'
        from /usr/lib/ruby/gems/1.8/gems/rails-2.1.0/lib/initializer.rb:454:in `each'
        from /usr/lib/ruby/gems/1.8/gems/rails-2.1.0/lib/initializer.rb:454:in `initialize_framework_settings'
        from /usr/lib/ruby/gems/1.8/gems/rails-2.1.0/lib/initializer.rb:451:in `each'
        from /usr/lib/ruby/gems/1.8/gems/rails-2.1.0/lib/initializer.rb:451:in `initialize_framework_settings'
        from /usr/lib/ruby/gems/1.8/gems/rails-2.1.0/lib/initializer.rb:133:in `process'
        from /usr/lib/ruby/gems/1.8/gems/rails-2.1.0/lib/initializer.rb:93:in `send'
        from /usr/lib/ruby/gems/1.8/gems/rails-2.1.0/lib/initializer.rb:93:in `run'
        from /home/taka/test/config/environment.rb:13
        from /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require'
        from /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:27:in `require'
        from /usr/lib/ruby/gems/1.8/gems/rails-2.1.0/lib/commands/generate.rb:1
        from /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require'
        from /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:27:in `require'
        from script/generate:3

のようなエラーが出てハマるので注意。


うまく実行されるとこんな感じ。

taka@taka-desktop:~/test$ ruby script/generate mailer OrderMailer confirm sent
      exists  app/models/
      create  app/views/order_mailer
      exists  test/unit/
      create  test/fixtures/order_mailer
      create  app/models/order_mailer.rb
      create  test/unit/order_mailer_test.rb
      create  app/views/order_mailer/confirm.erb
      create  test/fixtures/order_mailer/confirm
      create  app/views/order_mailer/sent.erb
      create  test/fixtures/order_mailer/sent


とりあえず送信できるかどうか試したいので、宛先とFromを設定する。
app/models/order_mailer.rb

  def confirm(sent_at = Time.now)
    subject    'OrderMailer#confirm'
    recipients 'taka@localhost'
    from       'taka@localhost'
    sent_on    sent_at

    body       :greeting => 'Hi,'
  end

recipientsとfromをtaka@localhostに設定しました。


メールを送信するアクションをコントローラに定義する。
app/controllers/products_controller.rb

  def send_mail
    OrderMailer.deliver_confirm
    
    redirect_to :action => :index
  end

メールを送信して、インデックスページにリダイレクトするだけのシンプルなアクションです。
ちなみに、create_confirmだと、作成したメールの内容が返されるだけで、メールは送信されません。(10分くらいハマった。。)


で、
http://localhost:3000/products/send_mail/1
にアクセス。


メーラ(Evolution)で確認したところ、
無事メールが送信されました。

ちなみに、日本語が化けるという情報もあるようですが、
私が確認した限りでは問題ありませんでした。