Commit 4f9d0a39 by Tô Ngọc Ánh

11.1 -> 11.2.4

parent 4294b5b3
Pipeline #680 failed with stages
in 0 seconds
...@@ -9,3 +9,4 @@ module TempFixForRakeLastComment ...@@ -9,3 +9,4 @@ module TempFixForRakeLastComment
end end
Rake::Application.send :include, TempFixForRakeLastComment Rake::Application.send :include, TempFixForRakeLastComment
Rails.application.load_tasks Rails.application.load_tasks
task 'test:prepare'
\ No newline at end of file
...@@ -144,6 +144,35 @@ aside { ...@@ -144,6 +144,35 @@ aside {
} }
} }
.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;
}
}
.gravatar { .gravatar {
float: left; float: left;
margin-right: 10px; margin-right: 10px;
......
class RelationshipsController < ApplicationController
before_action :signed_in_user
def create
@user = User.find(params[:relationship][:followed_id])
current_user.follow!(@user)
respond_to do |format|
format.html { redirect_to @uer }
format.js
end
end
def destroy
@user = Relationship.find(params[:id]).followed
current_user.unfollow!(@user)
respond_to do |format|
format.html { redirect_to @uer }
format.js
end
end
end
\ No newline at end of file
class UsersController < ApplicationController class UsersController < ApplicationController
before_action :signed_in_user, only: [:index, :edit, :update] before_action :signed_in_user, only: [:index, :edit, :update, :destroy, :following, :followers]
before_action :correct_user, only: [:edit, :update] before_action :correct_user, only: [:edit, :update]
before_action :admin_user, only: :destroy before_action :admin_user, only: :destroy
...@@ -47,6 +47,20 @@ class UsersController < ApplicationController ...@@ -47,6 +47,20 @@ class UsersController < ApplicationController
redirect_to users_url redirect_to users_url
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 private
def user_params def user_params
......
class Relationship < ActiveRecord::Base
belongs_to :follower, class_name: "User"
belongs_to :followed, class_name: "User"
validates :follower_id, presence: true
validates :followed_id, presence: true
end
class User < ActiveRecord::Base class User < ActiveRecord::Base
has_many :microposts, dependent: :destroy 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
validates :name, presence: true, length: { maximum: 50 } validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /[\w+\-.]+@[a-z\d*\-.]+\.[a-z]+/i VALID_EMAIL_REGEX = /[\w+\-.]+@[a-z\d*\-.]+\.[a-z]+/i
...@@ -22,6 +26,18 @@ class User < ActiveRecord::Base ...@@ -22,6 +26,18 @@ class User < ActiveRecord::Base
Micropost.where("user_id = ?", id) Micropost.where("user_id = ?", id)
end 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 private
def create_remember_token def create_remember_token
......
<% @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>
\ No newline at end of file
...@@ -5,6 +5,9 @@ ...@@ -5,6 +5,9 @@
<%= render 'shared/user_info' %> <%= render 'shared/user_info' %>
</section> </section>
<section> <section>
<%= render 'shared/stats' %>
</section>
<section>
<%= render 'shared/micropost_form' %> <%= render 'shared/micropost_form' %>
</section> </section>
</aside> </aside>
......
...@@ -9,5 +9,5 @@ ...@@ -9,5 +9,5 @@
<%= f.label :password %> <%= f.label :password %>
<%= f.password_field :password %> <%= f.password_field :password %>
<%= f.label :password_confirmation, "Confirm Password" %> <%= f.label :password_confirmation, "Confirmation" %>
<%= f.password_field :password_confirmation %> <%= f.password_field :password_confirmation %>
\ No newline at end of file
<%= 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 %>
\ No newline at end of file
<% unless current_user?(@user) %>
<div id="follow_form">
<% if current_user.following?(@user) %>
<%= render 'unfollow' %>
<% else %>
<%= render 'follow' %>
<% end %>
</div>
<% end %>
\ No newline at end of file
<%= form_for(current_user.relationships.find_by(followed_id: @user.id),
html: { method: :delete }, remote: true) do |f| %>
<%= f.submit "Unfollow", class: "btn btn-large" %>
<% end %>
\ No newline at end of file
...@@ -7,8 +7,12 @@ ...@@ -7,8 +7,12 @@
<%= @user.name %> <%= @user.name %>
</h1> </h1>
</section> </section>
<section>
<%= render 'shared/stats' %>
</section>
</aside> </aside>
<div class="span8"> <div class="span8">
<%= render 'follow_form' if signed_in? %>
<% if @user.microposts.any? %> <% if @user.microposts.any? %>
<h3>Microposts (<%= @user.microposts.count %>)</h3> <h3>Microposts (<%= @user.microposts.count %>)</h3>
<ol class="microposts"> <ol class="microposts">
......
<% 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>
\ No newline at end of file
...@@ -9,7 +9,12 @@ Rails.application.routes.draw do ...@@ -9,7 +9,12 @@ Rails.application.routes.draw do
get '/signup', to: 'users#new' get '/signup', to: 'users#new'
get '/signin', to: 'sessions#new' get '/signin', to: 'sessions#new'
delete '/signout', to: 'sessions#destroy' delete '/signout', to: 'sessions#destroy'
resources :users resources :users do
member do
get :following, :followers
end
end
resources :sessions, only: [:new, :create, :destroy] resources :sessions, only: [:new, :create, :destroy]
resources :microposts, only: [:create, :destroy] resources :microposts, only: [:create, :destroy]
resources :relationships, only: [:create, :destroy]
end end
class CreateRelationships < ActiveRecord::Migration
def change
create_table :relationships do |t|
t.integer :follower_id
t.integer :followed_id
t.timestamps null: false
end
add_index :relationships, :follower_id
add_index :relationships, :followed_id
add_index :relationships, [:follower_id, :followed_id], unique: true
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20200706085817) do ActiveRecord::Schema.define(version: 20200709031754) do
create_table "microposts", force: :cascade do |t| create_table "microposts", force: :cascade do |t|
t.string "content" t.string "content"
...@@ -22,6 +22,17 @@ ActiveRecord::Schema.define(version: 20200706085817) do ...@@ -22,6 +22,17 @@ ActiveRecord::Schema.define(version: 20200706085817) do
add_index "microposts", ["user_id", "created_at"], name: "index_microposts_on_user_id_and_created_at" add_index "microposts", ["user_id", "created_at"], name: "index_microposts_on_user_id_and_created_at"
create_table "relationships", force: :cascade 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: :cascade do |t| create_table "users", force: :cascade do |t|
t.string "name" t.string "name"
t.string "email" t.string "email"
......
namespace :db do namespace :db do
desc "Fill database with sample data" desc "Fill database with sample data"
task populate: :environment do task populate: :environment do
User.create!(name: "Example User", make_users
make_microposts
make_relationships
end
end
def make_users
admin = User.create!(name: "Example User",
email: "example@railstutorial.org", email: "example@railstutorial.org",
password: "foobar", password: "foobar",
password_confirmation: "foobar", password_confirmation: "foobar",
...@@ -15,10 +21,21 @@ namespace :db do ...@@ -15,10 +21,21 @@ namespace :db do
password: password, password: password,
password_confirmation: password) password_confirmation: password)
end end
end
def make_microposts
users = User.all.limit(6) users = User.all.limit(6)
50.times do 50.times do
content = Faker::Lorem.sentence(5) content = Faker::Lorem.sentence(5)
users.each { |user| user.microposts.create!(content: content) } users.each { |user| user.microposts.create!(content: content) }
end end
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 end
\ No newline at end of file
require 'spec_helper'
describe RelationshipsController do
let(:user) { FactoryGirl.create(:user) }
let(:other_user) { FactoryGirl.create(:user) }
before { sign_in user, no_capybara: true }
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.to change(Relationship, :count).by(1)
end
it "should respond with success" do
xhr :post, :create, relationship: { followed_id: other_user.id }
expect(response).to be_success
end
end
describe "destroying a relationship with Ajax" do
before { user.follow!(other_user) }
let(:relationship) do
user.relationships.find_by(followed_id: other_user.id)
end
it "should decrement the Relationship count" do
expect do
xhr :delete, :destroy, id: relationship.id
end.to change(Relationship, :count).by(-1)
end
it "should respond with success" do
xhr :delete, :destroy, id: relationship.id
expect(response).to be_success
end
end
end
\ No newline at end of file
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 "follower methods" do
it { should respond_to(:follower) }
it { should respond_to(:followed) }
its(:follower) { should eq follower }
its(:followed) { should eq 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
...@@ -11,13 +11,21 @@ describe User do ...@@ -11,13 +11,21 @@ describe User do
it { should respond_to(:password_digest) } it { should respond_to(:password_digest) }
it { should respond_to(:password) } it { should respond_to(:password) }
it { should respond_to(:password_confirmation) } it { should respond_to(:password_confirmation) }
it { should respond_to(:remember_token) }
it { should respond_to(:authenticate) } it { should respond_to(:authenticate) }
it { should respond_to(:admin) } it { should respond_to(:admin) }
it { should respond_to(:microposts) } it { should respond_to(:microposts) }
it { should respond_to(:feed) } 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 be_valid }
it { should_not be_admin }
describe "when name is not present" do describe "when name is not present" do
before { @user.name = " " } before { @user.name = " " }
...@@ -98,14 +106,6 @@ describe User do ...@@ -98,14 +106,6 @@ describe User do
it { should be_invalid } it { should be_invalid }
end end
it { should respond_to(:password_confirmation) }
it { should respond_to(:remember_token) }
it { should respond_to(:authenticate) }
it { should respond_to(:admin) }
it { should be_valid }
it { should_not be_admin }
describe "with admin attribute set to 'true'" do describe "with admin attribute set to 'true'" do
before do before do
@user.save! @user.save!
...@@ -152,4 +152,27 @@ describe User do ...@@ -152,4 +152,27 @@ describe User do
its(:feed) { should_not include(unfollowed_post) } its(:feed) { should_not include(unfollowed_post) }
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 end
...@@ -91,6 +91,28 @@ describe "AuthenticationPages" do ...@@ -91,6 +91,28 @@ describe "AuthenticationPages" do
before { visit users_path } before { visit users_path }
it { should have_title('Sign in') } it { should have_title('Sign in') }
end end
describe "visiting the following page" do
before { visit following_user_path(user) }
it { should have_title('Sign in') }
end
describe "visiting the followers page" do
before { visit followers_user_path(user) }
it { should have_title('Sign in') }
end
end
describe "in the Relationships controller" do
describe "submitting to the create action" do
before { post relationships_path }
specify { expect(response).to redirect_to(signin_path) }
end
describe "submitting to the destroy action" do
before { delete relationship_path(1) }
specify { expect(response).to redirect_to(signin_path) }
end
end end
end end
......
...@@ -9,6 +9,33 @@ describe "StaticPages" do ...@@ -9,6 +9,33 @@ describe "StaticPages" do
it { should have_content('Sample App') } it { should have_content('Sample App') }
it { should have_title(full_title('')) } it { should have_title(full_title('')) }
it { should_not have_title('| Home') } it { should_not have_title('| Home') }
describe "for signed-in users" do
let(:user) { FactoryGirl.create(:user) }
before do
FactoryGirl.create(:micropost, user: user, content: "Lorem")
FactoryGirl.create(:micropost, user: user, content: "Ipsum")
sign_in user
visit root_path
end
it "should render the user's feed" do
user.feed.each do |item|
expect(page).to 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 end
describe "Help page" do describe "Help page" do
......
...@@ -26,6 +26,56 @@ describe "User pages" do ...@@ -26,6 +26,56 @@ describe "User pages" do
it { should have_content(m2.content) } it { should have_content(m2.content) }
it { should have_content(user.microposts.count) } it { should have_content(user.microposts.count) }
end 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_xpath("//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_xpath("//input[@value='Follow']") }
end
end
end
end end
describe "signup" do describe "signup" do
...@@ -89,7 +139,7 @@ describe "User pages" do ...@@ -89,7 +139,7 @@ describe "User pages" do
fill_in "Name", with: new_name fill_in "Name", with: new_name
fill_in "Email", with: new_email fill_in "Email", with: new_email
fill_in "Password", with: user.password fill_in "Password", with: user.password
fill_in "Confirm Password", with: user.password fill_in "Confirmation", with: user.password
click_button "Save changes" click_button "Save changes"
end end
...@@ -143,4 +193,32 @@ describe "User pages" do ...@@ -143,4 +193,32 @@ describe "User pages" do
end end
end end
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_title(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_title(full_title('Followers')) }
it { should have_selector('h3', text: 'Followers') }
it { should have_link(user.name, href: user_path(user)) }
end
end
end end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment