mirror of
https://github.com/Growstuff/growstuff.git
synced 2026-02-23 10:24:50 -05:00
Merge pull request #448 from yoongkang/followerdev
Allow members to follow other members
This commit is contained in:
25
app/controllers/follows_controller.rb
Normal file
25
app/controllers/follows_controller.rb
Normal file
@@ -0,0 +1,25 @@
|
||||
class FollowsController < ApplicationController
|
||||
|
||||
# POST /follows
|
||||
def create
|
||||
@follow = current_member.follows.build(:followed_id => params[:followed_id])
|
||||
|
||||
if @follow.save
|
||||
flash[:notice] = "Followed #{ @follow.followed.login_name }"
|
||||
redirect_to :back
|
||||
else
|
||||
flash[:error] = "Already following or error while following."
|
||||
redirect_to :back
|
||||
end
|
||||
end
|
||||
|
||||
# DELETE /follows/1
|
||||
def destroy
|
||||
@follow = current_member.follows.find(params[:id])
|
||||
unfollowed_name = @follow.followed.login_name
|
||||
@follow.destroy
|
||||
|
||||
flash[:notice] = "Unfollowed #{ unfollowed_name }"
|
||||
redirect_to root_path
|
||||
end
|
||||
end
|
||||
@@ -23,7 +23,7 @@ class MembersController < ApplicationController
|
||||
# it requires a garden to be passed in @garden.
|
||||
# The new garden is not persisted unless Garden#save is called.
|
||||
@garden = Garden.new
|
||||
|
||||
|
||||
respond_to do |format|
|
||||
format.html # show.html.haml
|
||||
format.json { render :json => @member.to_json(:only => [:id, :login_name, :bio, :created_at, :slug, :location, :latitude, :longitude]) }
|
||||
@@ -34,4 +34,14 @@ class MembersController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
def view_follows
|
||||
@member = Member.confirmed.find(params[:login_name])
|
||||
@follows = @member.followed.paginate(:page => params[:page])
|
||||
end
|
||||
|
||||
def view_followers
|
||||
@member = Member.confirmed.find(params[:login_name])
|
||||
@followers = @member.followers.paginate(:page => params[:page])
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -6,6 +6,8 @@ class Ability
|
||||
|
||||
# everyone can do these things, even non-logged in
|
||||
can :read, :all
|
||||
can :view_follows, Member
|
||||
can :view_followers, Member
|
||||
|
||||
# except these, which don't make sense if you're not logged in
|
||||
cannot :read, Notification
|
||||
@@ -91,6 +93,11 @@ class Ability
|
||||
cannot :update, OrderItem, :order => { :member_id => member.id, :completed_at => nil }
|
||||
cannot :destroy, OrderItem, :order => { :member_id => member.id, :completed_at => nil }
|
||||
|
||||
# following/unfollowing permissions
|
||||
can :create, Follow do |f|
|
||||
!member.already_following?(f.followed) && f.followed_id != member.id
|
||||
end
|
||||
|
||||
if member.has_role? :admin
|
||||
|
||||
can :read, :all
|
||||
|
||||
17
app/models/follow.rb
Normal file
17
app/models/follow.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
class Follow < ActiveRecord::Base
|
||||
attr_accessible :followed_id, :follower_id
|
||||
belongs_to :follower, class_name: "Member"
|
||||
belongs_to :followed, class_name: "Member"
|
||||
validates :follower_id, uniqueness: { :scope => :followed_id }
|
||||
|
||||
after_create do
|
||||
Notification.create(
|
||||
:recipient_id => self.followed_id,
|
||||
:sender_id => self.follower_id,
|
||||
:subject => "#{self.follower.login_name} is now following you",
|
||||
:body => "#{self.follower.login_name} just followed you on #{ENV["GROWSTUFF_SITE_NAME"]}. "
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
@@ -27,6 +27,12 @@ class Member < ActiveRecord::Base
|
||||
|
||||
has_many :photos
|
||||
|
||||
has_many :follows, :class_name => "Follow", :foreign_key => "follower_id"
|
||||
has_many :followed, :through => :follows
|
||||
|
||||
has_many :inverse_follows, :class_name => "Follow", :foreign_key => "followed_id"
|
||||
has_many :followers, :through => :inverse_follows, :source => :follower
|
||||
|
||||
default_scope order("lower(login_name) asc")
|
||||
scope :confirmed, where('confirmed_at IS NOT NULL')
|
||||
scope :located, where("location <> '' and latitude IS NOT NULL and longitude IS NOT NULL")
|
||||
@@ -255,4 +261,12 @@ class Member < ActiveRecord::Base
|
||||
})
|
||||
end
|
||||
|
||||
def already_following?(member)
|
||||
self.follows.exists?(:followed_id => member.id)
|
||||
end
|
||||
|
||||
def get_follow(member)
|
||||
self.follows.where(:followed_id => member.id).first if already_following?(member)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -9,4 +9,3 @@
|
||||
= member.account_type
|
||||
account
|
||||
|
||||
|
||||
|
||||
@@ -3,21 +3,34 @@
|
||||
%ul
|
||||
%li
|
||||
- if member.plantings.count > 0
|
||||
= link_to pluralize(member.plantings.count, "planting"), plantings_by_owner_path(:owner => member)
|
||||
= link_to pluralize(member.plantings.size, "planting"), plantings_by_owner_path(:owner => member)
|
||||
- else
|
||||
0 plantings
|
||||
%li
|
||||
- if member.harvests.count > 0
|
||||
= link_to pluralize(member.harvests.count, "harvest"), harvests_by_owner_path(:owner => member)
|
||||
= link_to pluralize(member.harvests.size, "harvest"), harvests_by_owner_path(:owner => member)
|
||||
- else
|
||||
0 harvests
|
||||
%li
|
||||
- if member.seeds.count > 0
|
||||
= link_to pluralize(member.seeds.count, "seeds"), seeds_by_owner_path(:owner => member)
|
||||
= link_to pluralize(member.seeds.size, "seeds"), seeds_by_owner_path(:owner => member)
|
||||
- else
|
||||
0 seeds
|
||||
%li
|
||||
- if member.posts.count > 0
|
||||
= link_to pluralize(member.posts.count, "post"), posts_by_author_path(:author => member)
|
||||
= link_to pluralize(member.posts.size, "post"), posts_by_author_path(:author => member)
|
||||
- else
|
||||
0 posts
|
||||
|
||||
%li
|
||||
- if member.followed.count > 0
|
||||
= link_to pluralize(member.followed.size, "follow"), member_follows_path(member)
|
||||
- else
|
||||
0 following
|
||||
|
||||
%li
|
||||
- if member.followers.count > 0
|
||||
= link_to pluralize(member.followers.size, "follower"), member_followers_path(member)
|
||||
- else
|
||||
0 followers
|
||||
|
||||
|
||||
@@ -7,7 +7,12 @@
|
||||
= link_to "Upgrade account", shop_path, :class => 'btn btn-default'
|
||||
-if can? :create, Notification and current_member != @member
|
||||
=link_to 'Send message', new_notification_path(:recipient_id => @member.id), :class => 'btn btn-default'
|
||||
|
||||
- if can? :create, Follow and current_member != @member
|
||||
- if !current_member.already_following?(@member)
|
||||
= link_to 'Follow', follows_path(:followed_id => @member.id), :method => :post, :class => 'btn btn-default'
|
||||
- else
|
||||
- follow = current_member.get_follow(@member)
|
||||
= link_to 'Unfollow', follow_path(follow), :method => :delete, :class => 'btn btn-default'
|
||||
- content_for :member_rss_login_name, @member.login_name
|
||||
- content_for :member_rss_slug, @member.slug
|
||||
|
||||
|
||||
16
app/views/members/view_followers.html.haml
Normal file
16
app/views/members/view_followers.html.haml
Normal file
@@ -0,0 +1,16 @@
|
||||
- content_for :title, "#{@member.login_name}'s followers"
|
||||
|
||||
%div.pagination
|
||||
= page_entries_info @followers, :model => "members"
|
||||
= will_paginate @followers
|
||||
|
||||
.row
|
||||
.col-md-12
|
||||
- @followers.each do |f|
|
||||
.col-md-4.three-across
|
||||
.thumbnail
|
||||
= render :partial => "members/thumbnail", :locals => { :member => f }
|
||||
|
||||
%div.pagination
|
||||
= page_entries_info @followers, :model => "members"
|
||||
= will_paginate @followers
|
||||
16
app/views/members/view_follows.html.haml
Normal file
16
app/views/members/view_follows.html.haml
Normal file
@@ -0,0 +1,16 @@
|
||||
- content_for :title, "#{@member.login_name}'s follows"
|
||||
|
||||
%div.pagination
|
||||
= page_entries_info @follows, :model => "members"
|
||||
= will_paginate @follows
|
||||
|
||||
.row
|
||||
.col-md-12
|
||||
- @follows.each do |f|
|
||||
.col-md-4.three-across
|
||||
.thumbnail
|
||||
= render :partial => "members/thumbnail", :locals => { :member => f }
|
||||
|
||||
%div.pagination
|
||||
= page_entries_info @follows, :model => "members"
|
||||
= will_paginate @follows
|
||||
@@ -4,7 +4,7 @@ Growstuff::Application.routes.draw do
|
||||
|
||||
devise_for :members, :controllers => { :registrations => "registrations", :passwords => "passwords" }
|
||||
|
||||
resources :members
|
||||
resources :members
|
||||
|
||||
resources :photos
|
||||
|
||||
@@ -41,6 +41,11 @@ Growstuff::Application.routes.draw do
|
||||
resources :forums
|
||||
resources :notifications
|
||||
|
||||
resources :follows, :only => [:create, :destroy]
|
||||
get '/members/:login_name/follows' => 'members#view_follows', :as => 'member_follows'
|
||||
get '/members/:login_name/followers' => 'members#view_followers', :as => 'member_followers'
|
||||
|
||||
|
||||
get '/places' => 'places#index'
|
||||
get '/places/search' => 'places#search', :as => 'search_places'
|
||||
get '/places/:place' => 'places#show', :as => 'place'
|
||||
@@ -79,4 +84,6 @@ Growstuff::Application.routes.draw do
|
||||
match '/admin/newsletter' => 'admin#newsletter', :as => :admin_newsletter
|
||||
match '/admin/:action' => 'admin#:action'
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
||||
10
db/migrate/20141111130849_create_follows.rb
Normal file
10
db/migrate/20141111130849_create_follows.rb
Normal file
@@ -0,0 +1,10 @@
|
||||
class CreateFollows < ActiveRecord::Migration
|
||||
def change
|
||||
create_table :follows do |t|
|
||||
t.integer :member_id
|
||||
t.integer :followed_id
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
class ChangeFollowsMemberIdToFollowerId < ActiveRecord::Migration
|
||||
def change
|
||||
rename_column :follows, :member_id, :follower_id
|
||||
end
|
||||
end
|
||||
@@ -11,7 +11,7 @@
|
||||
#
|
||||
# It's strongly recommended to check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(:version => 20141018111015) do
|
||||
ActiveRecord::Schema.define(:version => 20141119130555) do
|
||||
|
||||
create_table "account_types", :force => true do |t|
|
||||
t.string "name", :null => false
|
||||
@@ -80,6 +80,13 @@ ActiveRecord::Schema.define(:version => 20141018111015) do
|
||||
add_index "crops_posts", ["crop_id", "post_id"], :name => "index_crops_posts_on_crop_id_and_post_id"
|
||||
add_index "crops_posts", ["crop_id"], :name => "index_crops_posts_on_crop_id"
|
||||
|
||||
create_table "follows", :force => true do |t|
|
||||
t.integer "follower_id"
|
||||
t.integer "followed_id"
|
||||
t.datetime "created_at", :null => false
|
||||
t.datetime "updated_at", :null => false
|
||||
end
|
||||
|
||||
create_table "forums", :force => true do |t|
|
||||
t.string "name", :null => false
|
||||
t.text "description", :null => false
|
||||
|
||||
7
spec/factories/follows.rb
Normal file
7
spec/factories/follows.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
FactoryGirl.define do
|
||||
factory :follow do
|
||||
follower
|
||||
followed
|
||||
end
|
||||
|
||||
end
|
||||
@@ -13,6 +13,8 @@ feature "member profile" do
|
||||
expect(page).to have_content "Account type: Free account"
|
||||
expect(page).to have_content "#{member.login_name}'s gardens"
|
||||
expect(page).to have_link "More about this garden...", :href => garden_path(member.gardens.first)
|
||||
expect(page).to_not have_link "Follow"
|
||||
expect(page).to_not have_link "Unfollow"
|
||||
end
|
||||
|
||||
scenario "no bio" do
|
||||
@@ -143,6 +145,53 @@ feature "member profile" do
|
||||
scenario "has a private message button" do
|
||||
expect(page).to have_link "Send message", :href => new_notification_path(:recipient_id => other_member.id)
|
||||
end
|
||||
|
||||
scenario "has a follow button" do
|
||||
expect(page).to have_link "Follow", :href => follows_path(:followed_id => other_member.id)
|
||||
end
|
||||
end
|
||||
|
||||
context "following another member" do
|
||||
background do
|
||||
visit member_path(other_member)
|
||||
click_link 'Follow'
|
||||
end
|
||||
|
||||
scenario "has correct message and unfollow button" do
|
||||
expect(page).to have_content "Followed #{other_member.login_name}"
|
||||
expect(page).to have_link "Unfollow", :href => follow_path(member.get_follow(other_member))
|
||||
end
|
||||
|
||||
scenario "has a followed member listed in the following page" do
|
||||
visit member_follows_path(member)
|
||||
expect(page).to have_content "#{other_member.login_name}"
|
||||
end
|
||||
|
||||
scenario "has correct message and follow button after unfollow" do
|
||||
click_link 'Unfollow'
|
||||
expect(page).to have_content "Unfollowed #{other_member.login_name}"
|
||||
visit member_path(other_member) # unfollowing redirects to root
|
||||
expect(page).to have_link "Follow", :href => follows_path(:followed_id => other_member.id)
|
||||
end
|
||||
|
||||
scenario "has member in following list" do
|
||||
visit member_follows_path(member)
|
||||
expect(page).to have_content "#{other_member.login_name}"
|
||||
end
|
||||
|
||||
scenario "appears in in followed member's followers list" do
|
||||
visit member_followers_path(other_member)
|
||||
expect(page).to have_content "#{member.login_name}"
|
||||
end
|
||||
|
||||
scenario "removes members from following and followers lists after unfollow" do
|
||||
click_link 'Unfollow'
|
||||
visit member_follows_path(member)
|
||||
expect(page).not_to have_content "#{other_member.login_name}"
|
||||
visit member_followers_path(other_member)
|
||||
expect(page).not_to have_content "#{member.login_name}"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context "home page" do
|
||||
|
||||
45
spec/models/follow_spec.rb
Normal file
45
spec/models/follow_spec.rb
Normal file
@@ -0,0 +1,45 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe Follow do
|
||||
|
||||
before(:each) do
|
||||
@member1 = FactoryGirl.create(:member)
|
||||
@member2 = FactoryGirl.create(:member)
|
||||
end
|
||||
|
||||
it "sends a notification when a follow is created" do
|
||||
expect {
|
||||
Follow.create(:follower_id => @member1.id, :followed_id => @member2.id)
|
||||
}.to change(Notification, :count).by(1)
|
||||
end
|
||||
|
||||
it "does not delete any members when follow is deleted" do
|
||||
expect {
|
||||
follow = Follow.create(:follower_id => @member1.id, :followed_id => @member2.id)
|
||||
follow.destroy
|
||||
}.not_to change(Member, :count)
|
||||
end
|
||||
|
||||
context "when follow is created" do
|
||||
|
||||
before (:each) do
|
||||
@follow = Follow.create(:follower_id => @member1.id, :followed_id => @member2.id)
|
||||
end
|
||||
|
||||
it "should not duplicate follows" do
|
||||
expect(Follow.create(:follower_id => @member1.id, :followed_id => @member2.id)).not_to be_valid
|
||||
end
|
||||
|
||||
it "should list users in following/follower collections when follow is created" do
|
||||
expect(@member1.followed).to include(@member2)
|
||||
expect(@member2.followers).to include(@member1)
|
||||
end
|
||||
|
||||
it "should no longer list users in following/follower collections when follow is deleted" do
|
||||
@follow.destroy
|
||||
expect(@member1.followed).not_to include(@member2)
|
||||
expect(@member2.followers).not_to include(@member1)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -381,4 +381,34 @@ describe 'member' do
|
||||
end
|
||||
end
|
||||
|
||||
context 'member who followed another member' do
|
||||
before(:each) do
|
||||
@member1 = FactoryGirl.create(:member)
|
||||
@member2 = FactoryGirl.create(:member)
|
||||
@member3 = FactoryGirl.create(:member)
|
||||
@follow = @member1.follows.create(:follower_id => @member1.id, :followed_id => @member2.id)
|
||||
end
|
||||
|
||||
context 'already_following' do
|
||||
it 'detects that member is already following a member' do
|
||||
expect(@member1.already_following?(@member2)).to eq true
|
||||
end
|
||||
|
||||
it 'detects that member is not already following a member' do
|
||||
expect(@member1.already_following?(@member3)).to eq false
|
||||
end
|
||||
end
|
||||
|
||||
context 'get_follow' do
|
||||
it 'gets the correct follow for a followed member' do
|
||||
expect(@member1.get_follow(@member2).id).to eq @follow.id
|
||||
end
|
||||
|
||||
it 'returns nil for a member that is not followed' do
|
||||
expect(@member1.get_follow(@member3)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
15
spec/routing/follows_routing_spec.rb
Normal file
15
spec/routing/follows_routing_spec.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
require "spec_helper"
|
||||
|
||||
describe FollowsController do
|
||||
describe "routing" do
|
||||
|
||||
it "routes to #create" do
|
||||
post("/follows").should route_to("follows#create")
|
||||
end
|
||||
|
||||
it "routes to #destroy" do
|
||||
delete("/follows/1").should route_to("follows#destroy", :id => "1")
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user