Skip to content

Commit

Permalink
merge upstream
Browse files Browse the repository at this point in the history
  • Loading branch information
broskoski committed May 29, 2016
2 parents a3f4430 + a04dfa0 commit bce1c4b
Show file tree
Hide file tree
Showing 15 changed files with 165 additions and 59 deletions.
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
language: ruby
rvm:
- 1.9.3
- 2.0.0
33 changes: 31 additions & 2 deletions README.rdoc
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ Main uses would be for Users to follow other Users or for Users to follow Books,

(Basically, to develop the type of follow system that GitHub has)

{<img src="https://travis-ci.org/tcocca/acts_as_follower.png" />}[https://travis-ci.org/tcocca/acts_as_follower]


== Installation

=== The master branch supports rails 3
=== The master branch supports Rails 4

The first release that support Rails 4 is 0.2.0

Add the gem to the gemfile:
gem "acts_as_follower"
Expand All @@ -19,6 +23,23 @@ Run the generator:

This will generate a migration file as well as a model called Follow.

=== Rails 3.x support

Rails 3 is supports in the rails_3 branch https://github.com/tcocca/acts_as_follower/tree/rails_3
The last gem release for Rails 3 support was 0.1.1, so install the gem using ~> 0.1.1

Add the gem to the gemfile:
gem "acts_as_follower", '~> 0.1.1'

or install from the branch

gem "acts_as_follower", :git => 'git://github.com/tcocca/acts_as_follower.git', :branch => 'rails_3'

Run the generator:
rails generate acts_as_follower

This will generate a migration file as well as a model called Follow.

=== Rails 2.3.x support

Rails 2.3.x is supported in the rails_2.3.x branch http://github.com/tcocca/acts_as_follower/tree/rails_2.3.x but must be installed as a plugin.
Expand Down Expand Up @@ -71,7 +92,7 @@ You can check to see if an object that acts_as_follower is following another obj
user.following?(book) # Returns true or false

To get the total number (count) of follows for a user use the following on a model that acts_as_follower
user.follow_count # Returns and integer
user.follow_count # Returns an integer

To get follow records that have not been blocked use the following
user.all_follows # returns an array of Follow records
Expand All @@ -95,6 +116,10 @@ To get the count of all Follow records by a certain type use the following
There is also a method_missing to get the count by type
user.following_books_count # Calls the user.following_by_type_count('Book') method

There is now a method that will just return the Arel scope for follows so that you can chain anything else you want onto it:
book.follows_scoped
This does not return the actual follows, just the scope of followings including the followables, essentially: book.follows.unblocked.includes(:followable)

The following methods take an optional hash parameter of ActiveRecord options (:limit, :order, etc...)
follows_by_type, all_follows, all_following, following_by_type
---
Expand All @@ -104,6 +129,10 @@ The following methods take an optional hash parameter of ActiveRecord options (:
To get all the followers of a model that acts_as_followable
book.followers # Returns an array of all the followers for that book, a collection of different object types (eg. type User or type Book)

There is also a method that will just return the Arel scope for followers so that you can chain anything else you want onto it:
book.followers_scoped
This does not return the actual followers, just the scope of followings including the followers, essentially: book.followings.includes(:follower)

To get just the number of follows use
book.followers_count

Expand Down
2 changes: 1 addition & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Bundler::GemHelper.install_tasks

require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
require 'rdoc/task'

desc 'Default: run unit tests.'
task :default => :test
Expand Down
8 changes: 5 additions & 3 deletions acts_as_follower.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Gem::Specification.new do |s|
s.homepage = "https://github.com/tcocca/acts_as_follower"
s.summary = %q{A Rubygem to add Follow functionality for ActiveRecord models}
s.description = %q{acts_as_follower is a Rubygem to allow any model to follow any other model. This is accomplished through a double polymorphic relationship on the Follow model. There is also built in support for blocking/un-blocking follow records. Main uses would be for Users to follow other Users or for Users to follow Books, etc… (Basically, to develop the type of follow system that GitHub has)}
s.license = 'MIT'

s.rubyforge_project = "acts_as_follower"

Expand All @@ -19,7 +20,8 @@ Gem::Specification.new do |s|
s.require_paths = ["lib"]

s.add_development_dependency "sqlite3"
s.add_development_dependency "shoulda"
s.add_development_dependency "factory_girl"
s.add_development_dependency "rails", "~>3.0.10"
s.add_development_dependency "shoulda_create"
s.add_development_dependency "shoulda", ">= 3.5.0"
s.add_development_dependency "factory_girl", ">= 4.2.0"
s.add_development_dependency "rails", "~> 4.0.0"
end
3 changes: 2 additions & 1 deletion lib/acts_as_follower/follow_scopes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ module ActsAsFollower #:nodoc:
module FollowScopes

def for_follower(follower)
where(:follower_id => follower.id, :follower_type => parent_class_name(follower))
where(:follower_id => follower.id,
:follower_type => parent_class_name(follower))
end

def for_followable(followable)
Expand Down
26 changes: 19 additions & 7 deletions lib/acts_as_follower/followable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ def followers_by_type(follower_type, options={})
follows = follower_type.constantize.
joins(:follows).
where('follows.blocked' => false,
'follows.followable_id' => self.id,
'follows.followable_type' => parent_class_name(self),
'follows.followable_id' => self.id,
'follows.followable_type' => parent_class_name(self),
'follows.follower_type' => follower_type)
if options.has_key?(:limit)
follows = follows.limit(options[:limit])
Expand Down Expand Up @@ -55,24 +55,36 @@ def method_missing(m, *args)
end
end

def respond_to?(m, include_private = false)
super || m.to_s[/count_(.+)_followers/] || m.to_s[/(.+)_followers/]
end

def blocked_followers_count
self.followings.blocked.count
end

# Returns the following records.
# Returns the followings records scoped
def followers_scoped
self.followings.includes(:follower)
end

def followers(options={})
self.followings.unblocked.includes(:follower).all(options).collect{|f| f.follower}
followers_scope = followers_scoped.unblocked
followers_scope = apply_options_to_scope(followers_scope, options)
followers_scope.to_a.collect{|f| f.follower}
end

# We don't want your blocks! We want our blocks!
# def blocks(options={})
# self.followings.blocked.includes(:follower).all(options).collect{|f| f.follower}
# blocked_followers_scope = followers_scoped.blocked
# blocked_followers_scope = apply_options_to_scope(blocked_followers_scope, options)
# blocked_followers_scope.to_a.collect{|f| f.follower}
# end

# Returns true if the current instance is followed by the passed record
# Returns false if the current instance is blocked by the passed record or no follow is found
def followed_by?(follower)
self.followings.unblocked.for_follower(follower).exists?
self.followings.unblocked.for_follower(follower).first.present?
end

def block(follower)
Expand All @@ -90,7 +102,7 @@ def get_follow_for(follower)
private

def block_future_follow(follower)
follows.create(:followable => self, :follower => follower, :blocked => true)
Follow.create(:followable => self, :follower => follower, :blocked => true)
end

def block_existing_follow(follower)
Expand Down
21 changes: 16 additions & 5 deletions lib/acts_as_follower/follower.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def follow_count
# Does not allow duplicate records to be created.
def follow(followable)
if self != followable
self.follows.find_or_create_by_followable_id_and_followable_type(followable.id, parent_class_name(followable))
self.follows.find_or_create_by(followable_id: followable.id, followable_type: parent_class_name(followable))
end
end

Expand All @@ -40,14 +40,21 @@ def stop_following(followable)
end
end

# returns the follows records to the current instance
def follows_scoped
self.follows.unblocked.includes(:followable)
end

# Returns the follow records related to this instance by type.
def follows_by_type(followable_type, options={})
self.follows.unblocked.includes(:followable).for_followable_type(followable_type).all(options)
follows_scope = follows_scoped.for_followable_type(followable_type)
follows_scope = apply_options_to_scope(follows_scope, options)
end

# Returns the follow records related to this instance with the followable included.
def all_follows(options={})
self.follows.unblocked.includes(:followable).all(options)
follows_scope = follows_scoped
follows_scope = apply_options_to_scope(follows_scope, options)
end

# Returns the actual records which this instance is following.
Expand All @@ -60,8 +67,8 @@ def following_by_type(followable_type, options={})
followables = followable_type.constantize.
joins(:followings).
where('follows.blocked' => false,
'follows.follower_id' => self.id,
'follows.follower_type' => parent_class_name(self),
'follows.follower_id' => self.id,
'follows.follower_type' => parent_class_name(self),
'follows.followable_type' => followable_type)
if options.has_key?(:limit)
followables = followables.limit(options[:limit])
Expand Down Expand Up @@ -90,6 +97,10 @@ def method_missing(m, *args)
end
end

def respond_to?(m, include_private = false)
super || m.to_s[/following_(.+)_count/] || m.to_s[/following_(.+)/]
end

# Returns a follow record for the current instance and followable object.
def get_follow(followable)
self.follows.unblocked.for_followable(followable).first
Expand Down
18 changes: 18 additions & 0 deletions lib/acts_as_follower/follower_lib.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,23 @@ def parent_class_name(obj)
return obj.class.name
end

def apply_options_to_scope(scope, options = {})
if options.has_key?(:limit)
scope = scope.limit(options[:limit])
end
if options.has_key?(:includes)
scope = scope.includes(options[:includes])
end
if options.has_key?(:joins)
scope = scope.joins(options[:joins])
end
if options.has_key?(:where)
scope = scope.where(options[:where])
end
if options.has_key?(:order)
scope = scope.order(options[:order])
end
scope
end
end
end
2 changes: 1 addition & 1 deletion lib/acts_as_follower/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module ActsAsFollower
VERSION = "0.1.1"
VERSION = "0.2.1"
end
34 changes: 23 additions & 11 deletions test/acts_as_followable_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ class ActsAsFollowableTest < ActiveSupport::TestCase

context "instance methods" do
setup do
@sam = Factory(:sam)
@sam = FactoryGirl.create(:sam)
end

should "be defined" do
Expand All @@ -16,10 +16,10 @@ class ActsAsFollowableTest < ActiveSupport::TestCase

context "acts_as_followable" do
setup do
@sam = Factory(:sam)
@jon = Factory(:jon)
@oasis = Factory(:oasis)
@metallica = Factory(:metallica)
@sam = FactoryGirl.create(:sam)
@jon = FactoryGirl.create(:jon)
@oasis = FactoryGirl.create(:oasis)
@metallica = FactoryGirl.create(:metallica)
@sam.follow(@jon)
end

Expand All @@ -30,7 +30,7 @@ class ActsAsFollowableTest < ActiveSupport::TestCase
end

should "return the proper number of multiple followers" do
@bob = Factory(:bob)
@bob = FactoryGirl.create(:bob)
@sam.follow(@bob)
assert_equal 0, @sam.followers_count
assert_equal 1, @jon.followers_count
Expand All @@ -45,15 +45,15 @@ class ActsAsFollowableTest < ActiveSupport::TestCase
end

should "return users (multiple followers)" do
@bob = Factory(:bob)
@bob = FactoryGirl.create(:bob)
@sam.follow(@bob)
assert_equal [], @sam.followers
assert_equal [@sam], @jon.followers
assert_equal [@sam], @bob.followers
end

should "return users (multiple followers, complex)" do
@bob = Factory(:bob)
@bob = FactoryGirl.create(:bob)
@sam.follow(@bob)
@jon.follow(@bob)
assert_equal [], @sam.followers
Expand All @@ -62,7 +62,7 @@ class ActsAsFollowableTest < ActiveSupport::TestCase
end

should "accept AR options" do
@bob = Factory(:bob)
@bob = FactoryGirl.create(:bob)
@bob.follow(@jon)
assert_equal 1, @jon.followers(:limit => 1).count
end
Expand All @@ -86,7 +86,7 @@ class ActsAsFollowableTest < ActiveSupport::TestCase

context "get follow record" do
setup do
@bob = Factory(:bob)
@bob = FactoryGirl.create(:bob)
@follow = @bob.follow(@sam)
end

Expand All @@ -101,7 +101,7 @@ class ActsAsFollowableTest < ActiveSupport::TestCase

context "blocks" do
setup do
@bob = Factory(:bob)
@bob = FactoryGirl.create(:bob)
@jon.block(@sam)
@jon.block(@bob)
end
Expand Down Expand Up @@ -251,6 +251,18 @@ class ActsAsFollowableTest < ActiveSupport::TestCase
assert_equal 1, @oasis.count_user_followers
end
end

context "respond_to?" do
should "advertise that it responds to following methods" do
assert @oasis.respond_to?(:user_followers)
assert @oasis.respond_to?(:user_followers_count)
end

should "return false when called with a nonexistent method" do
assert (not @oasis.respond_to?(:foobar))
end
end

end

end
Loading

0 comments on commit bce1c4b

Please sign in to comment.