Skip to content

Commit 8a0eaeb

Browse files
committed
Add user following
1 parent bf8ec15 commit 8a0eaeb

26 files changed

+494
-29
lines changed

app/assets/stylesheets/custom.css.scss

+34-4
Original file line numberDiff line numberDiff line change
@@ -196,19 +196,49 @@ input, textarea, select, .uneditable-input {
196196
border-top: 1px solid #e8e8e8;
197197
}
198198
}
199+
199200
.content {
200201
display: block;
201202
}
203+
202204
.timestamp {
203205
color: $grayLight;
204206
}
205-
.gravatar {
206-
float: left;
207-
margin-right: 10px;
208-
}
207+
209208
aside {
210209
textarea {
211210
height: 100px;
212211
margin-bottom: 5px;
213212
}
214213
}
214+
215+
/* sidebar */
216+
217+
.stats {
218+
overflow: auto;
219+
a {
220+
float: left;
221+
padding: 0 10px;
222+
border-left: 1px solid $grayLighter;
223+
color: gray;
224+
&:first-child {
225+
padding-left: 0;
226+
border: 0;
227+
}
228+
&:hover {
229+
text-decoration: none;
230+
color: $blue;
231+
}
232+
}
233+
strong {
234+
display: block;
235+
}
236+
}
237+
238+
.user_avatars {
239+
overflow: auto;
240+
margin-top: 10px;
241+
.gravatar {
242+
margin: 1px 1px;
243+
}
244+
}
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][:followed_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]).followed
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: [:index, :edit, :update]
2+
before_filter :signed_in_user,
3+
only: [:index, :edit, :update, :following, :followers]
34
before_filter :correct_user, only: [:edit, :update]
45
before_filter :admin_user, only: :destroy
56

@@ -46,6 +47,20 @@ def destroy
4647
redirect_to users_path
4748
end
4849

50+
def following
51+
@title = "Following"
52+
@user = User.find(params[:id])
53+
@users = @user.followed_users.paginate(page: params[:page])
54+
render 'show_follow'
55+
end
56+
57+
def followers
58+
@title = "Followers"
59+
@user = User.find(params[:id])
60+
@users = @user.followers.paginate(page: params[:page])
61+
render 'show_follow'
62+
end
63+
4964
private
5065

5166
def correct_user

app/models/micropost.rb

+14
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,18 @@ class Micropost < ActiveRecord::Base
66
validates :user_id, presence: true
77

88
default_scope order: 'microposts.created_at DESC'
9+
10+
# Returns microposts from the users being followed by the given user.
11+
scope :from_users_followed_by, lambda { |user| followed_by(user) }
12+
13+
private
14+
15+
# Returns an SQL condition for users followed by the given user.
16+
# We include the user's own id as well.
17+
def self.followed_by(user)
18+
followed_user_ids = %(SELECT followed_id FROM relationships
19+
WHERE follower_id = :user_id)
20+
where("user_id IN (#{followed_user_ids}) OR user_id = :user_id",
21+
{ user_id: user })
22+
end
923
end

app/models/relationship.rb

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class Relationship < ActiveRecord::Base
2+
attr_accessible :followed_id
3+
4+
belongs_to :follower, class_name: "User"
5+
belongs_to :followed, class_name: "User"
6+
7+
validates :follower_id, presence: true
8+
validates :followed_id, presence: true
9+
end

app/models/user.rb

+19-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ class User < ActiveRecord::Base
22
attr_accessible :name, :email, :password, :password_confirmation
33
has_secure_password
44
has_many :microposts, dependent: :destroy
5+
has_many :relationships, foreign_key: "follower_id", dependent: :destroy
6+
has_many :followed_users, through: :relationships, source: :followed
7+
has_many :reverse_relationships, foreign_key: "followed_id",
8+
class_name: "Relationship",
9+
dependent: :destroy
10+
has_many :followers, through: :reverse_relationships, source: :follower
511

612
before_save :create_remember_token
713

@@ -12,9 +18,20 @@ class User < ActiveRecord::Base
1218
validates :password, length: { minimum: 6 }
1319
validates :password_confirmation, presence: true
1420

15-
1621
def feed
17-
Micropost.where("user_id = ?", id)
22+
Micropost.from_users_followed_by(self)
23+
end
24+
25+
def following?(other_user)
26+
relationships.find_by_followed_id(other_user.id)
27+
end
28+
29+
def follow!(other_user)
30+
relationships.create!(followed_id: other_user.id)
31+
end
32+
33+
def unfollow!(other_user)
34+
relationships.find_by_followed_id(other_user.id).destroy
1835
end
1936

2037
private

app/views/relationships/create.js.erb

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
$("#follow_form").html("<%= escape_javascript(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("<%= escape_javascript(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.followed_users.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.html.erb

+3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
<section>
55
<%= render 'shared/user_info' %>
66
</section>
7+
<section>
8+
<%= render 'shared/stats' %>
9+
</section>
710
<section>
811
<%= render 'shared/micropost_form' %>
912
</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(followed_id: @user.id),
2+
remote: true) do |f| %>
3+
<div><%= f.hidden_field :followed_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

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

app/views/users/show.html.erb

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
</section>
1010
</aside>
1111
<div class="span8">
12+
<%= render 'follow_form' if signed_in? %>
1213
<% if @user.microposts.any? %>
1314
<h3>Microposts (<%= @user.microposts.count %>)</h3>
1415
<ol class="microposts">

app/views/users/show_follow.html.erb

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

config/routes.rb

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
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]
5-
9+
resources :relationships, only: [:create, :destroy]
10+
611
root to: 'static_pages#home'
712

813
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 :followed_id
6+
7+
t.timestamps
8+
end
9+
10+
add_index :relationships, :follower_id
11+
add_index :relationships, :followed_id
12+
add_index :relationships, [:follower_id, :followed_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 => 20120308210452) do
14+
ActiveRecord::Schema.define(:version => 20120308215846) 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 "followed_id"
28+
t.datetime "created_at", :null => false
29+
t.datetime "updated_at", :null => false
30+
end
31+
32+
add_index "relationships", ["followed_id"], :name => "index_relationships_on_followed_id"
33+
add_index "relationships", ["follower_id", "followed_id"], :name => "index_relationships_on_follower_id_and_followed_id", :unique => true
34+
add_index "relationships", ["follower_id"], :name => "index_relationships_on_follower_id"
35+
2536
create_table "users", :force => true do |t|
2637
t.string "name"
2738
t.string "email"

lib/tasks/sample_data.rake

+36-19
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,42 @@ namespace :db do
22
desc "Fill database with sample data"
33
task populate: :environment do
44
Rake::Task['db:reset'].invoke
5-
admin = User.create!(name: "Example User",
6-
7-
password: "foobar",
8-
password_confirmation: "foobar")
9-
admin.toggle!(:admin)
10-
99.times do |n|
11-
name = Faker::Name.name
12-
email = "example-#{n+1}@railstutorial.org"
13-
password = "password"
14-
User.create!(name: name,
15-
email: email,
16-
password: password,
17-
password_confirmation: password)
18-
end
5+
make_users
6+
make_microposts
7+
make_relationships
8+
end
9+
end
10+
11+
def make_users
12+
admin = User.create!(name: "Example User",
13+
14+
password: "foobar",
15+
password_confirmation: "foobar")
16+
admin.toggle!(:admin)
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)
25+
end
26+
end
1927

20-
users = User.all(limit: 6)
21-
50.times do
22-
content = Faker::Lorem.sentence(5)
23-
users.each { |user| user.microposts.create!(content: content) }
24-
end
28+
def make_microposts
29+
users = User.all(limit: 6)
30+
50.times do
31+
content = Faker::Lorem.sentence(5)
32+
users.each { |user| user.microposts.create!(content: content) }
2533
end
2634
end
35+
36+
def make_relationships
37+
users = User.all
38+
user = users.first
39+
followed_users = users[2..50]
40+
followers = users[3..40]
41+
followed_users.each { |followed| user.follow!(followed) }
42+
followers.each { |follower| follower.follow!(user) }
43+
end

0 commit comments

Comments
 (0)