diff --git a/Rakefile b/Rakefile index 7e6c3f34..1afced0b 100644 --- a/Rakefile +++ b/Rakefile @@ -15,6 +15,8 @@ namespace :justask do answered = Answer.where(user: user).count asked = Question.where(user: user).where(author_is_anonymous: false).count commented = Comment.where(user: user).count + user.friend_count = user.friends.count + user.follower_count = user.followers.count user.answered_count = answered user.asked_count = asked user.commented_count = commented diff --git a/app/assets/javascripts/application.js.erb.coffee b/app/assets/javascripts/application.js.erb.coffee index 84a3a63a..f2e4e380 100644 --- a/app/assets/javascripts/application.js.erb.coffee +++ b/app/assets/javascripts/application.js.erb.coffee @@ -72,7 +72,6 @@ $(document).on "click", "button[name=ib-answer]", -> btn.button "reset" $("textarea[name=ib-answer][data-id=#{iid}]").removeAttr "readonly" -# TODO $(document).on "click", "button[name=ab-destroy]", -> btn = $(this) btn.button "loading" @@ -92,6 +91,49 @@ $(document).on "click", "button[name=ab-destroy]", -> complete: (jqxhr, status) -> btn.button "reset" +$(document).on "click", "button[name=user-action]", -> + btn = $(this) + btn.button "loading" + target = btn[0].dataset.target + action = btn[0].dataset.action + count = Number($("h4.entry-text#follower-count").html()) + + target_url = switch action + when 'follow' + count++ + '/ajax/create_friend' + when 'unfollow' + count-- + '/ajax/destroy_friend' + + success = false + + $.ajax + url: target_url + type: 'POST' + data: + screen_name: target + success: (data, status, jqxhr) -> + success = data.success + if data.success + $("h4.entry-text#follower-count").html(count) + showNotification data.message, data.success + error: (jqxhr, status, error) -> + console.log jqxhr, status, error + showNotification "An error occurred, a developer should check the console for details", false + complete: (jqxhr, status) -> + btn.button "reset" + if success + switch action + when 'follow' + btn[0].dataset.action = 'unfollow' + btn.attr 'class', 'btn btn-default btn-block' + btn.html 'Unfollow' + when 'unfollow' + btn[0].dataset.action = 'follow' + btn.attr 'class', 'btn btn-primary btn-block' + btn.html 'Follow' + $(document).on "click", "button#create-account", -> Turbolinks.visit "/sign_up" diff --git a/app/controllers/ajax/friend_controller.rb b/app/controllers/ajax/friend_controller.rb new file mode 100644 index 00000000..c6e50fef --- /dev/null +++ b/app/controllers/ajax/friend_controller.rb @@ -0,0 +1,39 @@ +class Ajax::FriendController < ApplicationController + def create + params.require :screen_name + + target_user = User.find_by_screen_name(params[:screen_name]) + + begin + current_user.follow target_user + rescue + @status = :fail + @message = "You are already following that user." + @success = false + return + end + + @status = :okay + @message = "Successfully followed user." + @success = true + end + + def destroy + params.require :screen_name + + target_user = User.find_by_screen_name(params[:screen_name]) + + begin + current_user.unfollow target_user + rescue + @status = :fail + @message = "You are not following that user." + @success = false + return + end + + @status = :okay + @message = "Successfully unfollowed user." + @success = true + end +end diff --git a/app/models/relationship.rb b/app/models/relationship.rb new file mode 100644 index 00000000..20dec636 --- /dev/null +++ b/app/models/relationship.rb @@ -0,0 +1,6 @@ +class Relationship < ActiveRecord::Base + belongs_to :source, class_name: 'User' + belongs_to :target, class_name: 'User' + validates :source_id, presence: true + validates :target_id, presence: true +end diff --git a/app/models/user.rb b/app/models/user.rb index 9cd79bec..4795c9ba 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -11,7 +11,15 @@ class User < ActiveRecord::Base has_many :answers, dependent: :destroy has_many :comments, dependent: :destroy has_many :inboxes, dependent: :destroy - + has_many :active_relationships, class_name: 'Relationship', + foreign_key: 'source_id', + dependent: :destroy + has_many :passive_relationships, class_name: 'Relationship', + foreign_key: 'target_id', + dependent: :destroy + has_many :friends, through: :active_relationships, source: :target + has_many :followers, through: :passive_relationships, source: :source + SCREEN_NAME_REGEX = /\A[a-zA-Z0-9_]{1,16}\z/ validates :screen_name, presence: true, format: { with: SCREEN_NAME_REGEX }, uniqueness: { case_sensitive: false } @@ -34,4 +42,32 @@ class User < ActiveRecord::Base where(conditions).first end end + + # @return [Array] the users' timeline + def timeline + Answer.where("user_id in (?) OR user_id = ?", friend_ids, id).order(:created_at).reverse_order + end + + # follows an user. + def follow(target_user) + active_relationships.create(target: target_user) + + # increment counts + increment! :friend_count + target_user.increment! :follower_count + end + + # unfollows an user + def unfollow(target_user) + active_relationships.find_by(target: target_user).destroy + + # decrement counts + decrement! :friend_count + target_user.decrement! :follower_count + end + + # @return [Boolean] true if +current_user+ is following +target_user+ + def following?(target_user) + friends.include? target_user + end end diff --git a/app/views/static/index.html.haml b/app/views/static/index.html.haml index 11a442ad..64516ae7 100644 --- a/app/views/static/index.html.haml +++ b/app/views/static/index.html.haml @@ -1,9 +1,10 @@ - if user_signed_in? .container - %h1 Static#index + %h1 Timeline = render 'layouts/messages' - %p Find me in app/views/static/index.html.haml + - current_user.timeline.each do |answer| + = render 'shared/answerbox', a: answer = render "shared/links" - else .jumbotron diff --git a/app/views/user/_actions.html.haml b/app/views/user/_actions.html.haml new file mode 100644 index 00000000..584a1de1 --- /dev/null +++ b/app/views/user/_actions.html.haml @@ -0,0 +1,10 @@ +- if user_signed_in? + - if @user == current_user + %a.btn.btn-default.btn-block{href: edit_user_profile_path} Edit profile + - else + - if current_user.following? @user + %button#editprofile.btn.btn-default.btn-block{type: :button, name: 'user-action', 'data-action' => :unfollow, 'data-target' => @user.screen_name} + Unfollow + - else + %button#editprofile.btn.btn-primary.btn-block{type: :button, name: 'user-action', 'data-action' => :follow, 'data-target' => @user.screen_name} + Follow \ No newline at end of file diff --git a/app/views/user/show.html.haml b/app/views/user/show.html.haml index 9d86446c..2a5cb540 100644 --- a/app/views/user/show.html.haml +++ b/app/views/user/show.html.haml @@ -14,14 +14,15 @@ %p.user-admin %i.fa.fa-flask Admin - %h4.entry-text= @user.follower_count - %h6.entry-subtext Followers - %h4.entry-text= @user.friend_count - %h6.entry-subtext Followings - %h4.entry-text= @user.asked_count + %h4.entry-text#follower-count= @user.follower_count + %h6.entry-subtext Follower + %h4.entry-text#friend-count= @user.friend_count + %h6.entry-subtext Following + %h4.entry-text#asked-count= @user.asked_count %h6.entry-subtext Questions - %h4.entry-text= @user.answered_count + %h4.entry-text#answered-count= @user.answered_count %h6.entry-subtext Answers + = render 'user/actions' .hidden-xs= render 'shared/links' .col-md-9.col-xs-12.col-sm-9 = render 'shared/questionbox' diff --git a/config/routes.rb b/config/routes.rb index 65a6b651..d316dadc 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -27,6 +27,8 @@ Rails.application.routes.draw do match '/ask', to: 'question#create', via: :post, as: :ask match '/answer', to: 'inbox#destroy', via: :post, as: :answer match '/destroy_answer', to: 'answer#destroy', via: :post, as: :destroy_answer + match '/create_friend', to: 'friend#create', via: :post, as: :create_friend + match '/destroy_friend', to: 'friend#destroy', via: :post, as: :destroy_friend end match '/inbox', to: 'inbox#show', via: 'get' diff --git a/db/migrate/20141130130221_create_relationships.rb b/db/migrate/20141130130221_create_relationships.rb new file mode 100644 index 00000000..838d0fea --- /dev/null +++ b/db/migrate/20141130130221_create_relationships.rb @@ -0,0 +1,14 @@ +class CreateRelationships < ActiveRecord::Migration + def change + create_table :relationships do |t| + t.integer :source_id + t.integer :target_id + + t.timestamps + end + + add_index :relationships, :source_id + add_index :relationships, :target_id + add_index :relationships, [:source_id, :target_id], unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 3b8da33b..83c90912 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: 20141129211448) do +ActiveRecord::Schema.define(version: 20141130130221) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -59,6 +59,17 @@ ActiveRecord::Schema.define(version: 20141129211448) do add_index "questions", ["user_id", "created_at"], name: "index_questions_on_user_id_and_created_at", using: :btree + create_table "relationships", force: true do |t| + t.integer "source_id" + t.integer "target_id" + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "relationships", ["source_id", "target_id"], name: "index_relationships_on_source_id_and_target_id", unique: true, using: :btree + add_index "relationships", ["source_id"], name: "index_relationships_on_source_id", using: :btree + add_index "relationships", ["target_id"], name: "index_relationships_on_target_id", using: :btree + create_table "users", force: true do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false