【参考文献】:AJAX HACKS 6章 Hacks45-52(p207~)
MVCとはデザインパターンの一種で、アプリケーションの構成を、Model、View、Controllerというコンポーネントに分けて設計する手法を言う。
編集中。。。あまりここに時間を割きたくないな。ぼそ
ここでは、InstantRails-1.7-win.zipを導入して使います。
1.開発環境
始める前に、念のため開発に使用するマシンのスペックを調べておきます。
Windows XP SP2 であれば、まあ問題ないでしょう。今回はWindows2000でやってみます。
ちなみに、マシンスペックの調べ方は、コマンドで「dxdiag」
C:\InstantRails\rails_apps>dxdiag
今回の開発環境のサンプル)
------------------
System Information
------------------
Time of this report: 10/16/2008, 09:52:20
Machine name: HIDE2000
Operating System: Windows 2000 Professional (5.0, Build 2195) Service Pack
4
Language: Japanese (Regional Setting: Japanese)
System Manufacturer: VMware, Inc.
System Model: VMware Virtual Platform
BIOS: Default System BIOS
Processor: Intel(R) Core(TM)2 Quad CPU Q6600 @ 2.40GHz, ~2.4GHz
Memory: 724MB RAM
Page File: 408MB used, 1049MB available
Windows Dir: C:\WINNT
DirectX Version: DirectX 9.0c (4.09.0000.0904)
DX Setup Parameters: Not found
DxDiag Version: 5.03.0001.0904 32bit Unicode
【補足:開発支援の為のIDE Tool】
開発支援の為のIDEToolとして、eclipseかAptanaかNetBeansなどを利用すると便利だと思います。
今回は「eclipse」に「Aptana」プラグイン、「RadRails」プラグインを仕込んで使います。(※)
※)手っ取り早く環境を構築するには?
これらが全部そろった「pleiades-all-in-one-ultimate_20081006.zip」がお勧めです。(=>ダウンロードはこちらから。Eclipse 3.4.1 Ganymedeのultimateがお勧めです。ちなみに3.3.1のほうにはultimateでも今回使いたいRadRailsのプラグインが入ってませんでした。。。)
※) eclipse 3.2.1 に RadRailsプラグインを追加する方法
AptanaプラグインとRailsプラグインのインストール ここを参考にしました。 それは調べていません。 |
2.Railsのヴァージョン
Railsの開発の場合、特にヴァージョンの組み合わせは重要です。
今回は、InstantRails-1.7-win.zipをインストールして準備した環境なので、「Rails 1.2.3」になります。このヴァージョンが現在最も情報量が豊富です。(=>ダウンロードはこちらから。)
※ rubyの最新の1.9などは1.8.6に比べてだいぶ変わったようですし、フレームワークであるrailsもヴァージョンが1.2.X系と2.X系ではscaffoldなどかなりやり方が異なります。パッケージ管理Toolであるgemなども実はgem自体のヴァージョンによりインポートできるモジュールのヴァージョンが違ったりと、手抜かりがあるとなかなか手こずりますので注意しましょう。
C:\InstantRails\rails_apps>ruby -v
ruby 1.8.6 (2007-03-13 patchlevel 0) [i386-mswin32]C:\InstantRails\rails_apps>gem env
RubyGems Environment:
- VERSION: 0.9.2 (0.9.2)
- INSTALLATION DIRECTORY: C:/InstantRails/ruby/lib/ruby/gems/1.8
- GEM PATH:
- C:/InstantRails/ruby/lib/ruby/gems/1.8
- REMOTE SOURCES:
-http://gems.rubyforge.orgC:\InstantRails\rails_apps>rake --version
rake, version 0.7.2C:\InstantRails\rails_apps>rails --version
Rails 1.2.3C:\InstantRails\rails_apps>mysql --version
mysql Ver 14.12 Distrib 5.0.27, for Win32 (ia32)
・ もうひとつのヴァージョンの調べ方
ひとつひとつのコマンドを打ち込んでヴァージョンを調べてもいいけれども、Railsの場合たくさんのパッケージやモジュールで構成されていて調べるのはいささか面倒です。そこで一発でこれらを調べるコマンドを紹介します。ただし、これはプロジェクト内部のaboutコマンドなので、少なくとも後述するプロジェクトを何かひとつ作成してから、実行してください。
// これはうまくいきません。
C:\InstantRails\rails_apps>ruby script/about
C:\InstantRails\rails_apps\hacks>ruby script/about
About your application's environment
Ruby version 1.8.6 (i386-mswin32)
RubyGems version 0.9.2
Rails version 1.2.3
Active Record version 1.15.3
Action Pack version 1.13.3
Action Web Service version 1.2.3
Action Mailer version 1.3.3
Active Support version 1.4.2
Application root C:/InstantRails/rails_apps/hacks
Environment development
Database adapter mysql
3.作成手順
RailsアプリはDB利用を前提としたWebアプリになるため、Railsの命名規則が重要になってきます。ここら辺はDRYやCoCの思想によるところですが、今回のAjax用のRailsアプリサンプルとして以下の情報をまず収集します。
それでは、今回のAjax用のRailsアプリサンプルの情報を定義していきましょう。
ここで、一応テーブルの定義も決めておきましょうか。
Kilowattsテーブル定義
NO |
カラム名 |
型 |
NotNull |
主キー |
1 |
id |
int |
true |
true |
2 |
kdate |
text |
|
|
3 |
kwatts |
int |
|
|
DDLファイルを作成するとしたら、以下のようになるかと思います。(※)
DDL:kilowatts.sql
DROP TABLE IF EXISTS kilowatts;
CREATE TABLE kilowatts (
id MEDIUMINT NOT NULL AUTO_INCREMENT,
kdate TEXT,
kwatts INT,
PRIMARY KEY(id)
);
※)ただし、Railsの作成方法では、このようなDB周りの準備もRailsでサポートされており、具体的には「マイグレーション」という手法を使います。ちなみに、この作成方法はRails 1.2.x 系列とRails 2.x 系列で作成手順が違います。これは追って説明します。
【参考】:ソースダウンロード
ちなみに、このページはUTF8になります。ブラウザで文字化けなどしたらエンコードを変更してみてください。AjaxHacksSamples.zipを解凍してサンプルコードを手に入れてください。
【事前準備】
InstantRailsでインストールした場合は、InstantRails.exe は起動しておきましょう。起動するとApacheとMySQlが立ち上がります。後ほどRailsからDBを作成したり、テーブルを作成しますが、そのためには今回使用するMySQLがサービスとして起動している必要があります。Webサーバは開発モードでは他の(Mongrelサーバ)を使うのでApacheは立ち上げる必要性はないですが、DBの確認作業で、phpMyAdminというToolを使う場合はApacheサーバを立ち上げておく必要があります。
また、DBにMySQLを使用するのであれば、最初に、my.iniファイルにてDBの文字コード指定をしておきます。以下のソースの青字部分を追記してください。
C:\InstantRails\mysql\my.ini
; ---------------------- IMPORTANT ---------------------------;
; ${path} is used to specify Instant Rails installation path. ;
;-------------------------------------------------------------;[mysqld]
datadir=${path}/mysql/data
basedir=${path}/mysql
bind-address=127.0.0.1
; Uncomment for use on USB key
; skip-innodb
default-character-set=utf8
skip-character-set-client-handshake
ここまで準備すれば、雛形部分の作成は15分もかからないでしょう。
【プロジェクトの作成】
// 1.0 railsアプリのルートフォルダに移動
// InstantRailsでインストールした場合には、その下に「rails_apps」があるはず。そこです。
C:\>cd C:\InstantRails\rails_apps// 2.0 railsコマンドの実行
// 通常は、Rails 1.2.3 の場合、DBは mysql になります。
// rails hacks
//
// デフォルトじゃないDBシステムを指定するには
// rails hacks -d mysql
// かもしくは、
// rails hacks --database=mysql
//
C:\InstantRails\rails_apps>rails hacks -d mysql
【DB作成】
DBの作成の仕方には幾通りかあります。各ベンダーにより簡単なやりかたがそれぞれあるでしょうが、RailsにはrakeコマンドでDB作成Toolを作成することもできます。それを使用すればマルチDBの作成が夢ではないと思いますが、それは次回のテーマに譲ることにして、ここでは、使用するDBをMySQLとした場合、InstantRails導入した際に利用できるphpMyAdminを使用して簡単に作成してしまいましょう。InstantRails.exeが立ち上がっている状態であれば、以下のURLにアクセスできるはずです。
今回作成するDBは三種類です。
これは先ほどのプロジェクトの作成の際に自動で命名規約されているDB名です。もちろんこれを変更することも可能ですが、それは以下のリソースに定義してあります。まず素直に自動生成されたソースを覗いてみましょう。
C:\InstantRails\rails_apps\hacks\config\database.yml
development:
adapter: mysql
database:hacks_development
username: root
password:
host: localhosttest:
adapter: mysql
database:hacks_test
username: root
password:
host: localhostproduction:
adapter: mysql
database:hacks_production
username: root
password:
host: localhost
今回はここでデフォルトに指定されているDB名をそのまま使用することにします。DB名を変えたければ、[database:]の値を変えればいいでしょう。
ちなみに、今回はMySQLなのでこのまま使用しますが、Oracleなどの場合は、DBを複数作成するよりも、スキーマを3つ用意したほうが現実的だと思われます。
【補足:文字コード問題】
my.iniに指定したように、このdatabase.ymlファイルでも文字コードを指定します。3つのDB定義の最後の要素にそれぞれ以下を追加します。(※MySQL使用時には必要ですがOracle使用する場合は必要がないようです。)
encoding: utf8
また、文字コード問題というと、DBの文字コードだけでなく、Ruby自体の文字コード指定をする必要があります。
今回のRailsアプリの文字コード指定は、「environment.rb」というファイルでの最初の行で設定します。
例)文字コードをUTF-8に指定するやりかた。
$KCODE = "UTF8"
もしくは、
$KCODE = 'u'
どちらも同じ意味です。最初の 1
バイトしか読み取っていないようです。しかも大文字小文字すら関係ないようです。でも自分としては可読性を考慮してUTF8と記述したいところです。
【参考サイト】
http://www.ruby-lang.org/ja/man/html/_C1C8A4DFB9FEA4DFCAD1BFF4.html
では、phpMyAdminのサイトにてこれらの3つのDBを作成してみましょう。
確認は、phpMyAdminでもできますが、コンソールからDBが作成されたことを確認してみます。
C:\InstantRails\rails_apps\hacks>mysql -u root
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 18 to server version: 5.0.27-community
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
mysql> show databases;
+----------------------------------------+
| Database |
+----------------------------------------+
| information_schema |
|hacks_development |
|hacks_production |
|hacks_test |
+----------------------------------------+
29 rows in set (0.01 sec)
mysql>
【eclipse へインポート】
この辺で、今後の作業の円滑を図り、eclipseへプロジェクトごとインポートしておきます。
プロジェクトを選択
選択してOK押下
作成されたことを確認
プロジェクトをマウントしたら、すかさずeclipseプロジェクト上の文字コードをUTF8に設定する。
【テーブルの作成】
事前準備してあるテーブル定義書に従い、Rails用のDDLファイルといえるマイグレーションファイルを作成します。
作成の手順は、Rails 1.2.3 と Rails 2.0.2 では微妙に異なります。
Rails 1.2.3では、
という手順になるが、Rails 2.0.2では
ただし、その際の「scaffold」のコマンドオプションの指定の仕方が変わりました。カラム名や型を同時に宣言しなければならなくなりました。
ここでは、Railsのヴァージョンは 1.2.3 なので、最初の手続きによるやり方で行う。
ちなみに、eclipse の RadRailsパースペクティブにおいては、generatorというviewが存在し、
そこでコマンドを実行が可能です。
ここでは、コマンドラインでの実行方法を紹介します。(Rails 1.2.3の場合)
C:\InstantRails\rails_apps\hacks>ruby script/generate migration create_kilowatts
create db/migrate
create db/migrate/001_create_kilowatts.rb
C:\InstantRails\rails_apps\hacks>
ちなみに、Rails 2.0.2の場合は migrationではなく、ここでいきなりscaffold していいです。
C:\InstantRails\rails_apps\hacks>ruby script/generate scaffold kilowatt kdate:text kwatts:integer
では、eclipse に戻って、作業領域をリフレッシュ(F5)した後、編集してみましょう。
001_create_kilowatts.rb(Rails 1.2.3の場合)
class CreateKilowatts < ActiveRecord::Migration
def self.up
create_table(:kilowatts) do |table|
table.column :kdate , :text
table.column :kwatts , :integer
end
end
def self.down
drop_table :kilowatts
end
end
001_create_kilowatts.rb(Rails 2.0.2の場合)
class CreateKilowatts < ActiveRecord::Migration
def self.up
create_table(:kilowatts) do |table|table.text :kdate
table.integer :kwatts
end
end
def self.down
drop_table :kilowatts
end
end
(※注意)
Rails1.2.3とRails2.0.2の表記の違いに注意してください。また2.0.2の場合、自動生成される「table.timestamps」ですが、これはカラムの「create_at」と「update_at」に相当します。
このマイグレーションファイルはマルチDBに対応したRails独自の文法による記載になります。
非常に便利なものだが、まず各ベンダーのRDBの型との対応に注意しなければなりません。
手元には、データ型の対応表が必要になると思われる。
型 | 説明 | MySQL | SQL Server | Oracle |
id | プライマリキー | int(11) | int | NUMBER(38) |
:string | 文字列 | varchar(255) | varchar(255) | VARCHAR(255) |
:text | 長い文字列 | text | text | CLOB |
:integer | 整数 | int(11) | int | NUMBER(38) |
:float | 浮動少数 | float | real | NUMBER |
:decimal | 精度の高い小数 | decimal(10,0) | decimal(18,0) | NUMBER(38) |
:datetime | 日時 | datetime | datetime | DATE |
:timestamp | 日時(より細かい) | datetime | datetime | DATE |
:time | 時間 | time | datetime | DATE |
:date | 日付 | date | datetime | DATE |
:binary | バイナリデータ | blob | image | BLOB |
:boolean | Boolean型 | tinyint(1) | bit | NUMBER(1) |
create_at | マジックフィールド | datetime | datetime | DATE |
update_at | マジックフィールド | datetime | datetime | DATE |
次に「rake db:migrate」コマンドを実行する。(ちなみに eclipse の Rake view からも実行が可能だ。)
C:\InstantRails\rails_apps\hacks>rake db:migrate
== CreateKilowatts: migrating =================================================
-- create_table(:kilowatts)
-> 0.0780s
== CreateKilowatts: migrated (0.0780s) ========================================
この時、「テーブル: schema_info」も同時に作成されていることに着目しておこう。これはマイグレーションにおける履歴管理のためのテーブルで、Railsの管理の下、テーブル定義が仕様変更となっても、すぐに反映させたり、また逆に元に戻すことを可能にした仕組みだ。
この点を踏まえると、Railsを使うならば、マイグレーションなしに勝手にテーブル作成することは好ましくないといえるだろう。
【テーブル作成の確認】
C:\InstantRails\rails_apps\hacks>mysql -u root
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 30 to server version: 5.0.27-community
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
mysql>use hacks_development;
Database changed
mysql>show tables;
+-----------------------------+
| Tables_in_hacks_development |
+-----------------------------+
| kilowatts |
|schema_info |
+-----------------------------+
2 rows in set (0.00 sec)
mysql>
【足場の作成】
ここで足場となるMVCアーキテクチャに沿ったソースを自動生成します。そのためのコマンドとして、先ほど紹介した「scaffold」というコマンドがあります。
C:\InstantRails\rails_apps\hacks>ruby script/generate scaffold kilowatt
exists app/controllers/
exists app/helpers/
create app/views/kilowatts
exists app/views/layouts/
exists test/functional/
dependency model
exists app/models/
exists test/unit/
exists test/fixtures/
create app/models/kilowatt.rb
create test/unit/kilowatt_test.rb
create test/fixtures/kilowatts.yml
create app/views/kilowatts/_form.rhtml
create app/views/kilowatts/list.rhtml
create app/views/kilowatts/show.rhtml
create app/views/kilowatts/new.rhtml
create app/views/kilowatts/edit.rhtml
create app/controllers/kilowatts_controller.rb
create test/functional/kilowatts_controller_test.rb
create app/helpers/kilowatts_helper.rb
create app/views/layouts/kilowatts.rhtml
create public/stylesheets/scaffold.css
ここまでで雛形が完成したはずです。
【動作確認】
では、早速サーバを起動させてみましょう。WebサーバはRailsでは複数用意されていますが、開発モード時点ではApacheなどは使わないです。(かといって現在あがっているApacheを落とす必要はありません。)簡易サーバとして、Mongrelサーバというものが、ありますので、今回はそれを使用します。
サーバー起動のやり方は、コマンドラインとeclipseでの起動のさせ方とありますが、ポート番号まで比較的簡単に指定できるeclipse起動のやり方を紹介します。
まず、eclipseにて対象となるプロジェクト(今回の場合は「hacks」)を選択して、Runします。
ここでOKを押下すれば、対象のサーバインスタンスが立ち上がるはずなのですが、実際には別のインスタンスが起動してしまうような現象に遭遇しました。ヴァージョンは、3.2.1でも3.4.1でも起こります。なのでその場合は、一旦関係のないサーバインスタンスを停止し、再度Serversヴューにて起動対象のサーバインスタンスを選択して実行してみてください。
以上のように起動すれば、Webアプリが起動されているはずです。
以下のURLをブラウザで指定してみましょう。
(今回はIP指定しますが、普段の開発ではHOSTはlocalhostか127.0.0.1でかまわないでしょう。)
// 今回のURL
http://192.168.1.29:3008/kilowatts/
// 通常はデフォルト指定のままなので以下のURL
http://localhost:3008/kilowatts/
http://127.0.0.1:3008/kilowatts/
ホスト名の次に指定するURIの要素がテーブル名になっていることに着目してください。
また、デフォルトの入り口が、indexになるのですが、実際にはlist画面にリダイレクトされています。ここは、Railsの仕組みをもう少し追いかけていくことで判明します。
とりあえず、ここまでで Railsのアプリの土台部分がが作成できました。
おめでとうございます!!
Ajaxには多数のライブラリが存在しており、現在では生で「XMLHttpRequest」オブジェクトを操作することはあまり機会がなくなっているといえるんではないでしょうか。最も有名なライブラリは「Prototype」ですが、これはどうやらRuby On Railsがそのルーツだそうです。ということで Railsをやるならデフォルトでついてきてます。
<%= javascript_include_tag:defaults%>
この記述でHTMLソースにはどのように変換されているか調べてみると、
<script src="/javascripts/prototype.js?1224125580" type="text/javascript"></script>
<script src="/javascripts/effects.js?1224125580" type="text/javascript"></script>
<script src="/javascripts/dragdrop.js?1224125580" type="text/javascript"></script>
<script src="/javascripts/controls.js?1224125580" type="text/javascript"></script>
<script src="/javascripts/application.js?1224125580" type="text/javascript"></script>
でした。
自動生成された javascriptライブラリをこの一行でロードしています。一番お手軽な書式ですが、必要なものだけを指定したほうが効率がいいでしょう。その場合は、
<%= javascript_include_tag"prototype.js"%>
ところで、このロードの順番は重要です。前後を入れ替えないこと。
逆に言うと、自分で定義した javascript関数は一番最後にロードされる「application.js」に入れるべきです。
XMLHttpRequestの使い方を調べるためにサンプルを作成しましょう。
RailsではXMLHttpRequestを生で使わないとはどういうことでしょうか?
ひとつには、ライブラリ「prototype.js」を内部で利用しているため、そのラッパークラスを通じて操作できるためです。
しかし、Railsの場合は、さらにそれをラッピングしたメソッドが存在し、それを利用することができます。
まず、それを確かめるためのコントロールとヴューを作成したいと思います。
ここで、今回はDBを使わないので、あえてscaffoldはせずに、generateではコントローラだけを指定してモデルは作成しないことにします。必要なのはアクションとその画面のみ…ということで、このようなコマンドを実行してみます。
C(コントローラ):hacks
V(ヴュー):monitor
C:\InstantRails\rails_apps\hacks>ruby script/generate controller hacks
exists app/controllers/
exists app/helpers/
create app/views/hacks
exists test/functional/
create app/controllers/hacks_controller.rb
create test/functional/hacks_controller_test.rb
create app/helpers/hacks_helper.rb
コントローラですが、hacks_controller.rb はJavaでいえばサーブレットみたいなものです。URIにも反映されます。V(ヴュー):monitorはまだここでは作成してませんが、アクション名でもあり、コントローラに実装するメソッド名でもあり、Viewとしてのrhtmlファイル名であったりします。この命名規則がしっかりしたところが、Railsが優れた生産性を誇る仕組みでもあります。この恩恵は非常に効率的ですね。MVCに着眼すれば、これからの作業の太枠が決まります。
まずアクセス先のURLを決めてしまいます。
http://192.168.1.29:3008/hacks/monitor
にすることにして、
コントローラのスタブメソッド作成します。(file:///C:/InstantRails/rails_apps/hacks/app/controllers/hacks_controller.rb)
class HacksController < ApplicationController
def index
#
# URI省略時のインデックスページの処理。特定のアクションにたらい回します。
#
monitor
render :action => 'monitor'
end
def monitor
#
# ここに処理を書きます。アクセス時の業務処理です。
#
end
def tmp
#
# ここに処理を書きます。まだメソッド名も決めてませんが「送信ボタン」に対応するアクションです。
#
end
end
ヴューの作成
レイアウトをまず決めます。対応するコントローラに対して1本作成します。
file:///C:/InstantRails/rails_apps/hacks/app/views/layouts/hacks.rhtml
これは、外枠だけの入れ物なので、既存のレイアウトをそのまま複製してしまいます。タイトルくらいはそれらしく編集しておきましょう。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
<%= javascript_include_tag :defaults %>
<%= stylesheet_link_tag 'scaffold' %>
<title>Ajax呼び出し時の内部状態<%= controller.action_name %></title>
</head>
<body>
<p style="color: green"><%= flash[:notice] %></p>
<%= yield %>
</body>
</html>
yieldに入るアクションごとの部品ヴューは
file:///C:/InstantRails/rails_apps/hacks/app/views/hacks/monitor.rhtml
になるでしょう。
内容は、
<h1>Monitor</h1>
<% form_tag :action => 'tmp' do %>
<%= render :partial => 'form' %>
<%= submit_tag '送信' %>
<% end %>
な感じにしておきます。送信ボタン押下時のメソッド名をそろそろ決めておかないといけませんが、とりあえず、こうしておきます。
画面自体は出ることをサーバにて確認できます。
では、ここからいよいよメインディッシュにんります。RailsでAjaxを使うには、対応するいくつかのメソッドが用意されています。XMLHttpRequestでは「form_tag」は使用しないのです。その代わりに「form_remote_tag メソッド」を使用します。
form_remote_tag メソッド
XMLHttpRequestの機能を活用するためのメソッドです。
基本構造は、
<%=form_remote_tag(...param...) %>
<div></div>
<p>
<%= submit_tag '送信' %>
</p>
<%=end_form_tag%>
パラメータ部分は多少複雑です。ハッシュ形式の指定方法です。
それぞれのキー項目をみていきます。赤字は必須指定。
:update => "表示要素のid属性値"
なんだけれどもここは結構重要です。肝です。:completeや:success指定との違いがわかりづらいのです。ここに参考にできる記事がありますが、もっと調べておく必要性を感じます。
:url => { :action => リクエストを処理するアクションのシンボル }
:position => "挿入位置"
:success => "XMLHttpRequestのreadystateが成功時"
ちなみに、readystateがsuccess というのは厳密にはないはずなのだが、指定できるそうだ。completeとほとんど似た状態だが、わずかに:successのほうが早い状態になります。
:complete => "Element.toggle('id属性値')"
readystate
状態 | 意味 | |
0 | uninitialized | 初期化されていない |
1 | loading | 読み込み中 |
2 | loaded | 読み込み完了 |
3 | interactive | 操作可能 |
4 | complete | 準備完了 |
file:///C:/InstantRails/rails_apps/hacks/app/views/hacks/monitor.rhtml
<%= form_remote_tag (
:update => "complete",
:url => { :action => :zero_update },
:position => "top",
:success => "$('success').innerHTML='成功; ステータスコード='+request.status",
:loading => "$('loading').innerHTML='リクエスト送信中…; ステータスコード='+request.status",
:loaded => "$('loaded').innerHTML='リクエスト送信完了; ステータスコード='+request.status",
:interactive => "$('inter').innerHTML='レスポンス受信中; ステータスコード='+request.status",
:failure => "$('failure').innerHTML='エラー; ステータスコード='+request.status") %>
<h3>Ajax呼び出しの内部状態を表示します</h3>
<div id="loading" style="font-size: 1.2em"></div>
<div id="loaded" style="font-size: 1.2em"></div>
<div id="inter" style="font-size: 1.2em"></div>
<div id="success" style="font-size: 1.2em; color: green"></div>
<div id="failure" style="font-size: 1.2em; color: red"></div>
<div id="complete" style="font-size: 1.2em; color: green"></div>
<p>
<%=submit_tag'送信' %>
</p>
<%=end_form_tag %>
file:///C:/InstantRails/rails_apps/hacks/app/views/hacks/monitor.rhtml
defzero_update
#
# ここに処理を書きます。まだメソッド名も決めてませんが「送信ボタン」に対応するアクションです。
#
render :text => "Ajax hello!!!"
end
render :text => "Ajax hello!!!"
ここが遷移しない仕組みです。遷移先の画面指定などはしていないのです。この例ではサーバ側の固定文字列を返していますが、コントローラで料理ができるということは、「可変データを非同期に返せる」ということです。静的なサーバ上のデータを返したいだけであるならば、もっと別の手法があります。その手法は別のテーマで触れる予定です。
変換後のHTMLは
<form action="/hacks/zero_update" method="post"onsubmit="new Ajax.Updater('complete', '/hacks/zero_update', {asynchronous:true, evalScripts:true, insertion:Insertion.Top, onFailure:function(request){$('failure').innerHTML='エラー; ステータスコード='+request.status}, onInteractive:function(request){$('inter').innerHTML='レスポンス受信中; ステータスコード='+request.status}, onLoaded:function(request){$('loaded').innerHTML='リクエスト送信完了; ステータスコード='+request.status}, onLoading:function(request){$('loading').innerHTML='リクエスト送信中…; ステータスコード='+request.status}, onSuccess:function(request){$('success').innerHTML='成功; ステータスコード='+request.status}, parameters:Form.serialize(this)}); return false;">
<h3>Ajax呼び出しの内部状態を表示します</h3>
<div id="loading" style="font-size: 1.2em"></div>
<div id="loaded" style="font-size: 1.2em"></div>
<div id="inter" style="font-size: 1.2em"></div>
<div id="success" style="font-size: 1.2em; color: green"></div>
<div id="failure" style="font-size: 1.2em; color: red"></div>
<div id="complete" style="font-size: 1.2em; color: green"></div>
<p>
<input name="commit" type="submit" value="送信" />
</p>
</form>
内部では、prototype.jsのクラスが使用されているようです。
しかも変換されているのは、「Ajax.Request」ではなく「Ajax.Updater」のようです。ほとんど似た処理ですが、サーバ上のデータをページに非同期に挿入するのに使います。
非同期の持ち味を実感するには、クライアントサイドで「onSubmit」とか「onClick」とかでは、実感が薄いですね。
トリガーとなるイベントの種類や指定方法も整理しておくといいでしょう。
その前に、:successと:completeの違いについて整理しておきます。上記のソースの実行結果は、実は問題があります。
:successと:completeの違いについて
どちらもサーバからレスポンスが返される準備が整った状態ではありますが、ステータスでいうと、まず:successになり、次に:completeになります。これを同じ意味と捕らえるのはもったいないことです。実際に:updateで指定された要素に結果が挿入されるのは、:completeのタイミングですが、そもそも非同期処理のため、ページが遷移せず、つまりは既読のデータ(id=completeで指定された領域のデータ)は消えないのです。そのため、前回サーバより取得した値は消えずに残り、そこに追記という形で対象要素にロードされるため、意図した結果にならないでしょう。その場合に、:successのステータスにおいて対象要素の「過去の値」を消す処理を施すことができます。
<%= form_remote_tag (
:update => "complete",
:url => { :action => :zero_update },
:position => "top",
:success =>"$('complete').innerHTML='';$('success').innerHTML='成功; ステータスコード='+request.status",
:loading => "$('loading').innerHTML='リクエスト送信中…; ステータスコード='+request.status",
:loaded => "$('loaded').innerHTML='リクエスト送信完了; ステータスコード='+request.status",
:interactive => "$('inter').innerHTML='レスポンス受信中; ステータスコード='+request.status",
:failure => "$('failure').innerHTML='エラー; ステータスコード='+request.status") %>
<h3>Ajax呼び出しの内部状態を表示します</h3>
<div id="loading" style="font-size: 1.2em"></div>
<div id="loaded" style="font-size: 1.2em"></div>
<div id="inter" style="font-size: 1.2em"></div>
<div id="success" style="font-size: 1.2em; color: green"></div>
<div id="failure" style="font-size: 1.2em; color: red"></div>
<div id="complete" style="font-size: 1.2em; color: green"></div>
<p>
<%= submit_tag '送信' %>
</p>
<%= end_form_tag %>
a
a
a
a
a
定期的な実行を伴うAjaxの処理は、「PeriodicalExecuter」を使用しますが、Railsではこれをさらにラップして「periodically_call_remote」というメソッドを使用します。
ここでは、サンプルとして、クライアント側から非同期に、ブラウザを利用しているユーザからは意識させずに、サーバーにアクセスし、サーバ上にあるDBの最新情報を取得して、それを画面を遷移させることなく表示するサンプルを作成してみます。
対象の画面:index3
イベント:タイマー
定期実行時間:3秒
リクエスト先のコントローラ:greeting
リクエスト先のアクション:select_todos_by_mysql
レスポンス先のエレメントID:complete
とすると、
1、Viewは(greeting/index3.html.erb)
<hr/><%=periodically_call_remote(:update => "complete", :frequency =>3,
:url => {:action => :select_todos_by_mysql},
:position => "top",
:success => "$('complete').innerHTML = ''",
:loading => "",
:loaded => "",
:interactive => "",
:failure => "") %>
<div id="success" style="font-size: 1.0em; color: green">
</div>
<div id="complete" style="font-size: 1.0em; color: green"></div>
2、コントローラは(greeting_controller.rb)
def select_todos_by_mysql
if request.xml_http_request?()
@stmt = @con.createStatement
rs = @stmt.executeQuery("select * from todos")
str = ''
while rs.next
str += rs.getString(3)
str += "<br />"
end
puts str
render :text => str
endend
になります。(ここでDB接続と切断は他のアクションでも共通処理になるはずなので外だしすることにします。※後述します。)ここで、request.xml_http_request?()は非同期でリクエストされた時のみ処理するための記述です。
機能の内容をみてみましょう。
periodically_call_remoteメソッド
変換後のHTMLは
<script type="text/javascript">
//<![CDATA[
new PeriodicalExecuter(function() {new Ajax.Updater('complete', '/greeting/select_todos_by_mysql', {asynchronous:true, evalScripts:true, insertion:Insertion.Top, onFailure:function(request){}, onInteractive:function(request){}, onLoaded:function(request){}, onLoading:function(request){}, onSuccess:function(request){$('complete').innerHTML = ''}, parameters:'authenticity_token=' + encodeURIComponent('e4a2e3f8ab38073ed2a9d8acaced418f8042a651')})}, 3)
//]]>
</script>
<div id="success" style="font-size: 1.0em; color: green"></div>
<div id="complete" style="font-size: 1.0em; color: green"></div>
これだけの記述で、タイマーによる非同期処理が実現できます。
なお、DBアクセスの部分は、今回「ActiveRecordを使わないやりかた」として、通常の「DriverManagerによる接続」を実装してみました。しかし、これはJrubyOnRailsによるJDBC接続となるため、ここでのトピックとしてのテーマとずれますが、実現方法はいたって簡単です。使用するDBのJDBCドライバを使用するRubyのlibフォルダに格納し使います。
DB接続と切断をアクション共通処理としてくくりだしたいので、
こういう処理の流れが太枠になります。この構成をRailsで行うには、事前処理と事後処理ということで、
# DB接続部分は外だしし、メソッドの前に実行させる。
before_filter :db_connect, :except => [:index,:index2,:index3,:index4]# DB切断は外だしし、メソッドの前に実行させる。
after_filter :db_close, :except => [:index,:index2,:index3,:index4]
を定義し、
private
def db_connect
begin
java.lang.Class.forName('com.mysql.jdbc.Driver').newInstance
@con = java.sql.DriverManager.getConnection('jdbc:mysql://localhost/JRoR2MySQLTom01_development', 'root', '');# rescue => ex
# @message = 'error occureed !!!'
# ensure
# @con.close if @con
endend
def db_close
@con.close if @con
end
を実装します。この処理は「除外指定されていないメソッド」である「select_todos_by_mysql」の前後に差し込まれて実行されるため、DBの接続切断処理が自動的に行われる結果となります。
a