acts_as_follower1 1.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.travis.yml +8 -0
  4. data/Gemfile +6 -0
  5. data/MIT-LICENSE +44 -0
  6. data/README.rdoc +247 -0
  7. data/Rakefile +37 -0
  8. data/acts_as_follower.gemspec +29 -0
  9. data/init.rb +1 -0
  10. data/lib/acts_as_follower/follow_scopes.rb +45 -0
  11. data/lib/acts_as_follower/followable.rb +113 -0
  12. data/lib/acts_as_follower/follower.rb +112 -0
  13. data/lib/acts_as_follower/follower_lib.rb +42 -0
  14. data/lib/acts_as_follower/railtie.rb +14 -0
  15. data/lib/acts_as_follower/version.rb +3 -0
  16. data/lib/acts_as_follower1.rb +33 -0
  17. data/lib/generators/USAGE +5 -0
  18. data/lib/generators/acts_as_follower_generator.rb +30 -0
  19. data/lib/generators/templates/migration.rb +17 -0
  20. data/lib/generators/templates/model.rb +14 -0
  21. data/test/README +24 -0
  22. data/test/acts_as_followable_test.rb +283 -0
  23. data/test/acts_as_follower_test.rb +224 -0
  24. data/test/dummy30/Gemfile +1 -0
  25. data/test/dummy30/Rakefile +7 -0
  26. data/test/dummy30/app/models/application_record.rb +3 -0
  27. data/test/dummy30/app/models/band.rb +4 -0
  28. data/test/dummy30/app/models/band/punk.rb +4 -0
  29. data/test/dummy30/app/models/band/punk/pop_punk.rb +4 -0
  30. data/test/dummy30/app/models/custom_record.rb +3 -0
  31. data/test/dummy30/app/models/some.rb +5 -0
  32. data/test/dummy30/app/models/user.rb +5 -0
  33. data/test/dummy30/config.ru +4 -0
  34. data/test/dummy30/config/application.rb +42 -0
  35. data/test/dummy30/config/boot.rb +10 -0
  36. data/test/dummy30/config/database.yml +7 -0
  37. data/test/dummy30/config/environment.rb +5 -0
  38. data/test/dummy30/config/environments/development.rb +19 -0
  39. data/test/dummy30/config/environments/test.rb +20 -0
  40. data/test/dummy30/config/initializers/backtrace_silencers.rb +7 -0
  41. data/test/dummy30/config/initializers/inflections.rb +10 -0
  42. data/test/dummy30/config/initializers/secret_token.rb +7 -0
  43. data/test/dummy30/config/initializers/session_store.rb +8 -0
  44. data/test/dummy30/config/locales/en.yml +5 -0
  45. data/test/dummy30/config/routes.rb +2 -0
  46. data/test/factories/bands.rb +17 -0
  47. data/test/factories/somes.rb +9 -0
  48. data/test/factories/users.rb +13 -0
  49. data/test/follow_test.rb +28 -0
  50. data/test/schema.rb +25 -0
  51. data/test/test_helper.rb +18 -0
  52. metadata +213 -0
@@ -0,0 +1,113 @@
1
+ module ActsAsFollower #:nodoc:
2
+ module Followable
3
+
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ def acts_as_followable
10
+ has_many :followings, as: :followable, dependent: :destroy, class_name: 'Follow'
11
+ include ActsAsFollower::Followable::InstanceMethods
12
+ include ActsAsFollower::FollowerLib
13
+ end
14
+ end
15
+
16
+ module InstanceMethods
17
+
18
+ # Returns the number of followers a record has.
19
+ def followers_count
20
+ self.followings.unblocked.count
21
+ end
22
+
23
+ # Returns the followers by a given type
24
+ def followers_by_type(follower_type, options={})
25
+ follows = follower_type.constantize.
26
+ joins(:follows).
27
+ where('follows.blocked' => false,
28
+ 'follows.followable_id' => self.id,
29
+ 'follows.followable_type' => parent_class_name(self),
30
+ 'follows.follower_type' => follower_type)
31
+ if options.has_key?(:limit)
32
+ follows = follows.limit(options[:limit])
33
+ end
34
+ if options.has_key?(:includes)
35
+ follows = follows.includes(options[:includes])
36
+ end
37
+ follows
38
+ end
39
+
40
+ def followers_by_type_count(follower_type)
41
+ self.followings.unblocked.for_follower_type(follower_type).count
42
+ end
43
+
44
+ # Allows magic names on followers_by_type
45
+ # e.g. user_followers == followers_by_type('User')
46
+ # Allows magic names on followers_by_type_count
47
+ # e.g. count_user_followers == followers_by_type_count('User')
48
+ def method_missing(m, *args)
49
+ if m.to_s[/count_(.+)_followers/]
50
+ followers_by_type_count($1.singularize.classify)
51
+ elsif m.to_s[/(.+)_followers/]
52
+ followers_by_type($1.singularize.classify)
53
+ else
54
+ super
55
+ end
56
+ end
57
+
58
+ def respond_to?(m, include_private = false)
59
+ super || m.to_s[/count_(.+)_followers/] || m.to_s[/(.+)_followers/]
60
+ end
61
+
62
+ def blocked_followers_count
63
+ self.followings.blocked.count
64
+ end
65
+
66
+ # Returns the followings records scoped
67
+ def followers_scoped
68
+ self.followings.includes(:follower)
69
+ end
70
+
71
+ def followers(options={})
72
+ followers_scope = followers_scoped.unblocked
73
+ followers_scope = apply_options_to_scope(followers_scope, options)
74
+ followers_scope.to_a.collect{|f| f.follower}
75
+ end
76
+
77
+ def blocks(options={})
78
+ blocked_followers_scope = followers_scoped.blocked
79
+ blocked_followers_scope = apply_options_to_scope(blocked_followers_scope, options)
80
+ blocked_followers_scope.to_a.collect{|f| f.follower}
81
+ end
82
+
83
+ # Returns true if the current instance is followed by the passed record
84
+ # Returns false if the current instance is blocked by the passed record or no follow is found
85
+ def followed_by?(follower)
86
+ self.followings.unblocked.for_follower(follower).first.present?
87
+ end
88
+
89
+ def block(follower)
90
+ get_follow_for(follower) ? block_existing_follow(follower) : block_future_follow(follower)
91
+ end
92
+
93
+ def unblock(follower)
94
+ get_follow_for(follower).try(:delete)
95
+ end
96
+
97
+ def get_follow_for(follower)
98
+ self.followings.for_follower(follower).first
99
+ end
100
+
101
+ private
102
+
103
+ def block_future_follow(follower)
104
+ Follow.create(followable: self, follower: follower, blocked: true)
105
+ end
106
+
107
+ def block_existing_follow(follower)
108
+ get_follow_for(follower).block!
109
+ end
110
+
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,112 @@
1
+ module ActsAsFollower #:nodoc:
2
+ module Follower
3
+
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ def acts_as_follower
10
+ has_many :follows, as: :follower, dependent: :destroy
11
+ include ActsAsFollower::Follower::InstanceMethods
12
+ include ActsAsFollower::FollowerLib
13
+ end
14
+ end
15
+
16
+ module InstanceMethods
17
+
18
+ # Returns true if this instance is following the object passed as an argument.
19
+ def following?(followable)
20
+ 0 < Follow.unblocked.for_follower(self).for_followable(followable).count
21
+ end
22
+
23
+ # Returns the number of objects this instance is following.
24
+ def follow_count
25
+ Follow.unblocked.for_follower(self).count
26
+ end
27
+
28
+ # Creates a new follow record for this instance to follow the passed object.
29
+ # Does not allow duplicate records to be created.
30
+ def follow(followable)
31
+ if self != followable
32
+ params = {followable_id: followable.id, followable_type: parent_class_name(followable)}
33
+ self.follows.where(params).first_or_create!
34
+ end
35
+ end
36
+
37
+ # Deletes the follow record if it exists.
38
+ def stop_following(followable)
39
+ if follow = get_follow(followable)
40
+ follow.destroy
41
+ end
42
+ end
43
+
44
+ # returns the follows records to the current instance
45
+ def follows_scoped
46
+ self.follows.unblocked.includes(:followable)
47
+ end
48
+
49
+ # Returns the follow records related to this instance by type.
50
+ def follows_by_type(followable_type, options={})
51
+ follows_scope = follows_scoped.for_followable_type(followable_type)
52
+ follows_scope = apply_options_to_scope(follows_scope, options)
53
+ end
54
+
55
+ # Returns the follow records related to this instance with the followable included.
56
+ def all_follows(options={})
57
+ follows_scope = follows_scoped
58
+ follows_scope = apply_options_to_scope(follows_scope, options)
59
+ end
60
+
61
+ # Returns the actual records which this instance is following.
62
+ def all_following(options={})
63
+ all_follows(options).collect{ |f| f.followable }
64
+ end
65
+
66
+ # Returns the actual records of a particular type which this record is following.
67
+ def following_by_type(followable_type, options={})
68
+ followables = followable_type.constantize.
69
+ joins(:followings).
70
+ where('follows.blocked' => false,
71
+ 'follows.follower_id' => self.id,
72
+ 'follows.follower_type' => parent_class_name(self),
73
+ 'follows.followable_type' => followable_type)
74
+ if options.has_key?(:limit)
75
+ followables = followables.limit(options[:limit])
76
+ end
77
+ if options.has_key?(:includes)
78
+ followables = followables.includes(options[:includes])
79
+ end
80
+ followables
81
+ end
82
+
83
+ def following_by_type_count(followable_type)
84
+ follows.unblocked.for_followable_type(followable_type).count
85
+ end
86
+
87
+ # Allows magic names on following_by_type
88
+ # e.g. following_users == following_by_type('User')
89
+ # Allows magic names on following_by_type_count
90
+ # e.g. following_users_count == following_by_type_count('User')
91
+ def method_missing(m, *args)
92
+ if m.to_s[/following_(.+)_count/]
93
+ following_by_type_count($1.singularize.classify)
94
+ elsif m.to_s[/following_(.+)/]
95
+ following_by_type($1.singularize.classify)
96
+ else
97
+ super
98
+ end
99
+ end
100
+
101
+ def respond_to?(m, include_private = false)
102
+ super || m.to_s[/following_(.+)_count/] || m.to_s[/following_(.+)/]
103
+ end
104
+
105
+ # Returns a follow record for the current instance and followable object.
106
+ def get_follow(followable)
107
+ self.follows.unblocked.for_followable(followable).first
108
+ end
109
+
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,42 @@
1
+ module ActsAsFollower
2
+ module FollowerLib
3
+
4
+ private
5
+
6
+ DEFAULT_PARENTS = [ApplicationRecord, ActiveRecord::Base]
7
+
8
+ # Retrieves the parent class name if using STI.
9
+ def parent_class_name(obj)
10
+ unless parent_classes.include?(obj.class.superclass)
11
+ return obj.class.base_class.name
12
+ end
13
+ obj.class.name
14
+ end
15
+
16
+ def apply_options_to_scope(scope, options = {})
17
+ if options.has_key?(:limit)
18
+ scope = scope.limit(options[:limit])
19
+ end
20
+ if options.has_key?(:includes)
21
+ scope = scope.includes(options[:includes])
22
+ end
23
+ if options.has_key?(:joins)
24
+ scope = scope.joins(options[:joins])
25
+ end
26
+ if options.has_key?(:where)
27
+ scope = scope.where(options[:where])
28
+ end
29
+ if options.has_key?(:order)
30
+ scope = scope.order(options[:order])
31
+ end
32
+ scope
33
+ end
34
+
35
+ def parent_classes
36
+ return DEFAULT_PARENTS unless ActsAsFollower.custom_parent_classes
37
+
38
+ ActiveSupport::Deprecation.warn("Setting custom parent classes is deprecated and will be removed in future versions.")
39
+ ActsAsFollower.custom_parent_classes + DEFAULT_PARENTS
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,14 @@
1
+ require 'rails'
2
+
3
+ module ActsAsFollower
4
+ class Railtie < Rails::Railtie
5
+
6
+ initializer "acts_as_follower.active_record" do |app|
7
+ ActiveSupport.on_load :active_record do
8
+ include ActsAsFollower::Follower
9
+ include ActsAsFollower::Followable
10
+ end
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,3 @@
1
+ module ActsAsFollower
2
+ VERSION = "1.0.9"
3
+ end
@@ -0,0 +1,33 @@
1
+ require "acts_as_follower/version"
2
+
3
+ module ActsAsFollower
4
+ autoload :Follower, 'acts_as_follower/follower'
5
+ autoload :Followable, 'acts_as_follower/followable'
6
+ autoload :FollowerLib, 'acts_as_follower/follower_lib'
7
+ autoload :FollowScopes, 'acts_as_follower/follow_scopes'
8
+
9
+ def self.setup
10
+ @configuration ||= Configuration.new
11
+ yield @configuration if block_given?
12
+ end
13
+
14
+ def self.method_missing(method_name, *args, &block)
15
+ if method_name == :custom_parent_classes=
16
+ ActiveSupport::Deprecation.warn("Setting custom parent classes is deprecated and will be removed in future versions.")
17
+ end
18
+ @configuration.respond_to?(method_name) ?
19
+ @configuration.send(method_name, *args, &block) : super
20
+ end
21
+
22
+ class Configuration
23
+ attr_accessor :custom_parent_classes
24
+
25
+ def initialize
26
+ @custom_parent_classes = []
27
+ end
28
+ end
29
+
30
+ setup
31
+
32
+ require 'acts_as_follower/railtie' if defined?(Rails) && Rails::VERSION::MAJOR >= 3
33
+ end
@@ -0,0 +1,5 @@
1
+ Description:
2
+ rails generate acts_as_follower
3
+
4
+ no need to specify a name after acts_as_follower as you can not change the model name from Follow
5
+ the acts_as_follower_migration file will be created in db/migrate
@@ -0,0 +1,30 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ class ActsAsFollowerGenerator < Rails::Generators::Base
5
+
6
+ include Rails::Generators::Migration
7
+
8
+ def self.source_root
9
+ @source_root ||= File.join(File.dirname(__FILE__), 'templates')
10
+ end
11
+
12
+ # Implement the required interface for Rails::Generators::Migration.
13
+ # taken from https://github.com/rails/rails/blob/master/activerecord/lib/rails/generators/active_record.rb
14
+ def self.next_migration_number(dirname)
15
+ if ActiveRecord::Base.timestamped_migrations
16
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
17
+ else
18
+ "%.3d" % (current_migration_number(dirname) + 1)
19
+ end
20
+ end
21
+
22
+ def create_migration_file
23
+ migration_template 'migration.rb', 'db/migrate/acts_as_follower_migration.rb'
24
+ end
25
+
26
+ def create_model
27
+ template "model.rb", File.join('app/models', "follow.rb")
28
+ end
29
+
30
+ end
@@ -0,0 +1,17 @@
1
+ class ActsAsFollowerMigration < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :follows, force: true do |t|
4
+ t.references :followable, polymorphic: true, null: false
5
+ t.references :follower, polymorphic: true, null: false
6
+ t.boolean :blocked, default: false, null: false
7
+ t.timestamps
8
+ end
9
+
10
+ add_index :follows, ["follower_id", "follower_type"], name: "fk_follows"
11
+ add_index :follows, ["followable_id", "followable_type"], name: "fk_followables"
12
+ end
13
+
14
+ def self.down
15
+ drop_table :follows
16
+ end
17
+ end
@@ -0,0 +1,14 @@
1
+ class Follow < ActiveRecord::Base
2
+
3
+ extend ActsAsFollower::FollowerLib
4
+ extend ActsAsFollower::FollowScopes
5
+
6
+ # NOTE: Follows belong to the "followable" and "follower" interface
7
+ belongs_to :followable, polymorphic: true
8
+ belongs_to :follower, polymorphic: true
9
+
10
+ def block!
11
+ self.update_attribute(:blocked, true)
12
+ end
13
+
14
+ end
data/test/README ADDED
@@ -0,0 +1,24 @@
1
+ Testing
2
+ =======
3
+
4
+ Tests are written with Shoulda on top of Test::Unit and Factory Girl is used instead of fixtures. Tests are run using rake.
5
+
6
+ 1. Clone your fork git locally.
7
+ 2. Install the dependencies
8
+ $ bundle install
9
+ 3. Run the tests:
10
+ rake test
11
+
12
+
13
+ Coverage
14
+ =======
15
+
16
+ Test coverage can be calculated using Rcov. Make sure you have the rcov gem installed.
17
+
18
+ Again in the acts_as_follower directory:
19
+
20
+ rake rcov:gen DB=sqlite3 # For sqlite
21
+
22
+ The coverage will now be available in the test/coverage directory.
23
+
24
+ rake rcov:clobber will delete the coverage directory.
@@ -0,0 +1,283 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class ActsAsFollowableTest < ActiveSupport::TestCase
4
+
5
+ context "instance methods" do
6
+ setup do
7
+ @sam = FactoryGirl.create(:sam)
8
+ end
9
+
10
+ should "be defined" do
11
+ assert @sam.respond_to?(:followers_count)
12
+ assert @sam.respond_to?(:followers)
13
+ assert @sam.respond_to?(:followed_by?)
14
+ end
15
+ end
16
+
17
+ context "acts_as_followable" do
18
+ setup do
19
+ @sam = FactoryGirl.create(:sam)
20
+ @jon = FactoryGirl.create(:jon)
21
+ @oasis = FactoryGirl.create(:oasis)
22
+ @metallica = FactoryGirl.create(:metallica)
23
+ @green_day = FactoryGirl.create(:green_day)
24
+ @blink_182 = FactoryGirl.create(:blink_182)
25
+ @sam.follow(@jon)
26
+ end
27
+
28
+ context "followers_count" do
29
+ should "return the number of followers" do
30
+ assert_equal 0, @sam.followers_count
31
+ assert_equal 1, @jon.followers_count
32
+ end
33
+
34
+ should "return the proper number of multiple followers" do
35
+ @bob = FactoryGirl.create(:bob)
36
+ @sam.follow(@bob)
37
+ assert_equal 0, @sam.followers_count
38
+ assert_equal 1, @jon.followers_count
39
+ assert_equal 1, @bob.followers_count
40
+ end
41
+ end
42
+
43
+ context "followers" do
44
+ should "return users" do
45
+ assert_equal [], @sam.followers
46
+ assert_equal [@sam], @jon.followers
47
+ end
48
+
49
+ should "return users (multiple followers)" do
50
+ @bob = FactoryGirl.create(:bob)
51
+ @sam.follow(@bob)
52
+ assert_equal [], @sam.followers
53
+ assert_equal [@sam], @jon.followers
54
+ assert_equal [@sam], @bob.followers
55
+ end
56
+
57
+ should "return users (multiple followers, complex)" do
58
+ @bob = FactoryGirl.create(:bob)
59
+ @sam.follow(@bob)
60
+ @jon.follow(@bob)
61
+ assert_equal [], @sam.followers
62
+ assert_equal [@sam], @jon.followers
63
+ assert_equal [@sam, @jon], @bob.followers
64
+ end
65
+
66
+ should "accept AR options" do
67
+ @bob = FactoryGirl.create(:bob)
68
+ @bob.follow(@jon)
69
+ assert_equal 1, @jon.followers(limit: 1).count
70
+ end
71
+ end
72
+
73
+ context "followed_by" do
74
+ should "return_follower_status" do
75
+ assert_equal true, @jon.followed_by?(@sam)
76
+ assert_equal false, @sam.followed_by?(@jon)
77
+ end
78
+ end
79
+
80
+ context "destroying a followable" do
81
+ setup do
82
+ @jon.destroy
83
+ end
84
+
85
+ should_change("follow count", by: -1) { Follow.count }
86
+ should_change("@sam.all_following.size", by: -1) { @sam.all_following.size }
87
+ end
88
+
89
+ context "get follow record" do
90
+ setup do
91
+ @bob = FactoryGirl.create(:bob)
92
+ @follow = @bob.follow(@sam)
93
+ end
94
+
95
+ should "return follow record" do
96
+ assert_equal @follow, @sam.get_follow_for(@bob)
97
+ end
98
+
99
+ should "return nil" do
100
+ assert_nil @sam.get_follow_for(@jon)
101
+ end
102
+ end
103
+
104
+ context "blocks" do
105
+ setup do
106
+ @bob = FactoryGirl.create(:bob)
107
+ @jon.block(@sam)
108
+ @jon.block(@bob)
109
+ end
110
+
111
+ should "accept AR options" do
112
+ assert_equal 1, @jon.blocks(limit: 1).count
113
+ end
114
+ end
115
+
116
+ context "blocking a follower" do
117
+ context "in my following list" do
118
+ setup do
119
+ @jon.block(@sam)
120
+ end
121
+
122
+ should "remove him from followers" do
123
+ assert_equal 0, @jon.followers_count
124
+ end
125
+
126
+ should "add him to the blocked followers" do
127
+ assert_equal 1, @jon.blocked_followers_count
128
+ end
129
+
130
+ should "not be able to follow again" do
131
+ @jon.follow(@sam)
132
+ assert_equal 0, @jon.followers_count
133
+ end
134
+
135
+ should "not be present when listing followers" do
136
+ assert_equal [], @jon.followers
137
+ end
138
+
139
+ should "be in the list of blocks" do
140
+ assert_equal [@sam], @jon.blocks
141
+ end
142
+ end
143
+
144
+ context "not in my following list" do
145
+ setup do
146
+ @sam.block(@jon)
147
+ end
148
+
149
+ should "add him to the blocked followers" do
150
+ assert_equal 1, @sam.blocked_followers_count
151
+ end
152
+
153
+ should "not be able to follow again" do
154
+ @sam.follow(@jon)
155
+ assert_equal 0, @sam.followers_count
156
+ end
157
+
158
+ should "not be present when listing followers" do
159
+ assert_equal [], @sam.followers
160
+ end
161
+
162
+ should "be in the list of blocks" do
163
+ assert_equal [@jon], @sam.blocks
164
+ end
165
+ end
166
+ end
167
+
168
+ context "unblocking a blocked follow" do
169
+ setup do
170
+ @jon.block(@sam)
171
+ @jon.unblock(@sam)
172
+ end
173
+
174
+ should "not include the unblocked user in the list of followers" do
175
+ assert_equal [], @jon.followers
176
+ end
177
+
178
+ should "remove him from the blocked followers" do
179
+ assert_equal 0, @jon.blocked_followers_count
180
+ assert_equal [], @jon.blocks
181
+ end
182
+ end
183
+
184
+ context "unblock a non-existent follow" do
185
+ setup do
186
+ @sam.stop_following(@jon)
187
+ @jon.unblock(@sam)
188
+ end
189
+
190
+ should "not be in the list of followers" do
191
+ assert_equal [], @jon.followers
192
+ end
193
+
194
+ should "not be in the blocked followers count" do
195
+ assert_equal 0, @jon.blocked_followers_count
196
+ end
197
+
198
+ should "not be in the blocks list" do
199
+ assert_equal [], @jon.blocks
200
+ end
201
+ end
202
+
203
+ context "followers_by_type" do
204
+ setup do
205
+ @sam.follow(@oasis)
206
+ @jon.follow(@oasis)
207
+ end
208
+
209
+ should "return the followers for given type" do
210
+ assert_equal [@sam], @jon.followers_by_type('User')
211
+ assert_equal [@sam, @jon], @oasis.followers_by_type('User')
212
+ end
213
+
214
+ should "not return block followers in the followers for a given type" do
215
+ @oasis.block(@jon)
216
+ assert_equal [@sam], @oasis.followers_by_type('User')
217
+ end
218
+
219
+ should "return the count for followers_by_type_count for a given type" do
220
+ assert_equal 1, @jon.followers_by_type_count('User')
221
+ assert_equal 2, @oasis.followers_by_type_count('User')
222
+ end
223
+
224
+ should "not count blocked follows in the count" do
225
+ @oasis.block(@sam)
226
+ assert_equal 1, @oasis.followers_by_type_count('User')
227
+ end
228
+ end
229
+
230
+ context "followers_with_sti" do
231
+ setup do
232
+ @sam.follow(@green_day)
233
+ @sam.follow(@blink_182)
234
+ end
235
+
236
+ should "return the followers for given type" do
237
+ assert_equal @sam.follows_by_type('Band').first.followable, @green_day.becomes(Band)
238
+ assert_equal @sam.follows_by_type('Band').second.followable, @blink_182.becomes(Band)
239
+ assert @green_day.followers_by_type('User').include?(@sam)
240
+ assert @blink_182.followers_by_type('User').include?(@sam)
241
+ end
242
+ end
243
+
244
+ context "method_missing" do
245
+ setup do
246
+ @sam.follow(@oasis)
247
+ @jon.follow(@oasis)
248
+ end
249
+
250
+ should "return the followers for given type" do
251
+ assert_equal [@sam], @jon.user_followers
252
+ assert_equal [@sam, @jon], @oasis.user_followers
253
+ end
254
+
255
+ should "not return block followers in the followers for a given type" do
256
+ @oasis.block(@jon)
257
+ assert_equal [@sam], @oasis.user_followers
258
+ end
259
+
260
+ should "return the count for followers_by_type_count for a given type" do
261
+ assert_equal 1, @jon.count_user_followers
262
+ assert_equal 2, @oasis.count_user_followers
263
+ end
264
+
265
+ should "not count blocked follows in the count" do
266
+ @oasis.block(@sam)
267
+ assert_equal 1, @oasis.count_user_followers
268
+ end
269
+ end
270
+
271
+ context "respond_to?" do
272
+ should "advertise that it responds to following methods" do
273
+ assert @oasis.respond_to?(:user_followers)
274
+ assert @oasis.respond_to?(:user_followers_count)
275
+ end
276
+
277
+ should "return false when called with a nonexistent method" do
278
+ assert (not @oasis.respond_to?(:foobar))
279
+ end
280
+ end
281
+
282
+ end
283
+ end