1.before we do the UI, we need to populate database.
we will write a rake task to do this:
namespace :db do
desc "populate database"
task :populate => :environment do
Rake:Task["db:reset"].invoke
make_users
make_microposts
make_relationships
end
end
def make_users
admin = User.create!(:name => "fdf", :email => "fjds@jfd.com", :pass...)
admin.toggle!(:admin)
# add 99 sample users
end
def make _microposts
User.all(:limit => 6).each do |u|
50.times do
content = ......
u.microposts.create!(:content => content)
end
end
end
def make_relationships
users = User.all
user = users.first
following = users[1..50]
followers = users[3..40]
following.each { |followed| user.follow!(followed) }
followers.each { |follower| follower.follow!(user)}
end
2. next, we will do the partial showing the following and followers number of a user.
since the two numbers are links, the following number will link to the following user list page.
the followers number link will link to the follower list, so we first prepare the url for the two pages.
both the page belong to :users resources.
here is how we define the REST route:
resources :users do
member do
get :following, followers
end
end
this part of code will create ulr like this:
users/1/following
users/1/followers
and the following named routes:
following_user_path(1)
followers_user_path(1)
to make the url work, we still need to define
def following
render 'show_follow'
end
def followers
render 'show_follow'
end
in users controller.
and of course, the show_follow.html.erb should be in app/views/users
another kind of resoures routes is called collection:
resources :users do
collection do
get :tigers
end
end
this will create url:
users/tigers
tigers_users_path
3. after preparing routes, we will write test for the partial, since it will appear on user profile page and home page, so we will take this chance to refactor the test code in home page controller test, taking into account user signing in.
describe "when signed in" do
before :each do
@user = test_sign_in(Factory(:user))
other_user = Factory(:user, :email => Factory.next(:email))
other_user.follow! @user
end
it "should have the right following and followers count" do
get :home
response.should have_selector('a', :href => following_user_path(@user), :content => "0 following")
response.should have_selector('a', :href => followers_user_path(@user), :content => "1 follower")
end
end
4. next we will do the stats partial:
<% @user ||= current_user %>
<div class="stats">
<table summary="User stats">
<tr>
<td>
<a href="<%= following_user_path(@user) %>">
<span id="following" class="stats">
<%= @user.following.count %> following
</span>
</a>
</td>
<td>
<a href="<%= followers_user_path(@user) %>">
<span id="followers" class="stats">
<%= pluralize(@user.followers.count, "follower") %>
</span>
</a>
</td>
</tr>
</table>
</div>
pay special attention to the css id attr, it will be used for ajax, which access elements using their unique ids.
5. next, we can include the partial into the home page:
<% if signed_in? %>
.
.
.
<%= render 'shared/user_info' %>
<%= render 'shared/stats' %>
</td>
</tr>
</table>
<% else %>
.
.
.
<% end %>
6. next, we will prepare the follow/unfollow form button partial:
<% unless current_user?(@user) %>
<div id="follow_form">
<% if current_user.following?(@user) %>
<%= render 'unfollow' %>
<% else %>
<%= render 'follow' %>
<% end %>
</div>
<% end %>
7. next, we will prepare the routes for relationship resource:
resources :relationships, :only => [:create, :destroy]
8. ok now we can do the follow/unfollow partial.
<%= form_for current_user.relationships.build(:followed_id => @user.id) do |f| %>
<div><%= f.hidden_field :followed_id %></div>
<div class="actions"><%= submit_tag 'follow' %></div>
<% end %>
note, in the follow partial:
a. we new a relationship object, pass it as the param of form_for.
b. in this form, we only have a follow button.
c. but when user click this button, we need to make sure the form data include a param of
relationship[followed_id],
so we have to add a hidden field.
you may wondering, why I didn't input value into the hidden field, but it has data of user.id?
because of the build method, you already assign value to :followed_id, so the hidden field already has value of @user.id.
then is the unfollow partial:
<%= form_for current_user.relationships.find_by_followed_id(@user),
:html => { :method => :delete } do |f| %>
<div class="actions"><%= f.submit "Unfollow" %></div>
<% end %>
note, we don't need to hidden field here, since we only need the id to delete a object.
next we can put the stats partial and follow/unfollow partials into profile pages.
9. next, we will make following page and followers page.
first, we will make the link to following and followers page work, so first write test:
describe UsersController do
.
.
.
describe "follow pages" do
describe "when not signed in" do
it "should protect 'following'" do
get :following, :id => 1
response.should redirect_to(signin_path)
end
it "should protect 'followers'" do
get :followers, :id => 1
response.should redirect_to(signin_path)
end
end
describe "when signed in" do
before(:each) do
@user = test_sign_in(Factory(:user))
@other_user = Factory(:user, :email => Factory.next(:email))
@user.follow!(@other_user)
end
it "should show user following" do
get :following, :id => @user
response.should have_selector("a", :href => user_path(@other_user),
:content => @other_user.name)
end
it "should show user followers" do
get :followers, :id => @other_user
response.should have_selector("a", :href => user_path(@user),
:content => @user.name)
end
end
end
end
next, we will write code to make the test pass.
we need to add two new actions to users controller.
def following
@title = "Following"
@user = User.find(params[:id])
@users = @user.following.paginate(:page => params[:page])
render 'show_follow'
end
def followers
@title = "Followers"
@user = User.find(params[:id])
@users = @user.followers.paginate(:page => params[:page])
render 'show_follow'
end
.
next, we will do the show_follw view:
<table summary="Information about following/followers">
<tr>
<td class="main">
<h1><%= @title %></h1>
<% unless @users.empty? %>
<ul class="users">
<%= render @users %>
</ul>
<%= will_paginate @users %>
<% end %>
</td>
<td class="sidebar round">
<strong>Name</strong> <%= @user.name %><br />
<strong>URL</strong> <%= link_to user_path(@user), @user %><br />
<strong>Microposts</strong> <%= @user.microposts.count %>
<%= render 'shared/stats' %>
<% unless @users.empty? %>
<% @users.each do |user| %>
<%= link_to gravatar_for(user, :size => 30), user %>
<% end %>
<% end %>
</td>
</tr>
</table>
another thing to note, in the users controller, for the before filter of :authenticate,
we change from :only to :except.
before_filter :authenticate, :except => [:show, :new, :create]
10. next, we will try to make follow and unfollow button work.
since follow is creating a relationship, and unfollow is destroying a relationship, so we just need to add
create and destroying methods to relationships controller.
let's start from TDD!!!
require 'spec_helper'
describe RelationshipsController do
describe "access control" do
it "should require signin for create" do
post :create
response.should redirect_to(signin_path)
end
it "should require sign in for destroy" do
delete :destroy, :id => 1
response.should redirect_to(signin_path)
end
end
describe "POST 'create'" do
before :each do
@user = test_sign_in(Factory(:user))
@followed = Factory(:user, :email => Factory.next(:email))
end
it "should create a relationship" do
lambda do
post :create, :relationship => { :followed_id => @followed }
response.should be_redirect
end.should change(Relationship, :count).by(1)
end
end
describe "DELETE 'destroy'" do
before :each do
@user = test_sign_in(Factory(:user))
@followed = Factory(:user, :email => Factory.next(:email))
@user.follow!(@followed)
@relationship = @user.relationships.find_by_followed_id(@followed)
end
it "should destroy a relationship" do
lambda do
delete :destroy, :id => @relationship
response.should be_redirect
end.should change(Relationship, :count).by(-1)
end
end
end
now we can write create and destroy method to make this test pass:
class RelationshipsController < ApplicationController
before_filter :authenticate
def create
@user = User.find(params[:relationship][:followed_id])
current_user.follow!(@user)
redirect_to @user
end
def destroy
@user = Relationship.find(params[:id]).followed
current_user.unfollow!(@user)
redirect_to @user
end
end