中ボスから話もあったので、Ruby on Railsの初アプリとして以下のような感じで飲み会管理システムを作ることにした。
飲み会管理システム = MeetingManager = mm を作ってみよう。
% rails mm
作られたconfig/database.ymlのままで問題はないので、データベース作成
% mysql -u root mysql> create database mm_development; mysql> create database mm_test; mysql> create database mm_production; mysql> quit
モデルを作成
% ruby script/generate model user % ruby script/generate model event % ruby script/generate model membership
db/migrateに、テーブルを作ったり消したりするファイルも出来たので、self.upの中を更新。 ユーザに見せる会員番号のためにaccountnumberを入れた。
class CreateUsers < ActiveRecord::Migration
def self.up
options = {
:options => "DEFAULT CHARSET=utf8"
}
create_table(:users, options) do |t|
t.column :nickname, :string
t.column :accountnumber, :string
t.column :mailaddress, :string
t.column :root, :boolean, :default => 1
end
end
宴会の案内はメールしたいので、maibodyでtextにした。
class CreateEvents < ActiveRecord::Migration
def self.up
options = {
:options => "DEFAULT CHARSET=utf8"
}
create_table(:events, options) do |t|
t.column :title, :string
t.column :mailbody, :text
end
end
宴会に招待されるとmembershipが作られ、参加するとjoinedがtrueになる。 user_id, event_idは外部キーにしたいが、よく分からないので、integerにする。
class CreateMemberships < ActiveRecord::Migration
def self.up
create_table(:memberships, options) do |t|
t.column :user_id, :integer
t.column :event_id, :integer
t.column :joined, :boolean, :default => 0
end
実行。
% rake db:migrate
確かめてみる。
% mysql -u root mm_development mysql> describe events; +----------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +----------+--------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | title | varchar(255) | YES | | NULL | | | mailbody | text | YES | | NULL | | +----------+--------------+------+-----+---------+----------------+
管理者を一人、一般ユーザを一人足しておこう。
% ruby script/generate migration AddDefaultUsers class AddDefaultUsers < ActiveRecord::Migration def self.up User.create(:nickname => 'root', :accountnumber => '0000', :mailaddress => 'root@example.com', :root => 1) User.create(:nickname => 'test', :accountnumber => '9999', :mailaddress => 'test@example.com', :root => 0) end % rake db:migrate
確認してみる。
% mysql -u root mm_development mysql> select * from users; +----+----------+---------------+------------------+------+ | id | nickname | accountnumber | mailaddress | root | +----+----------+---------------+------------------+------+ | 1 | root | 0000 | root@example.com | 1 | | 2 | test | 9999 | test@example.com | 0 | +----+----------+---------------+------------------+------+
scaffoldを作る。
% ruby script/generate scaffold User User % ruby script/generate scaffold Event Event % ruby script/generate scaffold Membership Membership
サーバを起動して
% ruby script/server
http://0.0.0.0:3000/userにアクセスすると
Listing users Nickname Accountnumber Mailaddress Root root 0000 root@example.com true Show Edit Destroy test 9999 test@example.com false Show Edit Destroy New user
が出た。すげー。
メインの処理用にMMコントローラを作る。
ruby script/generate controller MM login logout edit update
ザルな認証のために、フォームからnickname, accountnumberを入れさせる。
<h1>MM#login</h1>
<p>Find me in app/views/mm/login.rhtml</p>
<%= form_tag %>
ニックネーム
<%= text_field("user", "nickname") %>
会員番号
<%= text_field("user", "accountnumber") %>
<input type="submit" value="ログイン" />
<%= end_form_tag %>
controllerでは、try_to_loginが通ったら、"edit"に移る。
この辺は、RailsによるアジャイルWebアプリケーション開発のまま。
class MMController < ApplicationController
def login
if request.get?
session[:user_id] = nil
@user = User.new
else
@user = User.new(params[:user])
logged_in_user = @user.try_to_login
if logged_in_user
session[:user_id] = logged_in_user.id
redirect_to(:action => "edit", :id => logged_in_user.id)
else
flash[:notice] = "invalid"
end
end
end
models/user.rbでは、nickname, accountnumberが一致したら、try_to_loginで該当userを返す。
class User < ActiveRecord::Base
def self.login(nickname, accontnumber)
find(:first,
:conditions => ["nickname = ? and accountnumber = ?",
nickname, accontnumber])
end
def try_to_login
User.login(self.nickname, self.accountnumber)
end
end
http://0.0.0.0:3000/mm/login にアクセスしたら
LoadError in MmController#login app/controllers/mm_controller.rb to define MmController
と言われた。
mm_controller.rbでは、MmControllerではなく、MMControllerが定義されている。 2文字のコントローラを作ったのが失敗だったか。MmControllerに修正しよう。
ログインに成功すると
MM#edit Find me in app/views/mm/edit.rhtml
が出るようになった。
管理者として認証にパスしたら、という条件は後で考えるとして、飲み会管理画面からユーザを誘えるようにしよう。 とりあえず、editのときだけ誘えればいいや。
models/event.rbで、membershipと招待フラグ、参加フラグをhashで返せるようにしておく。
class Event < ActiveRecord::Base
def memberships
mhash = Hash::new
m = Membership.find(:all,
:conditions => ["event_id = ?", self.id])
m.each do |membership|
mhash[membership.user_id] = membership
end
return mhash
end
def proposes
phash = Hash::new
memberships.each do |id, membership|
phash[membership.user_id] = true
end
return phash
end
def joins
jhash = Hash::new
memberships.each do |id, membership|
jhash[membership.user_id] = membership.joined
end
return jhash
end
end
event_contoller.rbで変数に入れて
def edit @event = Event.find(params[:id]) @users = User.find(:all) @memberships = @event.memberships @proposes = @proposes.joins @joins = @event.joins end
event/edit.rhtmlで表示する。 form_tagが、end_form_tagでなくendで閉じられているようだけど、いいのかな。
<h1>Editing event</h1>
<% form_tag :action => 'update', :id => @event do %>
<%= render :partial => 'form' %>
<table>
<tr><td>ニックネーム</td><td>招待</td><td>参加</td></tr>
<% @users.each do |user| %>
<tr>
<td><%= user.nickname %></td>
<td><%= check_box("proposed", user.id, {:checked => @proposes[user.id]}, "yes", "no") %></td>
<td><%= check_box("joined", user.id, {:checked => @joins[user.id]}, "yes", "no") %></td>
</tr>
<% end %>
<%= submit_tag '更新' %>
<% end %>
<%= link_to 'Show', :action => 'show', :id => @event %> |
<%= link_to 'Back', :action => 'list' %>
event_contoller.rbではmembershipを更新。
def update
@event = Event.find(params[:id])
users = User.find(:all)
users.each do |user|
membership = Membership.find(:first,
:conditions => ["user_id = ? and event_id = ?",
user.id, @event.id])
if ((params[:proposed][user.id.to_s] == "yes") ||
(params[:joined][user.id.to_s] == "yes"))
if (! membership)
membership = Membership.new
membership.event_id = @event.id
membership.user_id = user.id
end
if (params[:joined][user.id.to_s] == "yes")
membership.joined = true
else
membership.joined = false
end
membership.save
else
if (membership)
membership.destroy
end
end
end
eventが削除されたら関連するmembershipも削除する。
def destroy
@event = Event.find(params[:id])
memberships = Membership.find(:all,
:conditions => ["event_id = ?",
@event.id])
memberships.each do |membership|
membership.destroy
end
@event.destroy
redirect_to :action => 'list'
end
同様に、userが削除されたら関連するmembershipも削除するよう、 user_controller.rbも修正しておこう。
def destroy
@user = User.find(params[:id])
memberships = Membership.find(:all,
:conditions => ["user_id = ?",
@user.id])
memberships.each do |membership|
membership.destroy
end
@user.destroy
redirect_to :action => 'list'
end
mm/edit.rhtmlに戻ろう。 models/user.rbで、membershipと参加フラグを返せるようにしておく。
class User < ActiveRecord::Base
def memberships
mhash = Hash::new
m = Membership.find(:all,
:conditions => ["user_id = ?", self.id])
m.each do |membership|
mhash[membership.event_id] = membership
end
return mhash
end
def joins
jhash = Hash::new
memberships.each do |id, membership|
jhash[membership.event_id] = membership.joined
end
return jhash
end
mm/edit.rhtmlで、ログインしているのが管理者ならば管理者専用メニューを表示する。
<% if (@user.root) %> <h2>管理者メニュー</h2> <%= link_to 'ユーザ管理', :controller => 'user' %> <%= link_to 'イベント管理', :controller => 'event' %> <% end %>
一般ユーザも、ニックネーム、メールアドレス、参加フラグは更新出来るように、 text_field, check_boxで表示する。
<% form_tag :action => 'update', :id => @user do %>
<h2>アカウント情報</h2>
<table>
<tr><td>会員番号</td><td><%= @user.accountnumber %></td></tr>
<tr><td>ニックネーム</td><td><%= text_field("user", "nickname") %></td></tr>
<tr><td>メールアドレス</td><td><%= text_field("user", "mailaddress") %></td></tr>
</table>
<h2>参加状況</h2>
<table>
<tr><td>タイトル</td><td>参加</td></tr>
<% @memberships.each do |id, membership| %>
<tr>
<td><%= Event.find(membership.event_id).title %></td>
<td><%= check_box("join", membership.event_id, {:checked => @joins[membership.event_id]}, "yes", "no") %></td>
</tr>
<% end %>
</table>
<%= submit_tag '更新' %>
<% end %>
<%= link_to 'ログアウト', :action => 'login' %>
最後はend_form_tagだとなぜかエラーするので、endにする。 落ちつきが悪いのでログアウトへのリンクを付ける。
mm_controller.rbで更新を反映させる。もうちょっと簡潔に書く方法がありそうな感じだけど。
def update
@user = User.find(params[:id])
@user.nickname = params[:user][:nickname]
@user.mailaddress = params[:user][:mailaddress]
@memberships = @user.memberships
@memberships.each do |id, membership|
if (params[:join][id.to_s] == "yes")
membership.joined = true
else
membership.joined = false
end
membership.save
end
@user.save
redirect_to(:action => "edit", :id => params[:id])
end
user/editでユーザ情報を表示すると、現在の状態に関らず常にrootでTrueが選択されている。 user/_form.rhtmlで
<p><label for="user_root">Root</label><br/> <select id="user_root" name="user[root]"><option value="false">False</option><option value="true" selected>True</option></select></p>
となっているからのようだ。
user_controller.rbで、現在の状態に従って、片方にselectedを代入する。
def edit
@user = User.find(params[:id])
if (@user.root)
@true_select = 'selected'
@false_select = ''
else
@true_select = ''
@false_select = 'selected'
end
end
変数に従って適当な方がselectedになるようにuser/_form.rhtmlを変更。
<option value="false" <%= @false_select %>>False</option><option value="true" <%= @true_select %>>True</option>
両方ともnilで呼ばれることもありそうだが、気にしない。