Webフレームワークの新しい波 Waves探訪記

第6回Twの完成と拡張

今回は、これまで作ってきたフォロー/リムーブの仕組みなどを実際に組み込んで、WebアプリケーションとしてTwを完成させます。

発言一覧画面の改良

今まで「/words」で呼び出す発言一覧画面では全ユーザの全発言を表示していましたが、ここもTwitter風にするために自分の発言とフォローしている人の発言だけを表示するようにします。以下のようにtemplates/word/list.mabを修正しましょう。

自分とフォローしているユーザの発言のみを表示
@me = Tw::Models::User[:name=>request.env['REMOTE_USER']]
@users = Tw::Models::User.all
layout :default, :title=>'Tw' do
  table.tw do
    tr do
      td(:colspan=>2) do
        form :action=>'/words', :method=>'post' do
          textarea '', :name=>'word.text', :cols=>80, :class=>'words'; br
          input :type=>:hidden, :name=>'word.user_id', :value=>@me.id
          input :type=>:submit, :value=>'Update'
        end
      end
    end
    tr do
      td do
        table.contents do
          @words.each do |word|
            is_me = (word.user.name==@me.name)
            unless @me.following_users.map{|u|u.name}.include?(word.user.name) || is_me then
              next
            end
            view :word, :summary, :word=>word, :is_me=>is_me
          end
        end
      end
      td do
        table.friends do
          tr do
            th(:colspan=>2){"users"}
          end
          @users.each do |user|
            next if user.name == @me.name
            view :word, :show_user, :user=>user, :me=>@me
          end
        end
      end
    end
  end
end

自分を判別するために1行目で「@me = Tw::Models::User[:name=>request.env['REMOTE_USER']]」としています。BASIC認証がかかっているので、REMOTE_USER名を拾えば、ログイン名が分かります。全ユーザは「@users = Tw::Models::User.all」で取得します。⁠@words.each do |word|」以下のブロックが発言の処理部分です。個々の発言のユーザ名と自分の名前を比較して、その発言が自分のものかどうかチェックします。

その発言は自分のものかどうか?
is_me = (word.user.name==@me.name)

自分のものなら変数is_meが真になります。

その発言は自分のものもしくはフォローしている人のものか?
unless @me.following_users.map{|u|u.name}.include?(word.user.name) || is_me

フォローしているユーザの一覧は「@me.following_users」で分かるので、発言の名前と比較します。これで「自分の発言とフォローしている人の発言だけ表示する」ことができます。表示のビュー呼び出しにはis_me変数も一緒に渡しましょう。

発言を表示するビューの呼び出し
view :word, :summary, :word=>word, :is_me=>is_me

発言表示のビューを改良する

templates/word/summary.mabを以下のようにします。ここで行う主なことは、自分の発言とそれ以外では異なるcssのクラスを指定することです。

発言表示用のビュー
tr(:class=>@is_me ? 'mywords':'words') do
  td.name(:style=>'width:10%;') do
    strong do
      @word.user.name
    end
  end
  td.word do
    span.word{@word.text}
    span.date{@word.created_on.strftime("%Y-%m-%d %H:%M") if @word.created_on}
  end
end

呼ばれたビューの中のインスタンス変数は、呼び出す側で指定したものであることに注意してください。ここでは「view :word, :summary, :word=>word, :is_me=>is_me」の後半の「:word=>word, :is_me=>is_me」がそれにあたります。呼び出す側の変数wordとis_meが、呼ばれる側では@word、@is_meと参照できるようになります。

フォローUIを作る

次に任意のユーザをフォローしたり、フォローからはずしたりするUIを作ります。 前述の発言一覧画面に、ユーザのフォロー/リムーブ(フォローをやめる)ができるUIを追加します。

ビュー

templates/word/list.mabのリストの後半に以下のような部分があります。 これがフォロー用のUIです。

フォロー用のUIの呼び出し
table.friends do
  tr do
    th(:colspan=>2){"users"}
  end
  @users.each do |user|
    next if user.name == @me.name
    view :word, :show_user, :user=>user, :me=>@me
  end
end

実際のビューはtemplates/word/show_user.mabが呼ばれています。ファイルを作成して、以下のようにします。

フォロー用のUI
tr do
  td @user.name
  td {
  if @me.following_users[:name=>@user.name] then
    a 'remove', :href=>"/user/#{@user.name}/remove"
  else
    a 'follow', :href=>"/user/#{@user.name}/follow"
  end
  }
end

ここでは全ユーザをリストアップして、すでにフォロー済みのユーザには"remove"、まだフォローしていないユーザには"follow"のアクションへのリンクを生成しています。ここのアクション呼び出しのURLはWaves風ではなく、Rails風になっていますね。というわけで、このURLをWavesが解釈できるようにする必要があります。

mapping.rbの修正

デフォルトで使えるREST型のルールを使ってもいいのですが、ここでは自前のルールを定義してみます。追加するアクションはfollowとremoveの2つで、ちょっとRails風に「/words/(ユーザ名)/follow」⁠/words/(ユーザ名)/remove」と呼べるようにしてみます。

まずconfigrations/mapping.rbを修正して、上記のURLを受け取るようにします。

Rails風のURLを受け付けるようにしたmapping.rb
module Tw
  
  module Configurations
    
    module Mapping
      extend Waves::Mapping
      # your custom rules go here
      path %r{^/user/(\w+)/(follow|remove)/?$}, :method=>:get do |name, action|
        use(:user)
        controller{self.send(action,name)}
        redirect('/words')
      end
      path %r{^/words/?$}, :method=>:post do
        use(:word)
        controller{create}
        redirect('/words')
      end
      include Waves::Mapping::PrettyUrls::RestRules
      include Waves::Mapping::PrettyUrls::GetRules
    end
    
  end
  
end

followとremoveの場合処理はほとんど同じなので、1つのpathのブロックに処理をまとめました。、まずトリガーとなるURLパターンを定義します。正規表現でURLに対してマッチングを行うことができるのですが、このとき後方参照用の部分が、そのままブロックの引数に渡されます。例えば「%r{^/user/(\w+)/follow/?$}」と指定すると、/user/(任意の文字列)/follow というパターンにマッチして、その文字列がブロックの引数 name で参照できます。ブロック内では「use(:user)」で、Userモデルを指定します。⁠controller{self.send(action,name)}」で、Userコントローラのアクション(followかremove)がnameをパラメータとして起動します。最後に一覧表示画面である「/words」にリダイレクトしています。

アクションの追加

最後に実際のアクションを書きます。 controller/user.rbを以下のようにします。Wavesによって自動生成したものに手を入れたものです。

ユーザのフォロー・リムーブのアクション
module Tw
  
  module Controllers
  
    class User
      
      include Waves::Controllers::Mixin
      
      def attributes; params[model_name.singular.intern]; end
      
      def all; model.all; end

      def find( name ); model[ :name => name ] or not_found; end
    
      def create; model.create( attributes ); end
    
      def update( name )
        instance = find( name )
        instance.set( attributes )
        instance.save_changes
      end
    
      def follow(name)
        @me = Tw::Models::User[:name=>request.env['REMOTE_USER']]
        user = find(name)
        @me.add_following_user(user) unless @me.following_users[:name=>user.name]
      end
      
      def remove(name)
        @me = Tw::Models::User[:name=>request.env['REMOTE_USER']]
        user = find(name)
        @me.remove_following_user(user) if @me.following_users[:name=>user.name]
      end
      
    end

  end

end

フォローに追加するメソッドはadd_following_user、リムーブするアクションはremove_following_userです。重複して追加や、存在しないユーザを削除しないように、直前にチェックをしています。これはモデル側でチェックすることも可能です。

cssの追加

第4回で追加したbase.cssに適当な記述をすれば、見栄えもよくできるでしょう。自分の発言と他人の発言は異なるcssクラスなので色分けも可能です。簡単にcssを設定したサンプルのスクリーンショットは、以下のようになります。

cssを設定した画面
cssを設定した画面

まとめと次回の予定

Twitter風簡単アプリTWはこれでほぼ完成です。次回はWavesの今後のロードマップなどを解説したいと思います。

おすすめ記事

記事・ニュース一覧