Twitter風サービスの構築
さて、Twitterクライアントが完成しました。今度は自分たちの手でTwitter風サービス(Twitterクローン)を作成してみましょう。Twitter風サービスを作るには、以下の3つのテーブル(=モデル)が必要になります。
- 利用者(users)
- 利用者の名前、画像、認証用情報
- 友人、フォローしている人(friendships)
- 利用者がフォローしている人
- つぶやき(status)
- つぶやきの内容、つぶやいた日時
テーブルの関連は下のER図のようになります
これから、このTwitter風サービスのコードを説明していきます。今回は紙面の都合もありますのでView(テンプレート)の説明は省略しました。
まず、「tinytter」という名前でプロジェクトを作成し、テーブル情報を元にscaffoldを使ってひな形を作成します。
> jruby -S rails tinytter > cd tinytter > jruby -S scrip/generate scaffold status text:string user_id:integer > jruby -S scrip/generate scaffold user screen_name:string password:string profile_image_path:string > jruby -S scrip/generate scaffold friendship user_id:integer friend_id:integer
JRuby はC言語で作られたRDBのドライバー等を直接呼び出せないので、sqlite3はJDBC経由でアクセスしています。そこで config/database.yml ファイルの adapter: を sqlite3 から jdbcsqite3 に変更して下さい。
applicationコントローラー
最初に各コントローラーが継承しているapplicationコントローラーを簡単に説明します。
layout で全View共通のレイアウトファイル app/view/layout/scaffold.html.erbを指定しています。
before_filter でコントローラー内の各処理(アクション)の実行前に実行される処理を定義しています。ここではauthenticateメソッドでBasic認証を行っています。認証が失敗し、このメソッドが false を戻した場合はアクションは実行されません。またこのメソッド内で利用者のユーザーidをインスタンス変数 @user_id に設定しています。
class ApplicationController < ActionController::Base helper :all protect_from_forgery filter_parameter_logging :password layout "scaffold" before_filter :authenticate private def authenticate authenticate_or_request_with_http_basic do |user_name, password| @user_id = User.authenticate(user_name, password) end end end
利用者モデル
usersテーブルには、friendshipsテーブル、statusesテーブルに一対多の関連がありますので has_manyで宣言しています。ActiveRecord(Ruby on RailsのO/Rマッパー)ではテーブルの関連をモデルに定義する事で、簡単に関連したテーブルの情報を扱えるようになります。
authenticateメソッドはscreen_name、passwordを受け取り認証を行うクラスメソッドです。認証が成功した場合は利用者のidが戻ります。また、今回はパスワードを暗号化していません。アイコン画像はプログラムをシンプルにするため、ユーザー登録時に予め用意された画像から選択する方式にしました。 profile_image_selectorメソッドはアイコン画像のパス名一覧を戻します。
after_create{...} ですがuserモデルのデータをテーブルに新規作成する際に実行される処理を定義しています。ここではユーザー登録時に自分自身を自分の友人としてfriendshipsに登録しています。このように自分自身を友人に登録する事でつぶやき表示に自分のつぶやきが表示されるようにしています。
ActiveRecordでは、このようにデータの変更・作成の前後に独自の処理を追加できます。
class User < ActiveRecord::Base has_many :friendships has_many :statuses def self.authenticate(screen_name, password) user = self.find_by_screen_name(screen_name) (user && user.password == password) ? user.id : nil end IMAGES_DIR = "./public/images/" PROFILE_IMAGES_DIR = "profile_images" def self.profile_image_selector Dir.glob(IMAGES_DIR + PROFILE_IMAGES_DIR + "/*").map {|path| path.sub(IMAGES_DIR, '')} end after_create {|rec| Friendship.create_self(rec.id)} end
利用者コントローラー
ユーザー情報の登録、表示、変更、削除を行います。ほぼscaffoldのコードですが、XML関連出力を取り除きました。またパラメータでidを指定するのではなく、認証されたユーザーidに対して表示・変更・削除を行うようにしています。
skip_before_filter ... でユーザーの登録、トップページの処理では認証を外しています。
class UsersController < ApplicationController skip_before_filter :authenticate, :only => [:top, :new, :create] def index @user = User.find(@user_id) end def new @user = User.new end def edit @user = User.find(@user_id) end def create @user = User.new(params[:user]) if @user.save flash[:notice] = 'ユーザー登録しました' redirect_to(:action => :top) else render :action => "edit" end end def update @user = User.find(@user_id) if @user.update_attributes(params[:user]) flash[:notice] = 'ユーザー情報を変更しました' redirect_to(users_url) else render :action => "edit" end end def destroy @user = User.find(@user_id) @user.destroy redirect_to(users_url) end def top end end