cd controllers
cp -r 00_hello address_book
cd address_book
rm README
コントローラ名を、変更します。併せてrequireを以下の通り修正してください。
require "alone"
class AddressBookController < AlController
===== モデルの設計と実装 =====
モデルは、名前をAddressBook 、操作としてCRUDの機能を持つよう設計します。ゼロから実装してもいいのですが、Aloneには
[[alpersist:start|AlPersist データ永続化モジュール]]
というデータの永続化機構があり、CRUDを備えているのでそれを継承することにします。
require "al_persist_file"
class AddressBook < AlPersistFile
DB_FILE = "#{AL_TEMPDIR}/address_book.dat"
def initialize()
super( DB_FILE )
end
end
簡単のため、RDBMSではなく、単純なファイルを保存先にするAlPersistFileを使います。
コンストラクタでファイル名を指定し、データの保存先を明らかにします。モデルにCRUDの機能が必要なだけなら、コードはこれだけです。
残りの create(), read(), update(), delete() ほかは、AlPersistクラスから継承されます。
==== モデルのテスト ====
たとえば、createを試してみましょう。
require "alone"
require_relative "./address_book"
class AddressBookController < AlController
def action_test_create()
address_book = AddressBook.new
p address_book.create({:name=>"木田太良", :address=>"兵庫県宝塚市"})
end
このアクションを実行し、画面にtrueと表示されれば成功です。
http://localhost:10080/index.rb?ctrl=address_book&action=test_create
ファイル、/tmp/address_book.dat ができ、データをエンコードした行が保存されています。
===== フォームの実装 =====
入力フォームを表示、及び入力された値を受け取るために、フォームオブジェクトを作ります。
今回は、名前と住所の2つのテキスト入力が必要です。名前は必須入力としておきます。
加えて以下の2アイテムを追加します。
* フォームの自動生成を使いたいので、決定ボタンを追加。
* データを特定するためのIDが必要となるので、それを持ち回るためのIDを非表示で追加。
require "alone"
require_relative "./address_book"
class AddressBookController < AlController
# コンストラクタ
def initialize()
@form = AlForm.new(
AlText.new("name", :label=>"名前", :required=>true),
AlText.new("address", :label=>"住所"),
AlSubmit.new("決定"),
AlHidden.new("id"),
)
end
==== フォームのテスト ====
<%= header_section %>
<%= body_section %>
<%= @form.make_tiny_form() %>
<%= footer_section %>
これで入力フォームが表示されます。
{{:prog_cgi:mvc_form.png?nolink|}}
===== リスト(一覧)表示 =====
このアドレス帳の最初の表示はリスト(一覧)表示ですので、それを実装しましょう。
コントローラでは、モデルに全件を得るよう指示します。得たデータは@alldataへ入れます。
# デフォルトアクション(リスト表示)
def action_index()
address_book = AddressBook.new
@alldata = address_book.all()
AlTemplate.run("index.rhtml")
end
テンプレートでは、@alldataに入っている全件データをeachで1件ずつ取り出し、テーブルを使って表示します。
<%= header_section %>
<%= body_section %>
名前
住所
<% @alldata.each do |item| %>
<%=h item[:name] %>
<%=h item[:address] %>
<% end %>
<%= footer_section %>
==== 実行結果 ====
この例では、モデルのcreateテストで使ったデータが1件のみ表示されています。
{{:prog_cgi:mvc_list1.png?nolink|}}
==== 他のアクションへのリンクを追加 ====
新規(create)、編集(update)、削除(delete) の選択肢を追加します。\\
URLへは、データを特定するためのIDを追加するとともに、各操作とも以下の通り2段階の動作となるので、それぞれアクション名をわかるようにつけます。\\
**新規(create)**\\
新規フォーム表示 action_create_form ⇒ 確定 action_create_exec
**更新(update)**\\
更新フォーム表示 action_update_form ⇒ 確定 action_update_exec
**削除(delete)**\\
確認画面表示 action_delete_confirm ⇒ 確定 action_delete_exec
<%= header_section %>
<%= body_section %>
名前
住所
(操作)
<% @alldata.each do |item| %>
<%=h item[:name] %>
<%=h item[:address] %>
">更新
">削除
<% end %>
">新規
<%= footer_section %>
{{:prog_cgi:mvc_list2.png?nolink|}}
===== 新規登録 =====
新規登録アクションを定義します。\\
新規フォーム表示 action_create_form ⇒ 確定 action_create_exec の2段階になります。
==== フォーム表示 ====
空のフォームを表示すれば良いので、簡単です。
def action_create_form()
@form.action = make_uri(:action=>"create_exec")
AlTemplate.run("form.rhtml")
end
<%= header_section %>
<%= body_section %>
<%= @form.get_messages_by_html() %>
<%= @form.make_tiny_form() %>
<%= footer_section %>
{{:prog_cgi:mvc_form.png?nolink|}}
==== 登録実行 ====
def action_create_exec()
# 新規登録時は、IDは不要(自動採番)なので取り除く
@form.delete_widget( :id )
# 入力フォームにエラーがあれば、フォーム再表示
if !@form.validate()
AlTemplate.run("form.rhtml")
return
end
# モデルオブジェクトを生成し、createする。
address_book = AddressBook.new
address_book.create( @form.values )
# リダイレクトを使って、デフォルト(リスト表示)へ戻る。
Alone.redirect_to( make_uri() )
end
バリデーション、モデルによる登録実行、結果表示という流れです。
今回 AlPersistFile を使うので、IDは自動採番となり、ブラウザから送られてきたデータは不要です。あらかじめフォームオブジェクトから取り除いておきます。
モデルの create メソッドにバリデーション後のデータ一式を渡して、登録を行います。エラーチェックはしていませんが、より厳密にするためには create メソッドの戻り値を確認すると良いでしょう。
結果表示は、独立して行わず、リスト表示に戻しています。実用的なアプリケーションでも、このようにリダイレクトによって2重POSTによる誤動作を防ぎます。
===== 更新 =====
更新アクションを定義します。\\
更新フォーム表示 action_update_form ⇒ 確定 action_update_exec の2段階になります。
==== フォーム表示 ====
def action_update_form()
# データ特定用のIDを得る
id = AlForm.get_parameter( AlInteger.new("id") )
if !id
return
end
# モデルオブジェクトを生成し、readする。
address_book = AddressBook.new
if !address_book.read({:id=>id})
return
end
# フォームに初期値として設定し表示する
@form.values = address_book.values
@form.action = make_uri(:action=>"update_exec")
AlTemplate.run("form.rhtml")
end
更新のフォーム表示は、新規登録の時と違い、既存データが入力された状態で表示しなければなりません。そのため、IDをキーにモデルを使ってデータをreadし、その内容をフォームにセットする動作が必要です。\\
IDはフォームを使って取り出すこともできますが、プライマリキー等の単純なデータ1つを取得したい場合には、この例の通りget_parameterメソッドを使うとより簡単になります。
テンプレートは、新規登録と同じです。
{{:prog_cgi:mvc_update.png?nolink|}}
==== 更新実行 ====
def action_update_exec()
# 入力フォームにエラーがあれば、フォーム再表示
if !@form.validate()
AlTemplate.run("form.rhtml")
return
end
# モデルオブジェクトを生成し、updateする。
address_book = AddressBook.new
address_book.update( @form.values )
# リダイレクトを使って、デフォルト(リスト表示)へ戻る。
Alone.redirect_to( make_uri() )
end
新規登録とほとんど同じで、バリデーション、モデルによる更新実行、結果表示という流れです。新規登録との違いは、IDを使うためにフォームオブジェクトから取り除く必要が無いことと、モデルのメソッドが createではなく、update であること位です。
===== 削除 =====
削除アクションを定義します。\\
確認画面表示 action_delete_confirm ⇒ 確定 action_delete_exec の2段階になります。
==== 確認画面表示 ====
def action_delete_confirm()
# データ特定用のIDを得る
id = AlForm.get_parameter( AlInteger.new("id") )
if !id
return
end
# モデルオブジェクトを生成し、readする。
address_book = AddressBook.new
if !address_book.read({:id=>id})
return
end
# 簡易画面の生成のため、フォームに入れてテンプレートに渡す
@form.values = address_book.values
AlTemplate.run("delete_confirm.rhtml")
end
削除確認画面の表示も一度データを表示したいので、更新と同じようにIDの値を使ってreadを行い、その内容をフォームの機能を使って表示します。
<%= header_section %>
<%= body_section %>
<%= @form.make_tiny_sheet() %>
削除してもよろしいですか?
いいえ
@form[:id]) %>">はい
<%= footer_section %>
{{:prog_cgi:mvc_delete.png?nolink|}}
==== 削除実行 ====
def action_delete_exec()
# データ特定用のIDを得る
id = AlForm.get_parameter( AlInteger.new("id") )
if !id
return
end
# モデルオブジェクトを生成し、deleteする
address_book = AddressBook.new
if !address_book.delete({:id=>id})
return
end
# リダイレクトを使って、デフォルト(リスト表示)へ戻る。
Alone.redirect_to( make_uri() )
end
削除の実行は、データを特定するIDのみが必要となるので、ここでもget_parameterメソッドでIDのみを取得します。IDを使って、モデルのdeleteメソッドによりデータを削除します。その後、リダイレクトによりリスト表示に戻ります。
====== 項目の追加 ======
名前と住所だけでは実用的ではないので、登録項目を増やしてみます。\\
例として、電話番号入力欄と、登録日付を追加します。
===== 電話番号の追加 =====
コントローラでは、フォームオブジェクトに項目を追加します。
def initialize()
@form = AlForm.new(
AlText.new("name", :label=>"名前", :required=>true),
AlText.new("address", :label=>"住所"),
AlText.new("tel", :label=>"電話"), # 追加
AlSubmit.new("決定"),
AlHidden.new("id"),
)
end
テンプレートでは、リスト表示テンプレートに項目を追加します。
<%= header_section %>
<%= body_section %>
名前
住所
電話
(操作)
<% @alldata.each do |item| %>
<%=h item[:name] %>
<%=h item[:address] %>
<%=h item[:tel] %>
">更新
">削除
<% end %>
">新規
<%= footer_section %>
==== 実行結果 ====
{{:prog_cgi:mvc_list3.png?nolink|}}
AlPersistFileを使う場合は、これだけで項目の増減が可能です。RDBを使う場合は、データベース側のカラム増減も併せて行う必要があります。
===== 登録日の追加 =====
登録日は、手入力するのは無駄なので自動入力としたいでしょう。
ですから、モデルに手を加えて新規登録(create)時に現在時刻を追加するようにし、表示は一覧表示のみに出るようにします。
def create( values = nil )
values[:created_at] = Time.now
super( values )
end
<%= header_section %>
<%= body_section %>
名前
住所
電話
登録日
(操作)
<% @alldata.each do |item| %>
<%=h item[:name] %>
<%=h item[:address] %>
<%=h item[:tel] %>
<%=h item[:created_at] %>
">更新
">削除
<% end %>
">新規
<%= footer_section %>
==== 実行結果 ====
{{:prog_cgi:mvc_list4.png?nolink|}}
新規登録を行なった時の日時が追加され、更新では変わらないという動作が確認できると思います。
====== プログラム全体 ======
プログラム全体を再掲します。
* モデル address_book.rb
* コントローラ main.rb
* テンプレート(ビュー) index.rhtml, form.rhtml, delete_confirm.rhtml
require "al_persist_file"
class AddressBook < AlPersistFile
DB_FILE = "#{AL_TEMPDIR}/address_book.dat"
def initialize()
super( DB_FILE )
end
def create( values = nil )
values[:created_at] = Time.now
super( values )
end
end
# coding: utf-8
require "alone"
require_relative "./address_book"
class AddressBookController < AlController
#
# コンストラクタ
#
def initialize()
@form = AlForm.new(
AlText.new("name", :label=>"名前", :required=>true),
AlText.new("address", :label=>"住所"),
AlText.new("tel", :label=>"電話"),
AlSubmit.new("決定"),
AlHidden.new("id"),
)
end
#
# デフォルトアクション(リスト表示)
#
def action_index()
address_book = AddressBook.new
@alldata = address_book.all()
AlTemplate.run("index.rhtml")
end
#
# 新規登録 フォーム表示
#
def action_create_form()
@form.action = make_uri(:action=>"create_exec")
AlTemplate.run("form.rhtml")
end
#
# 新規登録 実行
#
def action_create_exec()
# 新規登録時は、IDは不要(自動採番)なので取り除く
@form.delete_widget( :id )
# 入力フォームにエラーがあれば、フォーム再表示
if !@form.validate()
AlTemplate.run("form.rhtml")
return
end
# モデルオブジェクトを生成し、createする。
address_book = AddressBook.new
address_book.create( @form.values )
# リダイレクトを使って、デフォルト(リスト表示)へ戻る。
Alone.redirect_to( make_uri() )
end
#
# 更新 フォーム表示
#
def action_update_form()
# データ特定用のIDを得る
id = AlForm.get_parameter( AlInteger.new("id") )
if !id
return
end
# モデルオブジェクトを生成し、readする。
address_book = AddressBook.new
if !address_book.read({:id=>id})
return
end
# フォームに初期値として設定し表示する
@form.values = address_book.values
@form.action = make_uri(:action=>"update_exec")
AlTemplate.run("form.rhtml")
end
#
# 更新 実行
#
def action_update_exec()
# 入力フォームにエラーがあれば、フォーム再表示
if !@form.validate()
AlTemplate.run("form.rhtml")
return
end
# モデルオブジェクトを生成し、updateする。
address_book = AddressBook.new
address_book.update( @form.values )
# リダイレクトを使って、デフォルト(リスト表示)へ戻る。
Alone.redirect_to( make_uri() )
end
#
# 削除 確認画面
#
def action_delete_confirm()
# データ特定用のIDを得る
id = AlForm.get_parameter( AlInteger.new("id") )
if !id
return
end
# モデルオブジェクトを生成し、readする。
address_book = AddressBook.new
if !address_book.read({:id=>id})
return
end
# 簡易画面の生成のため、フォームに入れてテンプレートに渡す
@form.values = address_book.values
AlTemplate.run("delete_confirm.rhtml")
end
#
# 削除 実行
#
def action_delete_exec()
# データ特定用のIDを得る
id = AlForm.get_parameter( AlInteger.new("id") )
if !id
return
end
# モデルオブジェクトを生成し、deleteする
address_book = AddressBook.new
if !address_book.delete({:id=>id})
return
end
# リダイレクトを使って、デフォルト(リスト表示)へ戻る。
Alone.redirect_to( make_uri() )
end
end
<%= header_section %>
<%= body_section %>
名前
住所
電話
登録日
(操作)
<% @alldata.each do |item| %>
<%=h item[:name] %>
<%=h item[:address] %>
<%=h item[:tel] %>
<%=h item[:created_at] %>
">更新
">削除
<% end %>
">新規
<%= footer_section %>
<%= header_section %>
<%= body_section %>
<%= @form.get_messages_by_html() %>
<%= @form.make_tiny_form() %>
<%= footer_section %>
<%= header_section %>
<%= body_section %>
<%= @form.make_tiny_sheet() %>
削除してもよろしいですか?
いいえ
@form[:id]) %>">はい
<%= footer_section %>