id:NeoCat:20080525 から作り始めたTwitterもどき「monologue」に、他のユーザのFollow機能を実装していきます。
まずは、ActiveRecordを使ってユーザ間の関連を取得できるようにします。
テーブルの作成
NeoCat:20080525:1211652175 に書いたように、Followテーブルを使ってFollowしているユーザを管理します。
まずモデルと、それに対応するテーブルを作成します。
% script/generate model follow
そして、生成されたmigrationファイル( 00x_create_follows.rb )を編集し、create_tableメソッドのブロック内に以下を追記。
t.column :user_id, :integer t.column :followee_id, :integer
user_idはFollow元のユーザのidを示し、followee_idはフォローされている側のユーザのidを示します。
なお、followeesというのは「フォローされている人」という意味です。逆の「フォローしている人」はfollower。
employee と employer と同じ関係です。
編集後、以下のコマンドで実際にテーブル作成。
% rake db:migrate
UserモデルとFollowモデル間の関連を記述(フォロー元→フォロー先)
次にFollowモデルおよびUserモデルに互いの関連を書きます。ここでは、@user.followees で@userがフォロー中のユーザを配列で取得できるようにしてみましょう。
まずapp/models/user.rbに以下を追記。
has_many :follows, :dependent => :destroy has_many :followees, :through => :follows, :class_name => 'User'
さらに、app/models/follow.rbに以下を記述します。
belongs_to :followee, :foreign_key => :followee_id, :class_name => 'User' validates_associated :followee validates_uniqueness_of :followee_id, :scope => [:user_id]
最初の has_many :follows〜 で UserとFollowが1:nの関係にあること、またUserが削除されたらFollowも削除することを示しています。
これで、@user.follows でそのユーザに対応する Follows の配列が取得できるようになります。
さらに、次の has_many :followees, :through => :follows, :class_name => 'User' は、先ほどのfollowsの関連を通して、User同士がn:nの関係にあることを示しています。
関連がUser同士であるため、クラス名_id という規約に沿った user_id という名前をFollowsテーブルのフィールドにつけることが出来ません。そこで、クラス名を:class =>で明示しておきます。
これに対応するFollow側の宣言がbelongs_to :followee〜です。Userクラスのfollowee(s)に属しており、user_id(規約による) → followee_id という対応付けを行うことを示しています。
その後のvalidationについては、followeeが正しい関連であることのチェック、および同じuser_idの中ではfollowee_idが重複しないことのチェックです。( validates_uniqueness_ofについての説明 )
以上で、@user.followees でフォロー中のユーザが取得できるはずです。
コントローラを書く
% script/generate controller follow
でコントローラのひな形を生成し、以下を記述します。ログイン中のユーザに対して、http://localhost:3000/follow/new/<ユーザ名> でフォローを追加、newの代わりにdeleteでフォロー解除できるようにします。
class FollowController < ApplicationController def new redirect_to(:controller => :account, :action => :login) if !logged_in? user = User.find_by_login(params[:id]) render :text => "not found" and return unless user follow = Follow.new(:followee_id=> user.id) begin current_user.follows << follow flash[:notice] = "#{user.login} をfollowしました。" redirect_to(:controller => :user, :action => :index, :id => params[:id]) and return rescue flash[:notice] = follow.errors.full_messages flash[:notice] = "エラー : followに失敗しました。" if @flash[:notice].to_s == "" end redirect_to(:controller => :user, :action => :index) end def delete user = User.find_by_login(params[:id]) follow = params[:id] && Follow.find(:first, :conditions => ["followee_id = ?", user.id]) if follow && follow.user_id == current_user.id follow.destroy flash[:notice] = "#{params[:id]} のfollowを解除しました。" end redirect_to(:controller => :user, :action => :index, :id => params[:id]) and return rescue flash[:notice] = "エラー : follow解除に失敗しました。" redirect_to(:controller => :user, :action => :index) end end
試しにビューを書いてみる
ユーザ情報のビュー(app/views/user/index.rhtml)に以下を記述して、ちゃんとFollowしているユーザが取得できることをチェックしてみましょう。
<b>このユーザがfollow中のユーザ:</b><br> <% @user.followees.each do |u| %> <div class="follow"><%= link_to(h(u.login), :action => :index, :id => u.login ) %></div> <% end %>
上記で、ユーザのlogin名と、そのユーザの情報ページへのリンクを表示しています。