Commits

heliostatic  committed fe87734

An initial implementation of following and the from_users_followed_by method, pre-optimization

  • Participants
  • Parent commits 4a58f35

Comments (0)

Files changed (30)

 source 'https://rubygems.org'
 
-gem 'rails', '3.2.7'
+gem 'rails', '3.2.8'
 gem 'bootstrap-sass', '2.0.0'
 gem 'bcrypt-ruby', '3.0.1'
 gem 'faker', '1.0.1'
 end
 
 gem 'annotate', '2.5.0', group: :development
+gem 'bullet', group: :development
 
 # Gems used only for assets and not required
 # in production environments by default.

File Gemfile.lock

 GEM
   remote: https://rubygems.org/
   specs:
-    actionmailer (3.2.7)
-      actionpack (= 3.2.7)
+    actionmailer (3.2.8)
+      actionpack (= 3.2.8)
       mail (~> 2.4.4)
-    actionpack (3.2.7)
-      activemodel (= 3.2.7)
-      activesupport (= 3.2.7)
+    actionpack (3.2.8)
+      activemodel (= 3.2.8)
+      activesupport (= 3.2.8)
       builder (~> 3.0.0)
       erubis (~> 2.7.0)
       journey (~> 1.0.4)
       rack-cache (~> 1.2)
       rack-test (~> 0.6.1)
       sprockets (~> 2.1.3)
-    activemodel (3.2.7)
-      activesupport (= 3.2.7)
+    activemodel (3.2.8)
+      activesupport (= 3.2.8)
       builder (~> 3.0.0)
-    activerecord (3.2.7)
-      activemodel (= 3.2.7)
-      activesupport (= 3.2.7)
+    activerecord (3.2.8)
+      activemodel (= 3.2.8)
+      activesupport (= 3.2.8)
       arel (~> 3.0.2)
       tzinfo (~> 0.3.29)
-    activeresource (3.2.7)
-      activemodel (= 3.2.7)
-      activesupport (= 3.2.7)
-    activesupport (3.2.7)
+    activeresource (3.2.8)
+      activemodel (= 3.2.8)
+      activesupport (= 3.2.8)
+    activesupport (3.2.8)
       i18n (~> 0.6)
       multi_json (~> 1.0)
     addressable (2.3.2)
     bootstrap-will_paginate (0.0.6)
       will_paginate
     builder (3.0.0)
+    bullet (4.1.6)
+      uniform_notifier (~> 1.0.0)
     capybara (1.1.2)
       mime-types (>= 1.16)
       nokogiri (>= 1.3.3)
     jquery-rails (2.0.2)
       railties (>= 3.2.0, < 5.0)
       thor (~> 0.14)
-    json (1.7.4)
+    json (1.7.5)
     libwebsocket (0.1.5)
       addressable
     mail (2.4.4)
       rack
     rack-test (0.6.1)
       rack (>= 1.0)
-    rails (3.2.7)
-      actionmailer (= 3.2.7)
-      actionpack (= 3.2.7)
-      activerecord (= 3.2.7)
-      activeresource (= 3.2.7)
-      activesupport (= 3.2.7)
+    rails (3.2.8)
+      actionmailer (= 3.2.8)
+      actionpack (= 3.2.8)
+      activerecord (= 3.2.8)
+      activeresource (= 3.2.8)
+      activesupport (= 3.2.8)
       bundler (~> 1.0)
-      railties (= 3.2.7)
-    railties (3.2.7)
-      actionpack (= 3.2.7)
-      activesupport (= 3.2.7)
+      railties (= 3.2.8)
+    railties (3.2.8)
+      actionpack (= 3.2.8)
+      activesupport (= 3.2.8)
       rack-ssl (~> 1.3.2)
       rake (>= 0.8.7)
       rdoc (~> 3.4)
       tilt (~> 1.1, != 1.3.0)
     sqlite3 (1.3.5)
     terminal-notifier (1.3.0)
-    thor (0.15.4)
+    thor (0.16.0)
     tilt (1.3.3)
     treetop (1.4.10)
       polyglot
     uglifier (1.2.7)
       execjs (>= 0.3.0)
       multi_json (~> 1.3)
+    uniform_notifier (1.0.2)
     will_paginate (3.0.3)
     xpath (0.1.4)
       nokogiri (~> 1.3)
   bcrypt-ruby (= 3.0.1)
   bootstrap-sass (= 2.0.0)
   bootstrap-will_paginate (= 0.0.6)
+  bullet
   capybara (= 1.1.2)
   coffee-rails (~> 3.2.1)
   factory_girl_rails (= 1.4.0)
   jquery-rails
   newrelic_rpm
   pg (= 0.12.2)
-  rails (= 3.2.7)
+  rails (= 3.2.8)
   rb-fsevent (~> 0.9.1)
   rspec-rails (= 2.10.0)
   sass-rails (~> 3.2.3)

File app/assets/stylesheets/custom.css.scss

   margin-right: 10px;
 }
 
+.stats {
+  overflow: auto;
+  a {
+    float: left;
+    padding: 0 10px;
+    border-left: 1px solid $grayLighter;
+    color: gray;
+    &:first-child {
+      padding-left: 0;
+      border: 0;
+    }
+    &:hover {
+      text-decoration:none;
+      color: $blue;
+    }
+  }
+  strong {
+    display:block;
+  }
+}
+
+.user_avatars {
+  overflow: auto;
+  margin-top: 10px;
+  .gravatar {
+    margin: 1px 1px;
+  }
+}
+
 /* microposts */
 
 .microposts {

File app/controllers/relationships_controller.rb

+class RelationshipsController < ApplicationController
+  before_filter :signed_in_user
+
+  def create
+    @user = User.find(params[:relationship][:followed_id])
+    current_user.follow!(@user)
+    respond_to do |format|
+      format.html { redirect_to @user }
+      format.js
+    end
+  end
+
+  def destroy
+    @user = Relationship.find(params[:id]).followed
+    current_user.unfollow!(@user)
+    respond_to do |format|
+      format.html { redirect_to @user }
+      format.js
+    end
+  end
+end

File app/controllers/static_pages_controller.rb

   def home
     if signed_in?
       @micropost = current_user.microposts.build
-      @feed_items = current_user.feed.paginate(page: params[:page])
+      @feed_items = current_user.feed.includes(:user).paginate(page: params[:page])
     end
   end
 

File app/controllers/users_controller.rb

 class UsersController < ApplicationController
-  before_filter :signed_in_user,    only: [:index, :edit, :update, :destroy]
+  before_filter :signed_in_user,    only: [:index, :edit, :update, :destroy, :following, :followers]
   before_filter :correct_user,      only: [:edit, :update]
   before_filter :admin_user,        only: :destroy
   before_filter :already_signed_in, only: [:new, :create]
 
   def show
     @user = User.find(params[:id])
-    @microposts = @user.microposts.paginate(page: params[:page])
+    @microposts = @user.microposts.includes(:user).paginate(page: params[:page])
   end
 
   def new
     end
   end
 
+  def following
+    @title = "Following"
+    @user = User.find(params[:id])
+    @users = @user.followed_users.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
+
   private
 
     def correct_user

File app/models/micropost.rb

+# == Schema Information
+#
+# Table name: microposts
+#
+#  id         :integer          not null, primary key
+#  content    :string(255)
+#  user_id    :integer
+#  created_at :datetime         not null
+#  updated_at :datetime         not null
+#
+
 class Micropost < ActiveRecord::Base
   attr_accessible :content
   belongs_to :user
   validates :content, presence: true, length: { maximum: 140 }
   validates :user_id, presence: true
   default_scope order: 'microposts.created_at DESC'
+
+  def self.from_users_followed_by(user)
+    followed_user_ids = user.followed_user_ids
+    where("user_id IN (?) OR user_id = ?", followed_user_ids, user)
+  end
 end

File app/models/relationship.rb

+# == Schema Information
+#
+# Table name: relationships
+#
+#  id          :integer          not null, primary key
+#  follower_id :integer
+#  followed_id :integer
+#  created_at  :datetime         not null
+#  updated_at  :datetime         not null
+#
+
+class Relationship < ActiveRecord::Base
+  attr_accessible :followed_id
+
+  belongs_to :follower, class_name: "User"
+  belongs_to :followed, class_name: "User"
+
+  validates :follower_id, presence: true
+  validates :followed_id, presence: true
+end

File app/models/user.rb

 #  updated_at      :datetime         not null
 #  password_digest :string(255)
 #  remember_token  :string(255)
+#  admin           :boolean          default(FALSE)
 #
 
 class User < ActiveRecord::Base
   attr_accessible :email, :name, :password, :password_confirmation
   has_secure_password
   has_many :microposts, dependent: :destroy
+  has_many :relationships, foreign_key: "follower_id", dependent: :destroy
+  has_many :followed_users, through: :relationships, source: :followed
+  has_many :reverse_relationships, foreign_key: "followed_id", 
+                                   class_name: "Relationship",
+                                   dependent: :destroy
+  has_many :followers, through: :reverse_relationships, 
+                       source: :follower # could leave off source here
 
   before_save { self.email.downcase! }
   before_save :create_remember_token
   validates :password_confirmation, presence: true
 
   def feed
-    # Prelim implementation, pre following
-    Micropost.where("user_id = ?", id)
+    Micropost.from_users_followed_by(self)
+  end
+
+  def following?(other_user)
+    relationships.find_by_followed_id(other_user.id)
+  end
+
+  def follow!(other_user)
+    relationships.create!(followed_id: other_user.id)
+  end
+
+  def unfollow!(other_user)
+    relationships.find_by_followed_id(other_user.id).destroy
   end
 
   private

File app/views/relationships/create.js.erb

+$("#follow_form").html("<%= escape_javascript(render('users/unfollow')) %>")
+$("#followers").html('<%= @user.followers.count %>')

File app/views/relationships/destroy.js.erb

+$("#follow_form").html("<%= escape_javascript(render('users/unfollow')) %>")
+$("#followers").html('<%= @user.followers.count %>')

File app/views/shared/_stats.html.erb

+<% @user ||= current_user %>
+<div class="stats">
+  <a href="<%= following_user_path(@user) %>">
+    <strong id="following" class="stat">
+      <%= @user.followed_users.count %>
+    </strong>
+    following
+  </a>
+  <a href="<%= followers_user_path(@user) %>">
+    <strong id="followers" class="stat">
+      <%= @user.followers.count %>
+    </strong>
+    followers
+  </a>
+</div>

File app/views/static_pages/_signed_in_home.html.erb

       <%= render 'shared/user_info' %>
     </section>
     <section>
+      <%= render 'shared/stats' %>
+    </section>
+    <section>
       <%= render 'shared/micropost_form' %>
     </section>
   </aside>

File app/views/users/_follow.html.erb

+<%= form_for(current_user.relationships.build(followed_id: @user.id),
+             remote: true) do |f| %>
+  <div><%= f.hidden_field :followed_id %></div>
+  <%= f.submit "Follow", class: "btn btn-large btn-primary" %>
+<% end %>

File app/views/users/_follow_form.html.erb

+<% unless current_user?(@user) %>
+  <div id="follow_form">
+    <% if current_user.following?(@user) %>
+      <%= render 'unfollow' %>
+    <% else %>
+      <%= render 'follow' %>
+    <% end %>
+  </div>
+<% end %>

File app/views/users/_unfollow.html.erb

+<%= form_for(current_user.relationships.find_by_followed_id(@user),
+              html: { method: :delete},
+              remote: true) do |f| %>
+  <%= f.submit "Unfollow", class: "btn btn-large" %>
+<% end %>

File app/views/users/show.html.erb

         <%= @user.name %>
       </h1>
     </section>
+    <section>
+      <%= render 'shared/stats' %>
+    </section>
   </aside>
   <div class="span8">
+    <%= render 'follow_form' if signed_in? %>
     <% if @user.microposts.any? %>
       <h3>Microposts (<%= @user.microposts.count %>)</h3>
         <ol class="microposts">

File app/views/users/show_follow.html.erb

+<% provide(:title, @title) %>
+<div class="row">
+  <aside class="span4">
+    <section>
+      <%= gravatar_for @user %>
+      <h1><%= @user.name %></h1>
+      <span><%= link_to "view my profile", @user %></span>
+      <span><b>Microposts:</b> <%= @user.microposts.count %></span>
+    </section>
+    <section>
+      <%= render 'shared/stats' %>
+      <% if @users.any? %>
+        <div class="user_avatars">
+          <% @users.each do |user| %>
+            <%= link_to gravatar_for(user, size: 30), user %>
+          <% end %>
+        </div>
+      <% end %>
+    </section>
+  </aside>
+  <div class="span8">
+    <h3><%= @title %></h3>
+    <% if @users.any? %>
+      <ul class="users">
+        <%= render @users %>
+      </ul>
+      <%= will_paginate %>
+    <% end %>
+  </div>
+</div>

File config/initializers/bullet.rb

+if defined? Bullet
+  Bullet.enable = true
+  # Bullet.alert = true
+  Bullet.bullet_logger = true
+end

File config/routes.rb

 SampleApp::Application.routes.draw do
-  resources :users
-  resources :sessions,   only: [:new, :create, :destroy]
-  resources :microposts, only: [:create, :destroy]
+  resources :users do
+    member do
+      get :following, :followers
+    end
+  end
+
+  resources :sessions,      only: [:new, :create, :destroy]
+  resources :microposts,    only: [:create, :destroy]
+  resources :relationships, only: [:create, :destroy]
 
   match '/signup',  to: 'users#new'
   match '/signin',  to: 'sessions#new'

File db/migrate/20120817191311_create_relationships.rb

+class CreateRelationships < ActiveRecord::Migration
+  def change
+    create_table :relationships do |t|
+      t.integer :follower_id
+      t.integer :followed_id
+
+      t.timestamps
+    end
+    add_index :relationships, :follower_id
+    add_index :relationships, :followed_id
+    add_index :relationships, [:follower_id, :followed_id], unique: true
+  end
+end

File db/schema.rb

 #
 # It's strongly recommended to check this file into your version control system.
 
-ActiveRecord::Schema.define(:version => 20120813224336) do
+ActiveRecord::Schema.define(:version => 20120817191311) do
 
   create_table "microposts", :force => true do |t|
     t.string   "content"
 
   add_index "microposts", ["user_id", "created_at"], :name => "index_microposts_on_user_id_and_created_at"
 
+  create_table "relationships", :force => true do |t|
+    t.integer  "follower_id"
+    t.integer  "followed_id"
+    t.datetime "created_at",  :null => false
+    t.datetime "updated_at",  :null => false
+  end
+
+  add_index "relationships", ["followed_id"], :name => "index_relationships_on_followed_id"
+  add_index "relationships", ["follower_id", "followed_id"], :name => "index_relationships_on_follower_id_and_followed_id", :unique => true
+  add_index "relationships", ["follower_id"], :name => "index_relationships_on_follower_id"
+
   create_table "users", :force => true do |t|
     t.string   "name"
     t.string   "email"

File lib/tasks/sample_data.rake

 namespace :db do
   desc "Fill database with sample data"
   task populate: :environment do 
-    admin = User.create!( name: "Example User",
-                  email: "example@railstutorial.org",
-                  password: "foobar",
-                  password_confirmation: "foobar")
-    admin.toggle!(:admin)
-    99.times do |n|
-      name = Faker::Name.name
-      email = "example-#{n+1}@railstutorial.org"
-      password = "password"
-      User.create!( name: name,
-                    email: email,
-                    password: password,
-                    password_confirmation: password)
-    end
-    users = User.all(limit: 6)
-    50.times do
-      content = Faker::Lorem.sentence(5)
-      users.each { |user| user.microposts.create!(content: content) }
-    end
+    make_users
+    make_microposts
+    make_relationships
   end
+end
+
+def make_users
+  admin = User.create!( name: "Example User",
+                email: "example@railstutorial.org",
+                password: "foobar",
+                password_confirmation: "foobar")
+  admin.toggle!(:admin)
+
+  99.times do |n|
+    name = Faker::Name.name
+    email = "example-#{n+1}@railstutorial.org"
+    password = "password"
+    User.create!( name: name,
+                  email: email,
+                  password: password,
+                  password_confirmation: password)
+  end
+end
+
+def make_microposts
+  users = User.all(limit: 6)
+  50.times do
+    content = Faker::Lorem.sentence(5)
+    users.each { |user| user.microposts.create!(content: content) }
+  end
+end
+
+def make_relationships
+  users = User.all
+  user = users.first
+  followed_users = users[2..50]
+  followers       = users[3..40]
+  followed_users.each { |followed| user.follow!(followed) }
+  followers.each      { |follower| follower.follow!(user) }
 end

File spec/controllers/relationships_controller_spec.rb

+require "spec_helper"
+
+describe RelationshipsController do
+
+  let(:user) { FactoryGirl.create(:user)}
+  let(:other_user) { FactoryGirl.create(:user) }
+
+  before { sign_in user }
+
+  describe "creating a relationship with Ajax" do
+    it "should increment the Relationship count" do
+      expect do
+        xhr :post, :create, relationship: { followed_id: other_user.id }
+      end.should change(Relationship, :count).by(1)
+    end
+
+    it "should respond with success" do
+      xhr :post, :create, relationship: { followed_id: other_user.id }
+      response.should be_success
+    end
+  end
+
+  describe "destroying a relationship with Ajax" do
+
+    before { user.follow!(other_user) }
+    let(:relationship) { user.relationships.find_by_followed_id(other_user) }
+
+    it "should decrement the Relationship count" do
+      expect do
+        xhr :delete, :destroy, id: relationship.id
+      end.should change(Relationship, :count).by(-1)
+    end
+
+    it "should respond with success" do
+      xhr :delete, :destroy, id: relationship.id
+      response.should be_success
+    end
+  end
+end

File spec/models/micropost_spec.rb

+# == Schema Information
+#
+# Table name: microposts
+#
+#  id         :integer          not null, primary key
+#  content    :string(255)
+#  user_id    :integer
+#  created_at :datetime         not null
+#  updated_at :datetime         not null
+#
+
 require 'spec_helper'
 
 describe Micropost do
     before { @micropost.content = "a" * 141 }
     it { should_not be_valid }
   end
-end
+end

File spec/models/relationship_spec.rb

+# == Schema Information
+#
+# Table name: relationships
+#
+#  id          :integer          not null, primary key
+#  follower_id :integer
+#  followed_id :integer
+#  created_at  :datetime         not null
+#  updated_at  :datetime         not null
+#
+
+require 'spec_helper'
+
+describe Relationship do
+  
+  let(:follower) { FactoryGirl.create(:user) }
+  let(:followed) { FactoryGirl.create(:user) }
+  let(:relationship) { follower.relationships.build(followed_id: followed.id) }
+
+  subject { relationship }
+
+  it { should be_valid }
+
+  describe "accessible attributes" do
+    it "should not allow access to follower_id" do
+      expect do
+        Relationship.new(follower_id: follower.id)
+      end
+    end
+  end
+
+  describe "follower methods" do
+    it { should respond_to(:follower) }
+    it { should respond_to(:followed) }
+    its(:follower) { should == follower }
+    its(:followed) { should == followed }
+  end
+
+  describe "when followed id is not present" do
+    before { relationship.followed_id = nil }
+    it { should_not be_valid }
+  end
+
+  describe "when follower id is not present" do
+    before { relationship.follower_id = nil }
+    it { should_not be_valid }
+  end
+end

File spec/models/user_spec.rb

 #  updated_at      :datetime         not null
 #  password_digest :string(255)
 #  remember_token  :string(255)
+#  admin           :boolean          default(FALSE)
 #
 
 require 'spec_helper'
   it { should respond_to(:authenticate) }
   it { should respond_to(:microposts) }
   it { should respond_to(:feed) }
+  it { should respond_to(:relationships) }
+  it { should respond_to(:followed_users) }
+  it { should respond_to(:reverse_relationships) }
+  it { should respond_to(:followers) }
+  it { should respond_to(:following?) }
+  it { should respond_to(:follow!) }
+  it { should respond_to(:unfollow!) }
 
   it { should be_valid }
   it { should_not be_admin }
       let(:unfollowed_post) do
         FactoryGirl.create(:micropost, user: FactoryGirl.create(:user))
       end
+      let(:followed_user) { FactoryGirl.create(:user) }
+
+      before do
+        @user.follow!(followed_user)
+        3.times { followed_user.microposts.create!(content: "Lorem ipsum") }
+      end
+
 
       its(:feed) { should include(newer_micropost) }
       its(:feed) { should include(older_micropost) }
       its(:feed) { should_not include(unfollowed_post) }
+      its(:feed) do
+        followed_user.microposts.each do |micropost|
+          should include(micropost)
+        end
+      end
+    end
+  end
+
+  describe "following" do
+    let(:other_user) { FactoryGirl.create(:user) }
+    before do
+      @user.save
+      @user.follow!(other_user)
+    end
+
+    it { should be_following(other_user) }
+    its(:followed_users) { should include(other_user) }
+
+    describe "followed user" do
+      subject { other_user }
+      its(:followers) { should include(@user) }
+    end
+
+    describe "and unfollowing" do
+      before { @user.unfollow!(other_user) }
+
+      it { should_not be_following(other_user) }
+      its(:followed_users) { should_not include(other_user) }
     end
   end
 end

File spec/requests/authentication_pages_spec.rb

         end
       end
 
+      describe "in the Relationships controller" do
+        describe "submitting to the create action" do
+          before { post relationships_path }
+          specify { response.should redirect_to(signin_path) }
+        end
+
+        describe "submitting to the destroy action" do
+          before { delete relationship_path(1) }
+          specify { response.should redirect_to(signin_path) }
+        end
+      end
+
       describe "in the Microposts controller" do
         
         describe "submitting to the create action" do
       end
 
       describe "in the Users controller" do
+        let(:user) { FactoryGirl.create(:user) }
         before { visit edit_user_path(user) }
         it { should have_selector('title', text: 'Sign in') }
 
           before { put user_path(user) }
           specify { response.should redirect_to(signin_path) }
         end
+
+        describe "visiting the following page" do
+          before { visit following_user_path(user) }
+          it { should have_selector('title', text: 'Sign in') }
+        end
+
+        describe "visiting the followers page" do
+          before { visit followers_user_path(user) }
+          it { should have_selector('title', text: 'Sign in') }
+        end
       end
     end
 

File spec/requests/static_pages_spec.rb

           page.should have_selector("li##{item.id}", text: item.content)
         end
       end
+
+      describe "follower/following counts" do
+        let(:other_user) { FactoryGirl.create(:user) }
+        before do
+          other_user.follow!(user)
+          visit root_path
+        end
+
+        it { should have_link("0 following", href: following_user_path(user)) }        
+        it { should have_link("1 followers", href: followers_user_path(user)) }
+      end
     end
   end
 

File spec/requests/user_pages_spec.rb

       it { should have_content(m2.content) }
       it { should have_content(user.microposts.count) }
     end
+
+    describe "follow/unfollow buttons" do
+      let(:other_user) { FactoryGirl.create(:user) }
+      before { sign_in user }
+
+      describe "following a user" do
+        before { visit user_path(other_user) }
+
+        it "should increment the followed user count" do
+          expect do
+            click_button "Follow"
+          end.to change(user.followed_users, :count).by(1)
+        end
+
+        it "should increment the other user's followers count" do
+          expect do
+            click_button "Follow"
+          end.to change(other_user.followers, :count).by(1)
+        end
+
+        describe "toggling the button" do
+          before { click_button "Follow" }
+          it { should have_selector('input', value: 'Unfollow') }
+        end
+      end
+
+      describe "unfollowing a user" do
+        before do
+          user.follow!(other_user)
+          visit user_path(other_user)
+        end
+
+        it "should decrement the followed user count" do
+          expect do
+            click_button "Unfollow"
+          end.to change(user.followed_users, :count).by(-1)
+        end
+
+        it "should decrement the other user's followers count" do
+          expect do
+            click_button "Unfollow"
+          end.to change(other_user.followers, :count).by(-1)
+        end
+
+        describe "toggling the button" do
+          before { click_button "Unfollow" }
+          it { should have_selector('input', value: 'Follow') }
+        end
+      end
+    end
   end
 
   describe "signup" do
       specify { user.reload.email.should == new_email }
     end
   end
+
+  describe "following/followers" do
+    let(:user) { FactoryGirl.create(:user) }
+    let(:other_user) { FactoryGirl.create(:user) }
+    before { user.follow!(other_user) }
+
+    describe "followed users" do
+      before do
+        sign_in user
+        visit following_user_path(user)
+      end
+
+      it { should have_selector('title', text: full_title('Following')) }
+      it { should have_selector('h3', text: 'Following') }
+      it { should have_link(other_user.name, href: user_path(other_user)) }
+    end
+
+    describe "followers" do
+      before do
+        sign_in other_user
+        visit followers_user_path(other_user)
+      end
+
+      it { should have_selector('title', text: full_title('Followers')) }
+      it { should have_selector('h3', text: 'Followers') }
+      it { should have_link(user.name, href: user_path(user)) }
+    end
+  end
 end