From 4f2f61558d1698ca4baacd81213cd88fa7f1ecdf Mon Sep 17 00:00:00 2001 From: Ryan Bedard Date: Mon, 18 Nov 2013 21:49:43 -0500 Subject: [PATCH] Add user following --- app/assets/stylesheets/static_pages.css.scss | 29 +++++++++ app/controllers/relationships_controller.rb | 21 +++++++ app/controllers/users_controller.rb | 21 ++++++- app/models/micropost.rb | 11 +++- app/models/relationship.rb | 6 ++ app/models/user.rb | 25 +++++++- app/views/relationships/create.js.erb | 2 + app/views/relationships/destroy.js.erb | 2 + app/views/shared/_stats.html.erb | 15 +++++ app/views/static_pages/home.html.erb | 3 + app/views/users/_follow.html.erb | 5 ++ app/views/users/_follow_form.html.erb | 9 +++ app/views/users/_unfollow.html.erb | 5 ++ app/views/users/show.html.erb | 6 ++ app/views/users/show_follow.html.erb | 30 ++++++++++ config/routes.rb | 6 ++ .../20131118215946_create_relationships.rb | 13 ++++ db/schema.rb | 13 +++- lib/tasks/sample_data.rake | 60 ++++++++++++------- spec/models/relationship_spec.rb | 5 ++ 20 files changed, 260 insertions(+), 27 deletions(-) create mode 100644 app/controllers/relationships_controller.rb create mode 100644 app/models/relationship.rb create mode 100644 app/views/relationships/create.js.erb create mode 100644 app/views/relationships/destroy.js.erb create mode 100644 app/views/shared/_stats.html.erb create mode 100644 app/views/users/_follow.html.erb create mode 100644 app/views/users/_follow_form.html.erb create mode 100644 app/views/users/_unfollow.html.erb create mode 100644 app/views/users/show_follow.html.erb create mode 100644 db/migrate/20131118215946_create_relationships.rb create mode 100644 spec/models/relationship_spec.rb diff --git a/app/assets/stylesheets/static_pages.css.scss b/app/assets/stylesheets/static_pages.css.scss index 0408814..9a85153 100644 --- a/app/assets/stylesheets/static_pages.css.scss +++ b/app/assets/stylesheets/static_pages.css.scss @@ -139,6 +139,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; + } +} + #error_explanation { color: #f00; ul { diff --git a/app/controllers/relationships_controller.rb b/app/controllers/relationships_controller.rb new file mode 100644 index 0000000..fbadfec --- /dev/null +++ b/app/controllers/relationships_controller.rb @@ -0,0 +1,21 @@ +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 @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 \ No newline at end of file diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 6b7b828..8ffd160 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,5 +1,6 @@ class UsersController < ApplicationController - before_action :signed_in_user, only: [:index, :edit, :update, :destroy] + before_action :signed_in_user, + only: [:index, :edit, :update, :destroy, :following, :followers] before_action :correct_user, only: [:edit, :update] before_action :admin_user, only: :destroy @@ -40,8 +41,26 @@ class UsersController < ApplicationController render 'edit' 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 user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) diff --git a/app/models/micropost.rb b/app/models/micropost.rb index de034b6..057dca3 100644 --- a/app/models/micropost.rb +++ b/app/models/micropost.rb @@ -4,4 +4,13 @@ class Micropost < ActiveRecord::Base default_scope -> { order('created_at DESC') } validates :content, presence: true, length: { maximum: 140 } validates :user_id, presence: true -end \ No newline at end of file + + + # Returns microposts from the users being followed by the given user. + def self.from_users_followed_by(user) + followed_user_ids = "SELECT followed_id FROM relationships + WHERE follower_id = :user_id" + where("user_id IN (#{followed_user_ids}) OR user_id = :user_id", + user_id: user.id) + end +end diff --git a/app/models/relationship.rb b/app/models/relationship.rb new file mode 100644 index 0000000..f900f25 --- /dev/null +++ b/app/models/relationship.rb @@ -0,0 +1,6 @@ +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 diff --git a/app/models/user.rb b/app/models/user.rb index c4128b1..00e46ac 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,5 +1,14 @@ class User < ActiveRecord::Base 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 + before_save { self.email = email.downcase } before_create :create_remember_token validates :name, presence: true, length: { maximum: 50 } @@ -19,8 +28,19 @@ class User < ActiveRecord::Base end def feed - # This is preliminary. See "Following users" for the full implementation. - 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 @@ -29,3 +49,4 @@ class User < ActiveRecord::Base self.remember_token = User.encrypt(User.new_remember_token) end end + diff --git a/app/views/relationships/create.js.erb b/app/views/relationships/create.js.erb new file mode 100644 index 0000000..d5509e8 --- /dev/null +++ b/app/views/relationships/create.js.erb @@ -0,0 +1,2 @@ +$("#follow_form").html("<%= escape_javascript(render('users/unfollow')) %>") +$("#followers").html('<%= @user.followers.count %>') \ No newline at end of file diff --git a/app/views/relationships/destroy.js.erb b/app/views/relationships/destroy.js.erb new file mode 100644 index 0000000..fa23b16 --- /dev/null +++ b/app/views/relationships/destroy.js.erb @@ -0,0 +1,2 @@ +$("#follow_form").html("<%= escape_javascript(render('users/follow')) %>") +$("#followers").html('<%= @user.followers.count %>') \ No newline at end of file diff --git a/app/views/shared/_stats.html.erb b/app/views/shared/_stats.html.erb new file mode 100644 index 0000000..e68c491 --- /dev/null +++ b/app/views/shared/_stats.html.erb @@ -0,0 +1,15 @@ +<% @user ||= current_user %> +
+ + + <%= @user.followed_users.count %> + + following + + + + <%= @user.followers.count %> + + followers + +
\ No newline at end of file diff --git a/app/views/static_pages/home.html.erb b/app/views/static_pages/home.html.erb index a0f5d2f..f30c1bd 100644 --- a/app/views/static_pages/home.html.erb +++ b/app/views/static_pages/home.html.erb @@ -3,6 +3,9 @@ +
+ <%= render 'follow_form' if signed_in? %> + <% if @user.microposts.any? %>

Microposts (<%= @user.microposts.count %>)

    diff --git a/app/views/users/show_follow.html.erb b/app/views/users/show_follow.html.erb new file mode 100644 index 0000000..c60e3d6 --- /dev/null +++ b/app/views/users/show_follow.html.erb @@ -0,0 +1,30 @@ +<% provide(:title, @title) %> +
    + +
    +

    <%= @title %>

    + <% if @users.any? %> +
      + <%= render @users %> +
    + <%= will_paginate %> + <% end %> +
    +
    \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 763b8e3..5394b75 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,8 +1,14 @@ SampleApp::Application.routes.draw do + resources :users do + member do + get :following, :followers + end + end resources :products resources :users resources :sessions, only: [:new, :create, :destroy] resources :microposts, only: [:create, :destroy] + resources :relationships, only: [:create, :destroy] root 'static_pages#home' match '/signup', to: 'users#new', via: 'get' match '/signin', to: 'sessions#new', via: 'get' diff --git a/db/migrate/20131118215946_create_relationships.rb b/db/migrate/20131118215946_create_relationships.rb new file mode 100644 index 0000000..c55c5e5 --- /dev/null +++ b/db/migrate/20131118215946_create_relationships.rb @@ -0,0 +1,13 @@ +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 \ No newline at end of file diff --git a/db/schema.rb b/db/schema.rb index 1c0e962..2999858 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20131107052554) do +ActiveRecord::Schema.define(version: 20131118215946) do create_table "microposts", force: true do |t| t.string "content" @@ -29,6 +29,17 @@ ActiveRecord::Schema.define(version: 20131107052554) do t.datetime "updated_at" end + create_table "relationships", force: true do |t| + t.integer "follower_id" + t.integer "followed_id" + t.datetime "created_at" + t.datetime "updated_at" + 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" diff --git a/lib/tasks/sample_data.rake b/lib/tasks/sample_data.rake index 52b02c4..febb52b 100644 --- a/lib/tasks/sample_data.rake +++ b/lib/tasks/sample_data.rake @@ -1,26 +1,42 @@ namespace :db do - desc "Fill database with sample data" task populate: :environment do - - User.create!(name: "Example User", - email: "example@railstutorial.org", - password: "foobar", - password_confirmation: "foobar") - 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 -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: true) + 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 - \ No newline at end of file + +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 \ No newline at end of file diff --git a/spec/models/relationship_spec.rb b/spec/models/relationship_spec.rb new file mode 100644 index 0000000..b23e15c --- /dev/null +++ b/spec/models/relationship_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +describe Relationship do + pending "add some examples to (or delete) #{__FILE__}" +end