From aa2439842fca310a0ced01a905efa0c5ef9f6b0b Mon Sep 17 00:00:00 2001 From: atom-morgan Date: Sun, 26 Jan 2014 14:26:47 -0500 Subject: [PATCH 1/5] Create Relationship model --- app/models/relationship.rb | 7 +++++++ app/models/user.rb | 19 +++++++++++++++++++ .../20140126185610_create_relationships.rb | 13 +++++++++++++ db/schema.rb | 13 ++++++++++++- test/fixtures/relationships.yml | 9 +++++++++ test/models/relationship_test.rb | 7 +++++++ 6 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 app/models/relationship.rb create mode 100644 db/migrate/20140126185610_create_relationships.rb create mode 100644 test/fixtures/relationships.yml create mode 100644 test/models/relationship_test.rb diff --git a/app/models/relationship.rb b/app/models/relationship.rb new file mode 100644 index 0000000..96948b9 --- /dev/null +++ b/app/models/relationship.rb @@ -0,0 +1,7 @@ +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 a755e66..531c979 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,5 +1,12 @@ class User < ActiveRecord::Base has_many :posts, 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 @@ -27,6 +34,18 @@ def feed Post.where("user_id = ?", id) 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 def create_remember_token diff --git a/db/migrate/20140126185610_create_relationships.rb b/db/migrate/20140126185610_create_relationships.rb new file mode 100644 index 0000000..fe36b5a --- /dev/null +++ b/db/migrate/20140126185610_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 diff --git a/db/schema.rb b/db/schema.rb index dcc5418..aaff66b 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: 20140122232133) do +ActiveRecord::Schema.define(version: 20140126185610) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -27,6 +27,17 @@ add_index "posts", ["user_id", "created_at"], name: "index_posts_on_user_id_and_created_at", using: :btree + 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", using: :btree + add_index "relationships", ["follower_id", "followed_id"], name: "index_relationships_on_follower_id_and_followed_id", unique: true, using: :btree + add_index "relationships", ["follower_id"], name: "index_relationships_on_follower_id", using: :btree + create_table "users", force: true do |t| t.string "email" t.string "name" diff --git a/test/fixtures/relationships.yml b/test/fixtures/relationships.yml new file mode 100644 index 0000000..ad542c1 --- /dev/null +++ b/test/fixtures/relationships.yml @@ -0,0 +1,9 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + follower_id: 1 + followed_id: 1 + +two: + follower_id: 1 + followed_id: 1 diff --git a/test/models/relationship_test.rb b/test/models/relationship_test.rb new file mode 100644 index 0000000..700cc41 --- /dev/null +++ b/test/models/relationship_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class RelationshipTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end From 1432cfd5f3b1dd982b5c3b98364a8bf442cc57a7 Mon Sep 17 00:00:00 2001 From: atom-morgan Date: Sun, 26 Jan 2014 16:02:03 -0500 Subject: [PATCH 2/5] Show followers/following and implement follow/unfollow functionality --- app/controllers/relationships_controller.rb | 15 +++++ app/controllers/users_controller.rb | 16 +++++- app/views/shared/_stats.html.erb | 15 +++++ app/views/static_pages/home.html.erb | 3 + app/views/users/_follow.html.erb | 4 ++ app/views/users/_follow_form.html.erb | 9 +++ app/views/users/_unfollow.html.erb | 4 ++ app/views/users/show.html.erb | 4 ++ app/views/users/show_follow.html.erb | 20 +++++++ config/routes.rb | 8 ++- lib/tasks/sample_data.rake | 63 ++++++++++++--------- 11 files changed, 132 insertions(+), 29 deletions(-) create mode 100644 app/controllers/relationships_controller.rb 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 diff --git a/app/controllers/relationships_controller.rb b/app/controllers/relationships_controller.rb new file mode 100644 index 0000000..0fa2374 --- /dev/null +++ b/app/controllers/relationships_controller.rb @@ -0,0 +1,15 @@ +class RelationshipsController < ApplicationController + before_action :signed_in_user + + def create + @user = User.find(params[:relationship][:followed_id]) + current_user.follow!(@user) + redirect_to @user + end + + def destroy + @user = Relationship.find(params[:id]).followed + current_user.unfollow!(@user) + redirect_to @user + end +end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 9e0c70d..a25bb40 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,6 +1,6 @@ class UsersController < ApplicationController before_action :not_new_user, only: [:new, :create] - 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 @@ -46,6 +46,20 @@ def destroy redirect_to users_url end + def following + @title = "Following" + @user = User.find(params[:id]) + @users = @user.followed_users + render 'show_follow' + end + + def followers + @title = "Followers" + @user = User.find(params[:id]) + @users = @user.followers + render 'show_follow' + end + private def user_params diff --git a/app/views/shared/_stats.html.erb b/app/views/shared/_stats.html.erb new file mode 100644 index 0000000..99d1c20 --- /dev/null +++ b/app/views/shared/_stats.html.erb @@ -0,0 +1,15 @@ +<% @user ||= current_user %> + \ 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 ba6da77..ccbf771 100644 --- a/app/views/static_pages/home.html.erb +++ b/app/views/static_pages/home.html.erb @@ -4,6 +4,9 @@
<%= render 'shared/user_info' %>
+
+ <%= render 'shared/stats' %> +
<%= render 'shared/post_form' %>
diff --git a/app/views/users/_follow.html.erb b/app/views/users/_follow.html.erb new file mode 100644 index 0000000..e4fe7ac --- /dev/null +++ b/app/views/users/_follow.html.erb @@ -0,0 +1,4 @@ +<%= form_for(current_user.relationships.build(followed_id: @user.id)) do |f| %> +
<%= f.hidden_field :followed_id %>
+ <%= f.submit "Follow", class: "btn btn-large btn-primary" %> +<% end %> diff --git a/app/views/users/_follow_form.html.erb b/app/views/users/_follow_form.html.erb new file mode 100644 index 0000000..0b17f7b --- /dev/null +++ b/app/views/users/_follow_form.html.erb @@ -0,0 +1,9 @@ +<% unless current_user?(@user) %> +
+ <% if current_user.following?(@user) %> + <%= render 'unfollow' %> + <% else %> + <%= render 'follow' %> + <% end %> +
+<% end %> diff --git a/app/views/users/_unfollow.html.erb b/app/views/users/_unfollow.html.erb new file mode 100644 index 0000000..81c69b3 --- /dev/null +++ b/app/views/users/_unfollow.html.erb @@ -0,0 +1,4 @@ +<%= form_for(current_user.relationships.find_by(followed_id: @user.id), + html: { method: :delete }) do |f| %> + <%= f.submit "Unfollow", class: "btn btn-large" %> +<% end %> diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index cb515ec..9a6b413 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -4,8 +4,12 @@

<%= @user.name %>

+
+ <%= render 'shared/stats' %> +
+ <%= render 'follow_form' if signed_in? %> <% if @user.posts.any? %>

Posts (<%= @user.posts.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..5730912 --- /dev/null +++ b/app/views/users/show_follow.html.erb @@ -0,0 +1,20 @@ +
    + +
    +

    <%= @title %>

    + <% if @users.any? %> +
      + <%= render @users %> +
    + <% end %> +
    +
    \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index c1183ee..daac21d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -3,7 +3,11 @@ match '/help', to: 'static_pages#help', via: 'get' match '/about', to: 'static_pages#about', via: 'get' - resources :users + resources :users do + member do + get :following, :followers + end + end match '/signup', to: 'users#new', via: 'get' resources :sessions, only: [:new, :create, :destroy] @@ -12,6 +16,8 @@ resources :posts, only: [:create, :destroy] + resources :relationships, only: [:create, :destroy] + # The priority is based upon order of creation: first created -> highest priority. # See how all your routes lay out with "rake routes". diff --git a/lib/tasks/sample_data.rake b/lib/tasks/sample_data.rake index a4179cb..a4a506c 100644 --- a/lib/tasks/sample_data.rake +++ b/lib/tasks/sample_data.rake @@ -1,34 +1,43 @@ namespace :db do desc "Fill database with sample data" task populate: :environment do - User.create!(name: "Adam Morganaki", - email: "adam@monkfishapp.com", - password: "foobar", - password_confirmation: "foobar", - admin: true) - User.create!(name: "Kill Clean", - email: "kyle@monkfishapp.com", - password: "foobar", - password_confirmation: "foobar", - admin: true) - 25.times do |n| - name = Faker::Name.name - email = "example-#{n+1}@testing.org" - password = "password" - User.create!(name: name, - email: email, - password: password, - password_confirmation: password) - end + make_users + make_posts + make_relationships end +end - desc "Fill database with sample data" - task populate: :environment do - users = User.all(limit: 5) - 10.times do - title = Faker::Lorem.sentence(5) - content = Faker::Lorem.sentence(5) - users.each { |user| user.posts.create!(title: title, content: content) } - end +def make_users + admin = User.create!(name: "Adam Morganaki", + email: "adam@monkfishapp.com", + password: "foobar", + password_confirmation: "foobar", + admin: true) + 99.times do |n| + name = Faker::Name.name + email = "example-#{n+1}@testing.org" + password = "password" + User.create!(name: name, + email: email, + password: password, + password_confirmation: password) end end + +def make_posts + users = User.all(limit: 5) + 10.times do + title = Faker::Lorem.sentence(5) + content = Faker::Lorem.sentence(5) + users.each { |user| user.posts.create!(title: title, 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 From a8bc04471918ae9e44700e812a09cf0c294053c6 Mon Sep 17 00:00:00 2001 From: atom-morgan Date: Sun, 26 Jan 2014 16:19:32 -0500 Subject: [PATCH 3/5] Add Ajax to follow/unfollow functionality --- app/controllers/relationships_controller.rb | 10 ++++++++-- app/views/relationships/create.js.erb | 2 ++ app/views/relationships/destroy.js.erb | 2 ++ app/views/users/_follow.html.erb | 3 ++- app/views/users/_unfollow.html.erb | 3 ++- 5 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 app/views/relationships/create.js.erb create mode 100644 app/views/relationships/destroy.js.erb diff --git a/app/controllers/relationships_controller.rb b/app/controllers/relationships_controller.rb index 0fa2374..66a8a0c 100644 --- a/app/controllers/relationships_controller.rb +++ b/app/controllers/relationships_controller.rb @@ -4,12 +4,18 @@ class RelationshipsController < ApplicationController def create @user = User.find(params[:relationship][:followed_id]) current_user.follow!(@user) - redirect_to @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) - redirect_to @user + respond_to do |format| + format.html { redirect_to @user } + format.js + end end end diff --git a/app/views/relationships/create.js.erb b/app/views/relationships/create.js.erb new file mode 100644 index 0000000..b509ca7 --- /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 %>') diff --git a/app/views/relationships/destroy.js.erb b/app/views/relationships/destroy.js.erb new file mode 100644 index 0000000..1bf797f --- /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 %>') diff --git a/app/views/users/_follow.html.erb b/app/views/users/_follow.html.erb index e4fe7ac..ce97e22 100644 --- a/app/views/users/_follow.html.erb +++ b/app/views/users/_follow.html.erb @@ -1,4 +1,5 @@ -<%= form_for(current_user.relationships.build(followed_id: @user.id)) do |f| %> +<%= form_for(current_user.relationships.build(followed_id: @user.id), + remote: true) do |f| %>
    <%= f.hidden_field :followed_id %>
    <%= f.submit "Follow", class: "btn btn-large btn-primary" %> <% end %> diff --git a/app/views/users/_unfollow.html.erb b/app/views/users/_unfollow.html.erb index 81c69b3..f75f602 100644 --- a/app/views/users/_unfollow.html.erb +++ b/app/views/users/_unfollow.html.erb @@ -1,4 +1,5 @@ <%= form_for(current_user.relationships.find_by(followed_id: @user.id), - html: { method: :delete }) do |f| %> + html: { method: :delete }, + remote: true) do |f| %> <%= f.submit "Unfollow", class: "btn btn-large" %> <% end %> From fe174e8e2b471f1b66e5bf8658b18bc4c141120b Mon Sep 17 00:00:00 2001 From: atom-morgan Date: Sun, 26 Jan 2014 16:38:52 -0500 Subject: [PATCH 4/5] Implement completed post feed to include followed users --- app/models/post.rb | 7 +++++++ app/models/user.rb | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/models/post.rb b/app/models/post.rb index 930d484..3f3593a 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -4,4 +4,11 @@ class Post < ActiveRecord::Base validates :user_id, presence: true validates :content, presence: true validates :title, presence: true, length: { maximum: 150 } + + 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/user.rb b/app/models/user.rb index 531c979..fa93de7 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -31,7 +31,7 @@ def User.encrypt(token) end def feed - Post.where("user_id = ?", id) + Post.from_users_followed_by(self) end def following?(other_user) From 7dd63b538afd12d169f2006d1c2c6d2d70f31b10 Mon Sep 17 00:00:00 2001 From: atom-morgan Date: Mon, 27 Jan 2014 20:36:26 -0500 Subject: [PATCH 5/5] Add comments for clarification --- app/controllers/relationships_controller.rb | 2 +- app/models/post.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/relationships_controller.rb b/app/controllers/relationships_controller.rb index 66a8a0c..fad3ab7 100644 --- a/app/controllers/relationships_controller.rb +++ b/app/controllers/relationships_controller.rb @@ -4,7 +4,7 @@ class RelationshipsController < ApplicationController def create @user = User.find(params[:relationship][:followed_id]) current_user.follow!(@user) - respond_to do |format| + respond_to do |format| # only one of these lines gets executed based on the type of request (HTTP vs Ajax) format.html { redirect_to @user } format.js end diff --git a/app/models/post.rb b/app/models/post.rb index 3f3593a..9bbf5e9 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -8,6 +8,7 @@ class Post < ActiveRecord::Base 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