Permalink
Browse files

Initial commit of the acts_as_follower

  • Loading branch information...
0 parents commit cdd7b1e4e6a486b7473e616e2c66349594cddd0d @tcocca committed Oct 12, 2008
@@ -0,0 +1,44 @@
+Copyright (c) 2008 Tom Cocca
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+The major design pattern of this plugin was abstracted from Peter Jackson's VoteFu, which is subject to the same license.
+Here is the original copyright notice for VoteFu:
+
+Copyright (c) 2008 Peter Jackson (peteonrails.com)
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
104 README
@@ -0,0 +1,104 @@
+ActsAsFollower
+==============
+
+acts_as_follower is a plugin to allow any model to "follow" any other model.
+
+Main uses would be for Users to follow other Users or for Users to follow Books, etc...
+
+(Basics to develop the type of follow system that GitHub has)
+
+Install
+=======
+
+* Run the following command:
+ ./script/plugin install git://github.com/tcocca/acts_as_follower.git
+
+* Create a new rails migration using the generator:
+ ./script/generate acts_as_follower_migration
+
+
+Usage & Examples
+=======
+
+Make your model(s) that you want to allow to to follow acts_as_followable
+
+ class User < ActiveRecord::Base
+ ...
+ acts_as_followable
+ ...
+ end
+
+ class Book < ActiveRecord::Base
+ ...
+ acts_as_followable
+ ...
+ end
+
+Make your model(s) that can follow other model(s) acts_as_follower
+
+ class User < ActiveRecord::Base
+ ...
+ acts_as_follower
+ ...
+ end
+
+ActsAsFollower Methods
+
+- To have a Model object start following another use the following:
+
+ book = Book.find(1)
+ current_user.follow(book) # Creates a record for the current_user as the follower and the book as the followable
+
+- To stop following a Model object use the following
+
+ current_user.stop_following(book) # Deletes that record in the Follow table
+
+- You can check to see if a Model that acts_as_follower is following a Model acts_as_followable through the following:
+
+ current_user.following?(book) # Returns true or false
+
+- To get all of the records that a Model is following use the following on a model that acts_as_follower
+
+ current_user.all_follows # Returns an array of every follow for the current_user
+
+- To get all follows by a certain type use the following on a model that acts_as_follower
+
+ current_user.follows_by_type('Book') # Returns an array of all follows for current user where followable_type is 'Book'
+
+- To get the total number (count) of follows for a user use the following on a model that acts_as_follower
+
+ current_user.follow_count # Returns and integer
+
+
+# ActsAsFollowable Methods
+
+- To get all the followers of a model that acts_as_followable
+
+ book = Book.find(1)
+ book.followers # Returns an array of all the followers for that book
+
+- To get just the number of follows use
+
+ book.follows_count
+
+- To see is a model that acts_as_followable is followed by a model that acts_as_follower use the following
+
+ book.followed_by?(current_user) # Returns true or false depending on if current_user is following book
+
+
+Comments
+=======
+
+If anyone has comments or questions please let me know (tom dot cocca at gmail dot com)
+If you have updates or patches or want to contribute I would love to see what you have or want to add
+
+
+TODO
+=======
+
+* Testing
+* Add Example Usage code to the plugin
+* Possibly add hooks for model observer code or after_save or after_update code
+ - Any ideas on this are more than welcome, just email me.
+
+Copyright (c) 2008 Tom Cocca ( tom dot cocca at gmail dot com ), released under the MIT license
@@ -0,0 +1,5 @@
+Description:
+ run ./script/generate acts_as_follower
+
+ no need to specify a name after acts_as_follower as you can not change the model name from Follow
+ the acts_as_follower_migration file will be created in db/migrate
@@ -0,0 +1,11 @@
+class ActsAsFollowerMigrationGenerator < Rails::Generator::Base
+ def manifest
+ record do |m|
+ m.migration_template 'migration.rb', 'db/migrate'
+ end
+ end
+
+ def file_name
+ "acts_as_follower_migration"
+ end
+end
@@ -0,0 +1,16 @@
+class ActsAsFollowerMigration < ActiveRecord::Migration
+ def self.up
+ create_table :follows, :force => true do |t|
+ t.references :followable, :polymorphic => true, :null => false
+ t.references :follower, :polymorphic => true, :null => false
+ t.timestamps
+ end
+
+ add_index :follows, ["follower_id", "follower_type"], :name => "fk_follows"
+ add_index :follows, ["followable_id", "followable_type"], :name => "fk_followables"
+ end
+
+ def self.down
+ drop_table :follows
+ end
+end
@@ -0,0 +1,6 @@
+require 'acts_as_follower'
+require 'acts_as_followable'
+require 'follow.rb'
+
+ActiveRecord::Base.send(:include, ActiveRecord::Acts::Follower)
+ActiveRecord::Base.send(:include, ActiveRecord::Acts::Followable)
@@ -0,0 +1 @@
+# Install hook code here
@@ -0,0 +1,45 @@
+module ActiveRecord
+ module Acts
+ module Followable
+
+ def self.included(base)
+ base.extend ClassMethods
+ end
+
+ module ClassMethods
+ def acts_as_followable
+ has_many :follows, :as => :followable, :dependent => :nullify
+ include ActiveRecord::Acts::Followable::InstanceMethods
+ extend ActiveRecord::Acts::Followable::SingletonMethods
+ end
+ end
+
+ # This module contains class methods
+ module SingletonMethods
+
+ end
+
+ # This module contains instance methods
+ module InstanceMethods
+
+ def followers_count
+ self.follows.size
+ end
+
+ def followers
+ self.follows.collect{ |f| f.follower }
+ end
+
+ def followed_by?(follwer)
+ rtn = false
+ self.follows.each do |f|
+ rtn = true if follower.id == f.follower.id && follower.class.name == f.follwer_type
+ end
+ rtn
+ end
+
+ end
+
+ end
+ end
+end
@@ -0,0 +1,65 @@
+module ActiveRecord
+ module Acts
+ module Follower
+
+ def self.included(base)
+ base.extend ClassMethods
+ end
+
+ module ClassMethods
+ def acts_as_follower
+ has_many :follows, :as => :follower, :dependent => :nullify # If a following entity is deleted, keep the follows.
+ include ActiveRecord::Acts::Follower::InstanceMethods
+ extend ActiveRecord::Acts::Follower::SingletonMethods
+ end
+ end
+
+ # This module contains class methods
+ module SingletonMethods
+
+ end
+
+ # This module contains instance methods
+ module InstanceMethods
+
+ def following?(followable)
+ 0 < Follow.count(:all, :conditions => [
+ "follower_id = ? AND follower_type = ? AND followable_id = ? AND gollowable_type = ?",
+ self.id, self.class.name, followable.id, followable.class.name
+ ])
+ end
+
+ def follow_count
+ Follow.count(:all, :conditions => ["follower_id = ? AND follower_type = ?", self.id, self.class.name])
+ end
+
+ def follow(followable)
+ Follow.create(:followable => followable, :follower => self)
+ end
+
+ def stop_following(followable)
+ follow = get_follow(followable)
+ if follow
+ follow.destroy
+ end
+ end
+
+ def follows_by_type(followable_type)
+ Follow.find(:all, :conditions => ["follower_id = ? AND follower_type = ? AND followable_type = ?", self.id, self.class.name, followable_type])
+ end
+
+ def all_follows
+ Follow.find(:all, :conditions => ["follower_id = ? AND follower_type = ?", self.id, self.class.name])
+ end
+
+ private
+
+ def get_follow(followable)
+ Follow.find(:first, :conditions => ["follower_id = ? AND follower_type = ? AND followable_id = ? AND followable_type = ?", self.id, self.class.name, followable.id, followable.class.name])
+ end
+
+ end
+
+ end
+ end
+end
@@ -0,0 +1,12 @@
+class Follow < ActiveRecord::Base
+
+ named_scope :for_follower, lambda { |*args| {:conditions => ["follower_id = ? AND follower_type = ?", args.first.id, args.first.type.name]} }
+ named_scope :for_followable, lambda { |*args| {:conditions => ["followable_id = ? AND followable_type = ?", args.first.id, args.first.type.name]} }
+ named_scope :recent, lambda { |*args| {:conditions => ["created_at > ?", (args.first || 2.weeks.ago).to_s(:db)]} }
+ named_scope :descending, :order => "created_at DESC"
+
+ # NOTE: Follows belong to the "followable" interface, and also to followers
+ belongs_to :followable, :polymorphic => true
+ belongs_to :follower, :polymorphic => true
+
+end
@@ -0,0 +1,8 @@
+require 'test/unit'
+
+class ActsAsFollowerTest < Test::Unit::TestCase
+ # Replace this with your real tests.
+ def test_this_plugin
+ flunk
+ end
+end

2 comments on commit cdd7b1e

There is a type on line 36 of lib/acts_as_followable.rb

Owner

tcocca replied Oct 20, 2008

Fixed the typo
Thanks

Please sign in to comment.