Skip to content

Commit 73dba82

Browse files
author
tk
committed
Add following/followers
1 parent 3bfb0a2 commit 73dba82

40 files changed

+598
-78
lines changed

app/assets/stylesheets/custom.css.scss

+29
Original file line numberDiff line numberDiff line change
@@ -212,3 +212,32 @@ aside {
212212
margin-bottom: 5px;
213213
}
214214
}
215+
216+
.stats {
217+
overflow: auto;
218+
a {
219+
float: left;
220+
padding: 0 10px;
221+
border-left: 1px solid $grayLighter;
222+
color: gray;
223+
&:first-child {
224+
padding-left: 0;
225+
border: 0;
226+
}
227+
&:hover {
228+
text-decoration: none;
229+
color: $blue;
230+
}
231+
}
232+
strong {
233+
display: block;
234+
}
235+
}
236+
237+
.user_avatars {
238+
overflow: auto;
239+
margin-top: 10px;
240+
.gravatar {
241+
margin: 1px 1px;
242+
}
243+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
class RelationshipsController < ApplicationController
2+
before_filter :signed_in_user
3+
4+
def create
5+
@user = User.find(params[:relationship][:leader_id])
6+
current_user.follow!(@user)
7+
respond_to do |format|
8+
format.html { redirect_to @user }
9+
format.js
10+
end
11+
end
12+
13+
def destroy
14+
@user = Relationship.find(params[:id]).leader
15+
current_user.unfollow!(@user)
16+
respond_to do |format|
17+
format.html { redirect_to @user }
18+
format.js
19+
end
20+
end
21+
end

app/controllers/users_controller.rb

+16-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
class UsersController < ApplicationController
2-
before_filter :signed_in_user, only: [:edit, :update, :index, :destroy]
2+
before_filter :signed_in_user,
3+
only: [:edit, :update, :index, :destroy, :following, :followers]
34
before_filter :correct_user, only: [:edit, :update]
45
before_filter :admin_user, only: :destroy
56

@@ -54,6 +55,20 @@ def destroy
5455
redirect_to users_url
5556
end
5657

58+
def following
59+
@title = "Following"
60+
@user = User.find(params[:id])
61+
@users = @user.leaders.paginate(page: params[:page])
62+
render 'show_follow'
63+
end
64+
65+
def followers
66+
@title = "Followers"
67+
@user = User.find(params[:id])
68+
@users = @user.followers.paginate(page: params[:page])
69+
render 'show_follow'
70+
end
71+
5772
private
5873

5974
def correct_user

app/models/micropost.rb

+8
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,12 @@ class Micropost < ActiveRecord::Base
2020
validates :content, presence: true, length: { maximum: MAX_LENGTH }
2121

2222
default_scope order: 'microposts.created_at DESC'
23+
24+
def self.from_users_followed_by(user)
25+
leader_ids = "SELECT leader_id FROM relationships
26+
WHERE follower_id = :user_id"
27+
where("user_id IN (#{leader_ids}) OR user_id = :user_id",
28+
user_id: user)
29+
end
30+
2331
end

app/models/relationship.rb

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# == Schema Information
2+
#
3+
# Table name: relationships
4+
#
5+
# id :integer not null, primary key
6+
# follower_id :integer
7+
# leader_id :integer
8+
# created_at :datetime not null
9+
# updated_at :datetime not null
10+
#
11+
12+
class Relationship < ActiveRecord::Base
13+
attr_accessible :leader_id
14+
15+
belongs_to :follower, class_name: "User"
16+
belongs_to :leader, class_name: "User"
17+
18+
validates :follower_id, presence: true
19+
validates :leader_id, presence: true
20+
end

app/models/user.rb

+27-10
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,46 @@
1313
#
1414

1515
class User < ActiveRecord::Base
16-
attr_accessible :name, :email, :password, :password_confirmation
16+
attr_accessible :name, :email, :password, :password_confirmation
1717
has_secure_password
1818
has_many :microposts, dependent: :destroy
19+
has_many :relationships, foreign_key: "follower_id", dependent: :destroy
20+
has_many :leaders, through: :relationships
21+
has_many :reverse_relationships, foreign_key: "leader_id",
22+
class_name: "Relationship",
23+
dependent: :destroy
24+
has_many :followers, through: :reverse_relationships
1925

20-
before_save { self.email.downcase! }
21-
before_save :create_remember_token
26+
before_save { self.email.downcase! }
27+
before_save :create_remember_token
2228

2329
validates :name, presence: true, length: { maximum: 50 }
24-
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
30+
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
2531
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX },
2632
uniqueness: { case_sensitive: false }
2733
validates :password, length: { minimum: 6 }
2834
validates :password_confirmation, presence: true
2935

3036
def feed
31-
# This is preliminary. See "Following users" for the full implementation.
32-
Micropost.where("user_id = ?", id)
37+
Micropost.from_users_followed_by(self)
3338
end
3439

35-
private
40+
def following?(other_user)
41+
relationships.find_by_leader_id(other_user.id)
42+
end
43+
44+
def follow!(other_user)
45+
relationships.create!(leader_id: other_user.id)
46+
end
47+
48+
def unfollow!(other_user)
49+
relationships.find_by_leader_id(other_user.id).destroy
50+
end
51+
52+
private
3653

37-
def create_remember_token
38-
self.remember_token = SecureRandom.urlsafe_base64
39-
end
54+
def create_remember_token
55+
self.remember_token = SecureRandom.urlsafe_base64
56+
end
4057

4158
end

app/views/relationships/create.js.erb

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
$("#follow_form").html("<%= j render('users/unfollow') %>")
2+
$("#followers").html('<%= @user.followers.count %>')
+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
$("#follow_form").html("<%= j render('users/follow') %>")
2+
$("#followers").html('<%= @user.followers.count %>')

app/views/shared/_stats.html.erb

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<% @user ||= current_user %>
2+
<div class="stats">
3+
<a href="<%= following_user_path(@user) %>">
4+
<strong id="following" class="stat">
5+
<%= @user.leaders.count %>
6+
</strong>
7+
following
8+
</a>
9+
<a href="<%= followers_user_path(@user) %>">
10+
<strong id="followers" class="stat">
11+
<%= @user.followers.count %>
12+
</strong>
13+
followers
14+
</a>
15+
</div>

app/views/static_pages/_home_signed_in.html.erb

+3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
<section>
44
<%= render 'shared/user_info' %>
55
</section>
6+
<section>
7+
<%= render 'shared/stats' %>
8+
</section>
69
<section>
710
<%= render 'shared/micropost_form' %>
811
</section>

app/views/users/_follow.html.erb

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<%= form_for(current_user.relationships.build(leader_id: @user.id),
2+
remote: true) do |f| %>
3+
<div><%= f.hidden_field :leader_id %></div>
4+
<%= f.submit "Follow", class: "btn btn-large btn-primary" %>
5+
<% end %>

app/views/users/_follow_form.html.erb

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<% unless current_user?(@user) %>
2+
<div id="follow_form">
3+
<% if current_user.following?(@user) %>
4+
<%= render 'unfollow' %>
5+
<% else %>
6+
<%= render 'follow' %>
7+
<% end %>
8+
</div>
9+
<% end %>

app/views/users/_unfollow.html.erb

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<%= form_for(current_user.relationships.find_by_leader_id(@user),
2+
html: { method: :delete }, remote: true) do |f| %>
3+
<%= f.submit "Unfollow", class: "btn btn-large" %>
4+
<% end %>

app/views/users/show.html.erb

+4
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@
77
<%= @user.name %>
88
</h1>
99
</section>
10+
<section>
11+
<%= render 'shared/stats' %>
12+
</section>
1013
</aside>
1114
<div class="span8">
15+
<%= render 'follow_form' if signed_in? %>
1216
<% if @user.microposts.any? %>
1317
<h3>Microposts (<%= @user.microposts.count %>)</h3>
1418
<ol class="microposts">

app/views/users/show_follow.html.erb

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<% provide(:title, @title) %>
2+
<div class="row">
3+
<aside class="span4">
4+
<section>
5+
<%= gravatar_for @user %>
6+
<h1><%= @user.name %></h1>
7+
<span><%= link_to "view my profile", @user %></span>
8+
<span><b>Microposts:</b> <%= @user.microposts.count %></span>
9+
</section>
10+
<section>
11+
<%= render 'shared/stats' %>
12+
<% if @users.any? %>
13+
<div class="user_avatars">
14+
<% @users.each do |user| %>
15+
<%= link_to gravatar_for(user, size: 30), user %>
16+
<% end %>
17+
</div>
18+
<% end %>
19+
</section>
20+
</aside>
21+
<div class="span8">
22+
<h3><%= @title %></h3>
23+
<% if @users.any? %>
24+
<ul class="users">
25+
<%= render @users %>
26+
</ul>
27+
<%= will_paginate %>
28+
<% end %>
29+
</div>
30+
</div>

config/routes.rb

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
SampleApp::Application.routes.draw do
2-
resources :users
2+
resources :users do
3+
member do
4+
get :following, :followers
5+
end
6+
end
37
resources :sessions, only: [:new, :create, :destroy]
48
resources :microposts, only: [:create, :destroy]
9+
resources :relationships, only: [:create, :destroy]
510

611
root to: 'static_pages#home'
712
match '/signup', to: 'users#new'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
class CreateRelationships < ActiveRecord::Migration
2+
def change
3+
create_table :relationships do |t|
4+
t.integer :follower_id
5+
t.integer :leader_id
6+
7+
t.timestamps
8+
end
9+
10+
add_index :relationships, :follower_id
11+
add_index :relationships, :leader_id
12+
add_index :relationships, [:follower_id, :leader_id], unique: true
13+
end
14+
end

db/schema.rb

+12-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
#
1212
# It's strongly recommended to check this file into your version control system.
1313

14-
ActiveRecord::Schema.define(:version => 20121113195507) do
14+
ActiveRecord::Schema.define(:version => 20121116080832) do
1515

1616
create_table "microposts", :force => true do |t|
1717
t.string "content"
@@ -22,6 +22,17 @@
2222

2323
add_index "microposts", ["user_id", "created_at"], :name => "index_microposts_on_user_id_and_created_at"
2424

25+
create_table "relationships", :force => true do |t|
26+
t.integer "follower_id"
27+
t.integer "leader_id"
28+
t.datetime "created_at", :null => false
29+
t.datetime "updated_at", :null => false
30+
end
31+
32+
add_index "relationships", ["follower_id", "leader_id"], :name => "index_relationships_on_follower_id_and_leader_id", :unique => true
33+
add_index "relationships", ["follower_id"], :name => "index_relationships_on_follower_id"
34+
add_index "relationships", ["leader_id"], :name => "index_relationships_on_leader_id"
35+
2536
create_table "users", :force => true do |t|
2637
t.string "name"
2738
t.string "email"

lib/tasks/sample_data.rake

+36-18
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,42 @@
11
namespace :db do
22
desc "Fill database with sample data"
33
task populate: :environment do
4-
admin = User.create!(name: "Example User",
5-
6-
password: "foobar",
7-
password_confirmation: "foobar")
8-
admin.toggle!(:admin)
9-
99.times do |n|
10-
name = Faker::Name.name
11-
email = "example-#{n+1}@railstutorial.org"
12-
password = "password"
13-
User.create!(name: name,
14-
email: email,
15-
password: password,
16-
password_confirmation: password)
17-
end
4+
make_users
5+
make_microposts
6+
make_relationships
7+
end
8+
end
9+
10+
def make_users
11+
admin = User.create!(name: "Example User",
12+
13+
password: "foobar",
14+
password_confirmation: "foobar")
15+
admin.toggle!(:admin)
1816

19-
users = User.all(limit: 6)
20-
users.each do |user|
21-
50.times { user.microposts.create!(content: Faker::Lorem.sentence(5)) }
22-
end
17+
99.times do |n|
18+
name = Faker::Name.name
19+
email = "example-#{n+1}@railstutorial.org"
20+
password = "password"
21+
User.create!(name: name,
22+
email: email,
23+
password: password,
24+
password_confirmation: password)
2325
end
26+
end
27+
28+
def make_microposts
29+
users = User.all(limit: 6)
30+
users.each do |user|
31+
50.times { user.microposts.create!(content: Faker::Lorem.sentence(5)) }
32+
end
33+
end
34+
35+
def make_relationships
36+
users = User.all
37+
user = users.first
38+
leaders = users[2..50]
39+
followers = users[3..40]
40+
leaders.each { |leader| user.follow!(leader) }
41+
followers.each { |follower| follower.follow!(user) }
2442
end

0 commit comments

Comments
 (0)