From d9df28fa6c04d7ab70810d1f0d176a04c11b02d8 Mon Sep 17 00:00:00 2001 From: James Coleman Date: Fri, 11 Jul 2014 15:36:52 -0400 Subject: [PATCH 1/2] Implemented the user profile page --- app/controllers/users_controller.rb | 6 +++- app/models/user.rb | 2 +- app/views/users/show.html.haml | 36 +++++++++++++++++++++++ app/views/videos/show.html.haml | 4 ++- config/routes.rb | 3 ++ spec/controllers/users_controller_spec.rb | 13 ++++++++ spec/models/user_spec.rb | 1 + 7 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 app/views/users/show.html.haml diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index f31f9d38a..e228db35e 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,5 +1,5 @@ class UsersController < ApplicationController - + before_filter :require_user, only: [:show] def new @user = User.new end @@ -13,6 +13,10 @@ def create end end + def show + @user = User.find(params[:id]) + end + private def user_params diff --git a/app/models/user.rb b/app/models/user.rb index 68a0b36ae..bdb6c7af8 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,7 +1,7 @@ class User < ActiveRecord::Base validates_presence_of :email, :password, :full_name validates_uniqueness_of :email - has_many :reviews + has_many :reviews, -> { order "created_at DESC" } has_secure_password validations: false diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml new file mode 100644 index 000000000..df5c47769 --- /dev/null +++ b/app/views/users/show.html.haml @@ -0,0 +1,36 @@ +%section.user.container + .row + .col-sm-10.col-sm-offset-1 + %article + %header + %img(src="http://www.gravatar.com/avatar/#{Digest::MD5.hexdigest(@user.email.downcase)}?s=40") + %h2 #{@user.full_name}'s video collections (#{@user.queue_items.count}) + %button.btn.btn-default Follow + %table.table + %thead + %tr + %th(width="30%") Video Title + %th(width="15%") Genre + %tbody + - @user.queue_items.each do |queue_item| + %tr + %td + = link_to queue_item.video.title, queue_item.video + %td + = link_to queue_item.category_name, queue_item.category + +%section.user_reviews.container + .row + .col-sm-10.col-sm-offset-1 + %header + %h3 #{@user.full_name}'s Reviews (#{@user.reviews.count}) + %ul + - @user.reviews.each do |review| + %article.review + %li.row + .col-sm-2 + %p + = link_to review.video.title, review.video + %col Rating: #{review.rating} / 5 + .col-sm-8 + %p= review.content \ No newline at end of file diff --git a/app/views/videos/show.html.haml b/app/views/videos/show.html.haml index e4a85f7c3..885d81ce1 100644 --- a/app/views/videos/show.html.haml +++ b/app/views/videos/show.html.haml @@ -32,6 +32,8 @@ %li.row .col-sm-2 %span Rating: #{review.rating} / 5 - %p by #{review.user.full_name} + %p + by + = link_to review.user.full_name, review.user .col-sm-8 %p= review.content diff --git a/config/routes.rb b/config/routes.rb index 984def343..0077c656a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -7,6 +7,9 @@ end resources :reviews, only: [:create] end + + resources :users, only: [:show] + resources :categories resources :queue_items, only: [:create, :destroy] post 'update_queue', to: 'queue_items#update_queue' diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index 970f0a7d2..880aa2bf4 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -35,4 +35,17 @@ end end end + + describe "GET show" do + it_behaves_like "requires sign in" do + let(:action) { get :show, id: 3} + end + + it "sets @user" do + set_current_user + alice = Fabricate(:user) + get :show, id: alice.id + expect(assigns(:user)).to eq(alice) + end + end end \ No newline at end of file diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index a44fd047d..aa66d900c 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -6,6 +6,7 @@ it { should validate_presence_of(:full_name) } it { should validate_uniqueness_of(:email) } it { should have_many(:queue_items).order(:position)} + it { should have_many(:reviews).order("created_at DESC")} describe "#queued_video?" do it "returns true when the user queued the video" do From aeb77ad5c30d59abfd08fb4b60731263577eecf2 Mon Sep 17 00:00:00 2001 From: James Coleman Date: Mon, 14 Jul 2014 16:41:53 -0400 Subject: [PATCH 2/2] Implemented user profile page, People I Follow page, ability to follow a person, and feature spec for social networking features --- app/assets/stylesheets/user.sass | 2 +- app/controllers/relationships_controller.rb | 18 ++++ app/models/relationship.rb | 4 + app/models/user.rb | 10 +++ app/views/relationships/index.html.haml | 21 +++++ app/views/shared/_header.html.haml | 6 +- app/views/users/show.html.haml | 2 +- config/routes.rb | 2 + .../20140714122019_create_relationships.rb | 8 ++ db/schema.rb | 9 +- .../relationships_controller_spec.rb | 87 +++++++++++++++++++ spec/fabricators/relationship_fabricator.rb | 3 + spec/features/user_following_spec.rb | 24 +++++ .../user_interacts_with_queue_spec.rb | 2 +- spec/models/user_spec.rb | 15 ++++ spec/support/macros.rb | 3 + 16 files changed, 209 insertions(+), 7 deletions(-) create mode 100644 app/controllers/relationships_controller.rb create mode 100644 app/models/relationship.rb create mode 100644 app/views/relationships/index.html.haml create mode 100644 db/migrate/20140714122019_create_relationships.rb create mode 100644 spec/controllers/relationships_controller_spec.rb create mode 100644 spec/fabricators/relationship_fabricator.rb create mode 100644 spec/features/user_following_spec.rb diff --git a/app/assets/stylesheets/user.sass b/app/assets/stylesheets/user.sass index a9af42213..c62f14be2 100644 --- a/app/assets/stylesheets/user.sass +++ b/app/assets/stylesheets/user.sass @@ -12,7 +12,7 @@ section.user h2 font-size: 16px font-weight: normal - button + a float: right section.user_reviews diff --git a/app/controllers/relationships_controller.rb b/app/controllers/relationships_controller.rb new file mode 100644 index 000000000..f8e8bb9c6 --- /dev/null +++ b/app/controllers/relationships_controller.rb @@ -0,0 +1,18 @@ +class RelationshipsController < ApplicationController + before_filter :require_user + def index + @relationships = current_user.following_relationships + end + + def destroy + relationship = Relationship.find(params[:id]) + relationship.destroy if relationship.follower == current_user + redirect_to people_path + end + + def create + leader = User.find(params[:leader_id]) + Relationship.create(leader_id: params[:leader_id], follower: current_user) if current_user.can_follow?(leader) + redirect_to people_path + end +end \ No newline at end of file diff --git a/app/models/relationship.rb b/app/models/relationship.rb new file mode 100644 index 000000000..de5a7ad84 --- /dev/null +++ b/app/models/relationship.rb @@ -0,0 +1,4 @@ +class Relationship < ActiveRecord::Base + belongs_to :follower, class_name: "User" + belongs_to :leader, class_name: "User" +end \ No newline at end of file diff --git a/app/models/user.rb b/app/models/user.rb index bdb6c7af8..12910aea1 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -7,6 +7,9 @@ class User < ActiveRecord::Base has_many :queue_items, -> { order(:position) } + has_many :following_relationships, class_name: "Relationship", foreign_key: :follower_id + has_many :leading_relationships, class_name: "Relationship", foreign_key: :leader_id + def normalize_queue_item_positions queue_items.each_with_index do |queue_item, index| queue_item.update_attributes(position: index+1) @@ -17,4 +20,11 @@ def queued_video?(video) queue_items.map(&:video).include?(video) end + def follows?(another_user) + following_relationships.map(&:leader).include?(another_user) + end + + def can_follow?(another_user) + !(self.follows?(another_user) || self == another_user) + end end \ No newline at end of file diff --git a/app/views/relationships/index.html.haml b/app/views/relationships/index.html.haml new file mode 100644 index 000000000..35ee254ec --- /dev/null +++ b/app/views/relationships/index.html.haml @@ -0,0 +1,21 @@ +%section.people + %header + %h2 People I Follow + %table.table + %thead + %tr + %th(width="30%") + %th(width="20%") Videos in Queue + %th(width="20%") Followers + %th(width="30%") Unfollow + %tbody + - @relationships.each do |relationship| + %tr + %td + %img(src="http://www.gravatar.com/avatar/#{Digest::MD5.hexdigest(relationship.leader.email.downcase)}?s=40") + = link_to relationship.leader.full_name, relationship.leader + %td.extra-padding= relationship.leader.queue_items.count + %td.extra-padding= relationship.leader.leading_relationships.count + %td.extra-padding + = link_to relationship, method: :delete do + %i.glyphicon.glyphicon-remove diff --git a/app/views/shared/_header.html.haml b/app/views/shared/_header.html.haml index 3da4fc794..72a30bcdd 100644 --- a/app/views/shared/_header.html.haml +++ b/app/views/shared/_header.html.haml @@ -3,9 +3,9 @@ = link_to "MyFLiX" - if current_user %ul.col-md-4.clearfix - %li= link_to "Videos" - %li= link_to "My Queue" - %li= link_to "People" + %li= link_to "Videos", home_path + %li= link_to "My Queue", my_queue_path + %li= link_to "People", people_path = form_tag search_videos_path, method: "get", class: 'col-md-5 navbar-form' do .form-group = text_field_tag 'search', params[:search], class: 'form-control', placeholder: 'Search for videos here' diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index df5c47769..bc3226512 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -5,7 +5,7 @@ %header %img(src="http://www.gravatar.com/avatar/#{Digest::MD5.hexdigest(@user.email.downcase)}?s=40") %h2 #{@user.full_name}'s video collections (#{@user.queue_items.count}) - %button.btn.btn-default Follow + = link_to "Follow", relationships_path(leader_id: @user.id), class: "btn btn-default", method: :post if current_user.can_follow?(@user) %table.table %thead %tr diff --git a/config/routes.rb b/config/routes.rb index 0077c656a..dd6b14e72 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -9,6 +9,8 @@ end resources :users, only: [:show] + get 'people', to: 'relationships#index' + resources :relationships, only: [:create, :destroy] resources :categories resources :queue_items, only: [:create, :destroy] diff --git a/db/migrate/20140714122019_create_relationships.rb b/db/migrate/20140714122019_create_relationships.rb new file mode 100644 index 000000000..47127033a --- /dev/null +++ b/db/migrate/20140714122019_create_relationships.rb @@ -0,0 +1,8 @@ +class CreateRelationships < ActiveRecord::Migration + def change + create_table :relationships do |t| + t.integer :leader_id, :follower_id + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 48ca69429..4cc250542 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: 20140707101635) do +ActiveRecord::Schema.define(version: 20140714122019) do create_table "categories", force: true do |t| t.string "name" @@ -27,6 +27,13 @@ t.datetime "updated_at" end + create_table "relationships", force: true do |t| + t.integer "leader_id" + t.integer "follower_id" + t.datetime "created_at" + t.datetime "updated_at" + end + create_table "reviews", force: true do |t| t.integer "user_id" t.integer "video_id" diff --git a/spec/controllers/relationships_controller_spec.rb b/spec/controllers/relationships_controller_spec.rb new file mode 100644 index 000000000..e043f55e0 --- /dev/null +++ b/spec/controllers/relationships_controller_spec.rb @@ -0,0 +1,87 @@ +require 'spec_helper' + +describe RelationshipsController do + describe "GET index" do + it "sets @relationships to the current user's following relationships" do + alice = Fabricate(:user) + set_current_user(alice) + bob = Fabricate(:user) + relationship = Fabricate(:relationship, follower: alice, leader: bob) + get :index + expect(assigns(:relationships)).to eq([relationship]) + end + + it_behaves_like "requires sign in" do + let(:action) { get :index } + end + end + + describe "DELETE destroy" do + it_behaves_like "requires sign in" do + let(:action) { delete :destroy, id: 4 } + end + + it "redirects to the people page" do + alice = Fabricate(:user) + set_current_user(alice) + bob = Fabricate(:user) + relationship = Fabricate(:relationship, follower: alice, leader: bob) + delete :destroy, id: relationship + expect(response).to redirect_to people_path + end + + it "deletes the relationship if the current user is the follower" do + alice = Fabricate(:user) + set_current_user(alice) + bob = Fabricate(:user) + relationship = Fabricate(:relationship, follower: alice, leader: bob) + delete :destroy, id: relationship + expect(Relationship.count).to eq(0) + end + it "does not delete the relationship if the current user is not the follower" do + alice = Fabricate(:user) + set_current_user(alice) + bob = Fabricate(:user) + charlie = Fabricate(:user) + relationship = Fabricate(:relationship, follower: charlie, leader: bob) + delete :destroy, id: relationship + expect(Relationship.count).to eq(1) + end + end + + describe "POST create" do + it_behaves_like "requires sign in" do + let(:action) { post :create, leader_id: 3 } + end + + it "redirects to the people page" do + alice = Fabricate(:user) + bob = Fabricate(:user) + set_current_user(alice) + post :create, leader_id: bob.id + expect(response).to redirect_to people_path + end + + it "creates a relationship that the current user follows the leader" do + alice = Fabricate(:user) + bob = Fabricate(:user) + set_current_user(alice) + post :create, leader_id: bob.id + expect(alice.following_relationships.first.leader).to eq(bob) + end + it "does not create a relationship if the current user already follows the leader" do + alice = Fabricate(:user) + bob = Fabricate(:user) + set_current_user(alice) + Fabricate(:relationship, leader: bob, follower: alice) + post :create, leader_id: bob.id + expect(Relationship.count).to eq(1) + end + it "does not allow one to follow oneself" do + alice = Fabricate(:user) + set_current_user(alice) + post :create, leader_id: alice.id + expect(Relationship.count).to eq(0) + end + end +end \ No newline at end of file diff --git a/spec/fabricators/relationship_fabricator.rb b/spec/fabricators/relationship_fabricator.rb new file mode 100644 index 000000000..a6577d641 --- /dev/null +++ b/spec/fabricators/relationship_fabricator.rb @@ -0,0 +1,3 @@ +Fabricator(:relationship) do + +end \ No newline at end of file diff --git a/spec/features/user_following_spec.rb b/spec/features/user_following_spec.rb new file mode 100644 index 000000000..262557bce --- /dev/null +++ b/spec/features/user_following_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +feature 'User following' do + scenario "user follows and unfollows someone" do + alice = Fabricate(:user) + category = Fabricate(:category) + video = Fabricate(:video, category: category) + Fabricate(:review, user: alice, video: video) + + sign_in + click_on_video_on_home_page(video) + + click_link alice.full_name + click_link "Follow" + expect(page).to have_content(alice.full_name) + + unfollow(alice) + expect(page).not_to have_content(alice.full_name) + end +end + +def unfollow(user) + find("a[data-method='delete']").click +end \ No newline at end of file diff --git a/spec/features/user_interacts_with_queue_spec.rb b/spec/features/user_interacts_with_queue_spec.rb index c7a980b1f..5ad43ceaf 100644 --- a/spec/features/user_interacts_with_queue_spec.rb +++ b/spec/features/user_interacts_with_queue_spec.rb @@ -45,7 +45,7 @@ def expect_link_not_to_be_seen(link_text) def add_video_to_queue(video) visit home_path - find("a[href='/videos/#{video.id}']").click + click_on_video_on_home_page(video) click_link "+ My Queue" end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index aa66d900c..0b1fb5993 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -21,4 +21,19 @@ user.queued_video?(video).should be false end end + + describe "#follows?" do + it "returns true if the user has a following relationship with another user" do + alice = Fabricate(:user) + bob = Fabricate(:user) + Fabricate(:relationship, leader: bob, follower: alice) + expect(alice.follows?(bob)).to be_truthy + end + it "returns false if the user does not have a following relationship with another user" do + alice = Fabricate(:user) + bob = Fabricate(:user) + Fabricate(:relationship, leader: alice, follower: bob) + expect(alice.follows?(bob)).to be_falsey + end + end end \ No newline at end of file diff --git a/spec/support/macros.rb b/spec/support/macros.rb index b9253b64d..a7ce7216c 100644 --- a/spec/support/macros.rb +++ b/spec/support/macros.rb @@ -8,5 +8,8 @@ def sign_in(a_user=nil) fill_in "Email Address", with: user.email fill_in "Password", with: user.password click_button "Sign In" +end +def click_on_video_on_home_page(video) + find("a[href='/videos/#{video.id}']").click end \ No newline at end of file