party_boy 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/.gitignore +23 -0
- data/LICENSE +20 -0
- data/README.rdoc +89 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/generators/party_boy/party_boy_generator.rb +14 -0
- data/generators/party_boy/templates/migration.rb +14 -0
- data/generators/party_boy/templates/model.rb +30 -0
- data/lib/party_boy.rb +225 -0
- data/social_lite.gemspec +63 -0
- data/spec/models/follower_class.rb +5 -0
- data/spec/models/friend_class.rb +5 -0
- data/spec/party_boy_spec.rb +160 -0
- data/spec/schema.rb +19 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +21 -0
- metadata +85 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Mike Nelson
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
= party_boy
|
2
|
+
|
3
|
+
Models relationships between AR models. Allows you to follow, friend, and block other AR's. Consists of two mixins: acts_as_followable and acts_as_friend. These options allow an AR to inherit either a twitter-like follower system or a facebook-like friend system.
|
4
|
+
|
5
|
+
== Installation
|
6
|
+
|
7
|
+
Install the gem
|
8
|
+
gem install party_boy
|
9
|
+
|
10
|
+
Run the generator
|
11
|
+
script/generate party_boy
|
12
|
+
|
13
|
+
This will generate a migration file as well as the necessary Relationship class in your models folder.
|
14
|
+
|
15
|
+
== Usage
|
16
|
+
|
17
|
+
=== Setup
|
18
|
+
|
19
|
+
Add the appropriate mixin to your models:
|
20
|
+
class Waterboy < ActiveRecord::Base
|
21
|
+
...
|
22
|
+
acts_as_followable
|
23
|
+
...
|
24
|
+
end
|
25
|
+
|
26
|
+
class Quarterback < ActiveRecord::Base
|
27
|
+
...
|
28
|
+
acts_as_friend
|
29
|
+
...
|
30
|
+
end
|
31
|
+
|
32
|
+
---
|
33
|
+
|
34
|
+
=== acts_as_follower
|
35
|
+
|
36
|
+
To allow a model (A) to follow another (B), add acts_as_follow to at least model A. Now, you can follow any other model in your project:
|
37
|
+
a = Waterboy.find 1
|
38
|
+
b = Quarterback.find 2
|
39
|
+
a.follow(b)
|
40
|
+
|
41
|
+
To stop following, simply just:
|
42
|
+
a.unfollow(b)
|
43
|
+
|
44
|
+
Or to block the relationship:
|
45
|
+
b.block(a)
|
46
|
+
|
47
|
+
To find out if there is a relationship between two models, use the methods:
|
48
|
+
a.following?(b)
|
49
|
+
b.followed_by?(a)
|
50
|
+
|
51
|
+
To retrieve a set of models based on the relationships, use:
|
52
|
+
a.following
|
53
|
+
b.followers
|
54
|
+
|
55
|
+
==== STI
|
56
|
+
|
57
|
+
STI is also handled by party_boy. The relationship is always stored using the super-most class. However, relationships to inheriting classes can also be retrieved. Do so by passing in the type(s):
|
58
|
+
|
59
|
+
class Quarterback < User; end
|
60
|
+
class Cheerleader < User; end
|
61
|
+
class Waterboy < User; end
|
62
|
+
|
63
|
+
In string form
|
64
|
+
a.followers('users')
|
65
|
+
a.following(%w(users quarterbacks))
|
66
|
+
|
67
|
+
Or in class form
|
68
|
+
a.followers(User)
|
69
|
+
b.following([User, Quarterback, Cheerleader])
|
70
|
+
|
71
|
+
==== STI - part deux
|
72
|
+
|
73
|
+
On top of accessing relationships through followers / following methods, party_boy will dynamically filter the results based on the combined class name:
|
74
|
+
|
75
|
+
a.quarterback_followers # returns all followers of type quarterback
|
76
|
+
b.cheerleader_followers # returns all followers of type cheerleader
|
77
|
+
a.following_quarterbacks # returns all the quarterbacks 'a' is following
|
78
|
+
b.following_cheerleaders # returns all the cheerleaders 'b' is following
|
79
|
+
|
80
|
+
---
|
81
|
+
|
82
|
+
=== acts_as_friend
|
83
|
+
|
84
|
+
Docs coming soon. I don't need it, do you?
|
85
|
+
|
86
|
+
|
87
|
+
== Copyright
|
88
|
+
|
89
|
+
Copyright (c) 2010 Mike Nelson. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "party_boy"
|
8
|
+
gem.summary = "Models relationships between AR models. Allows you to follow, friend, and block other AR's."
|
9
|
+
gem.description = "Models relationships between AR models. Allows you to follow, friend, and block other AR's. Consists of two acts_as: acts_as_followable and acts_as_friend. These options allow an AR to inherit either a twitter-like follower system or a facebook-like friend system."
|
10
|
+
gem.email = "mdnelson30@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/mnelson/party_boy"
|
12
|
+
gem.authors = ["Mike Nelson"]
|
13
|
+
gem.add_development_dependency "rspec", ">= 1.2.9"
|
14
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'spec/rake/spectask'
|
22
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
23
|
+
spec.libs << 'lib' << 'spec'
|
24
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
25
|
+
end
|
26
|
+
|
27
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
28
|
+
spec.libs << 'lib' << 'spec'
|
29
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
30
|
+
spec.rcov = true
|
31
|
+
end
|
32
|
+
|
33
|
+
task :spec => :check_dependencies
|
34
|
+
|
35
|
+
task :default => :spec
|
36
|
+
|
37
|
+
require 'rake/rdoctask'
|
38
|
+
Rake::RDocTask.new do |rdoc|
|
39
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
40
|
+
|
41
|
+
rdoc.rdoc_dir = 'rdoc'
|
42
|
+
rdoc.title = "party_boy #{version}"
|
43
|
+
rdoc.rdoc_files.include('README*')
|
44
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
45
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class PartyBoyGenerator < Rails::Generator::Base
|
2
|
+
def manifest
|
3
|
+
record do |m|
|
4
|
+
m.directory "app/models"
|
5
|
+
m.template "model.rb", "app/models/relationship.rb"
|
6
|
+
|
7
|
+
m.migration_template 'migration.rb', 'db/migrate'
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def file_name
|
12
|
+
"party_boy_migration"
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class PartyBoyMigration < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :relationships do |t|
|
4
|
+
t.references :requestor, :polymorphic => true, :null => false
|
5
|
+
t.references :requestee, :polymorphic => true, :null => false
|
6
|
+
t.boolean :restricted, :default => true
|
7
|
+
t.timestamps
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.down
|
12
|
+
drop_table :relationships
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class Relationship < ActiveRecord::Base
|
2
|
+
|
3
|
+
named_scope :restricted, :conditions => ['relationships.restricted = ?', true]
|
4
|
+
named_scope :unrestricted, :conditions => ['relationships.restricted = ?', false]
|
5
|
+
|
6
|
+
named_scope :to_type, lambda {|rtype| rtype = [rtype].flatten.compact; rtype.size > 0 && {:conditions => ['relationships.requestee_type IN (?)', [rtype].flatten]} || {}}
|
7
|
+
named_scope :from_type, lambda {|rtype| rtype = [rtype].flatten.compact; rtype.size > 0 && {:conditions => ['relationships.requestor_type IN (?)', [rtype].flatten]} || {}}
|
8
|
+
|
9
|
+
default_scope :order => 'created_at DESC'
|
10
|
+
|
11
|
+
with_options :polymorphic => true do |klazz|
|
12
|
+
klazz.belongs_to :requestor
|
13
|
+
klazz.belongs_to :requestee
|
14
|
+
end
|
15
|
+
|
16
|
+
validates_presence_of :requestor, :requestee
|
17
|
+
|
18
|
+
alias_attribute :blocked, :restricted
|
19
|
+
def accepted?
|
20
|
+
!self.restricted
|
21
|
+
end
|
22
|
+
|
23
|
+
class << self
|
24
|
+
alias_attribute :blocked, :restricted
|
25
|
+
alias_attribute :unblocked, :unrestricted
|
26
|
+
|
27
|
+
alias_attribute :unaccepted, :restricted
|
28
|
+
alias_attribute :accepted, :unrestricted
|
29
|
+
end
|
30
|
+
end
|
data/lib/party_boy.rb
ADDED
@@ -0,0 +1,225 @@
|
|
1
|
+
module Party
|
2
|
+
module Boy
|
3
|
+
|
4
|
+
class IdentityTheftError < StandardError; end
|
5
|
+
class StalkerError < StandardError; end
|
6
|
+
|
7
|
+
def self.included(klazz)
|
8
|
+
klazz.extend(Party::Boy::ClassMethods)
|
9
|
+
klazz.class_eval do
|
10
|
+
include Party::Boy::RelateableInstanceMethods
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
|
16
|
+
def acts_as_followable
|
17
|
+
with_options :class_name => 'Relationship', :dependent => :destroy do |klazz|
|
18
|
+
klazz.has_many :followings, :as => :requestee
|
19
|
+
klazz.has_many :follows, :as => :requestor
|
20
|
+
end
|
21
|
+
|
22
|
+
include Party::Boy::FollowableInstanceMethods
|
23
|
+
end
|
24
|
+
|
25
|
+
def acts_as_friend
|
26
|
+
with_options :class_name => 'Relationship', :dependent => :destroy do |klazz|
|
27
|
+
klazz.has_many :outgoing_friendships, :as => :requestor, :include => :requestee
|
28
|
+
klazz.has_many :incoming_friendships, :as => :requestee, :include => :requestor
|
29
|
+
end
|
30
|
+
|
31
|
+
include Party::Boy::RelateableInstanceMethods
|
32
|
+
include Party::Boy::FriendlyInstanceMethods
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
module RelateableInstanceMethods
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# should be able to pass a class, string, or object and get back the super-most class (before activerecord)
|
43
|
+
def super_class_name(obj = self)
|
44
|
+
obj = (obj.class == Class && obj || obj.class == String && obj.constantize || obj.class)
|
45
|
+
if obj.superclass != ActiveRecord::Base
|
46
|
+
super_class_name(obj.superclass)
|
47
|
+
else
|
48
|
+
obj.name
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def super_class_names(obj = self)
|
53
|
+
if obj.nil?
|
54
|
+
return nil
|
55
|
+
end
|
56
|
+
obj = (obj.class == Class && obj || obj.class == String && obj.constantize || obj.class)
|
57
|
+
if obj.superclass != ActiveRecord::Base
|
58
|
+
[obj.name, super_class_names(obj.superclass)].flatten
|
59
|
+
else
|
60
|
+
[obj.name]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def get_relationship_to(requestee)
|
65
|
+
requestee && Relationship.unblocked.find(:first, :conditions => ['requestor_id = ? and requestor_type = ? and requestee_id = ? and requestee_type = ?', self.id, super_class_name, requestee.id, super_class_name(requestee)]) || nil
|
66
|
+
end
|
67
|
+
|
68
|
+
def get_relationship_from(requestor)
|
69
|
+
requestor && Relationship.unblocked.find(:first, :conditions => ['requestor_id = ? and requestor_type = ? and requestee_id = ? and requestee_type = ?', requestor.id, super_class_name(requestor), self.id, super_class_name]) || nil
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
module FollowableInstanceMethods
|
75
|
+
|
76
|
+
def following?(something)
|
77
|
+
!!(something && Relationship.unblocked.count(:conditions => ['requestor_id = ? and requestor_type = ? and requestee_id = ? and requestee_type = ?', self.id, super_class_name, something.id, super_class_name(something)]) > 0)
|
78
|
+
end
|
79
|
+
|
80
|
+
def followed_by?(something)
|
81
|
+
!!(something && Relationship.unblocked.count(:conditions => ['requestor_id = ? and requestor_type = ? and requestee_id = ? and requestee_type = ?', something.id, super_class_name(something), self.id, super_class_name]) > 0)
|
82
|
+
end
|
83
|
+
|
84
|
+
def follow(something)
|
85
|
+
if blocked_by?(something)
|
86
|
+
raise(Party::Boy::StalkerError, "#{super_class_name} #{self.id} has been blocked by #{super_class_name(something)} #{something.id} but is trying to follow them")
|
87
|
+
else
|
88
|
+
Relationship.create(:requestor => self, :requestee => something, :restricted => false) if !(blocked_by?(something) || following?(something))
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def blocked_by?(something)
|
93
|
+
!!(something && Relationship.blocked.count(:conditions => ['requestor_id = ? and requestor_type = ? and requestee_id = ? and requestee_type = ?', self.id, super_class_name, something.id, super_class_name(something)]) > 0)
|
94
|
+
end
|
95
|
+
|
96
|
+
def unfollow(something)
|
97
|
+
(rel = get_relationship_to(something)) && rel.destroy
|
98
|
+
end
|
99
|
+
|
100
|
+
def block(something)
|
101
|
+
(rel = (get_relationship_from(something) || get_relationship_to(something))) && rel.update_attribute(:restricted, true)
|
102
|
+
end
|
103
|
+
|
104
|
+
def follower_count(type = nil)
|
105
|
+
followings.unblocked.from_type(type).size
|
106
|
+
end
|
107
|
+
|
108
|
+
def following_count(type = nil)
|
109
|
+
follows.unblocked.to_type(type).size
|
110
|
+
end
|
111
|
+
|
112
|
+
def followers(type = nil)
|
113
|
+
type = [type].flatten.compact.uniq
|
114
|
+
super_class = type.last
|
115
|
+
exact_class = type.first
|
116
|
+
results = relationships_from(super_class)
|
117
|
+
if super_class != exact_class
|
118
|
+
results.collect{|r| r.requestor.class.name == exact_class && r.requestor || nil}.compact
|
119
|
+
else
|
120
|
+
results.collect{|r| r.requestor}
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
def following(type = nil)
|
126
|
+
type = [type].flatten.compact.uniq
|
127
|
+
super_class = type.last
|
128
|
+
exact_class = type.first
|
129
|
+
results = relationships_to(super_class)
|
130
|
+
if super_class != exact_class
|
131
|
+
results.collect{|r| r.requestee.class.name == exact_class && r.requestee || nil}.compact
|
132
|
+
else
|
133
|
+
results.collect{|r| r.requestee}
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def extended_network(type = nil)
|
138
|
+
following.collect{|f| f.methods.include?('following') && f.following(type) || []}.flatten.uniq
|
139
|
+
end
|
140
|
+
|
141
|
+
def method_missing(method, *args)
|
142
|
+
case method.id2name
|
143
|
+
when /^(.+)ss_followers$/
|
144
|
+
# this is for the rare case of a class name ending in ss, like 'business'; 'business'.classify => 'Busines'
|
145
|
+
followers(super_class_names("#{$1.classify}ss"))
|
146
|
+
when /^(.+)s_followers$/, /^(.+)_followers$/
|
147
|
+
followers(super_class_names($1.classify))
|
148
|
+
when /^following_(.+)$/
|
149
|
+
following(super_class_names($1.classify))
|
150
|
+
else
|
151
|
+
super
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
def relationships_to(type)
|
158
|
+
self.follows.unblocked.to_type(type).all(:include => [:requestee])
|
159
|
+
end
|
160
|
+
|
161
|
+
def relationships_from(type)
|
162
|
+
self.followings.unblocked.from_type(type).all(:include => [:requestor])
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
module FriendlyInstanceMethods
|
168
|
+
|
169
|
+
def friends
|
170
|
+
(outgoing_friendships.accepted + incoming_friendships.accepted).collect{|r| [r.requestor, r.requestee]}.flatten.uniq - [self]
|
171
|
+
end
|
172
|
+
|
173
|
+
def extended_network
|
174
|
+
friends.collect{|f| f.methods.include?('friends') && f.friends || []}.flatten.uniq - [self]
|
175
|
+
end
|
176
|
+
|
177
|
+
def outgoing_friend_requests
|
178
|
+
self.outgoing_friendships.unaccepted.all
|
179
|
+
end
|
180
|
+
|
181
|
+
def incoming_friend_requests
|
182
|
+
self.incoming_friendships.unaccepted.all
|
183
|
+
end
|
184
|
+
|
185
|
+
def is_friends_with?(something)
|
186
|
+
arr = something && [self.id, super_class_name, super_class_name(something), something.id]
|
187
|
+
arr && Relationship.accepted.count(:conditions => [(['(requestor_id = ? AND requestor_type = ? AND requestee_type = ? AND requestee_id = ?)']*2).join(' OR '), arr, arr.reverse].flatten) > 0
|
188
|
+
end
|
189
|
+
|
190
|
+
def pending?(something)
|
191
|
+
arr = something && [self.id, super_class_name, super_class_name(something), something.id]
|
192
|
+
arr && Relationship.unaccepted.count(:conditions => [(['(requestor_id = ? AND requestor_type = ? AND requestee_type = ? AND requestee_id = ?)']*2).join(' OR '), arr, arr.reverse].flatten) > 0
|
193
|
+
end
|
194
|
+
|
195
|
+
def friend_count
|
196
|
+
arr = [self.id, super_class_name]
|
197
|
+
Relationship.accepted.count(:conditions => ['(requestor_id = ? AND requestor_type = ?) OR (requestee_id = ? and requestee_type = ?)', arr, arr].flatten)
|
198
|
+
end
|
199
|
+
|
200
|
+
def request_friendship(friendship_or_something)
|
201
|
+
rel = relationship_from(friendship_or_something)
|
202
|
+
rel.nil? && Relationship.create(:requestor => self, :requestee => friendship_or_something, :restricted => true) || rel.update_attributes(:restricted => false)
|
203
|
+
end
|
204
|
+
|
205
|
+
def deny_friendship(friendship_or_something)
|
206
|
+
(rel = relationship_from(friendship_or_something)) && rel.destroy
|
207
|
+
end
|
208
|
+
|
209
|
+
alias_method :reject_friendship, :deny_friendship
|
210
|
+
alias_method :accept_friendship, :request_friendship
|
211
|
+
|
212
|
+
private
|
213
|
+
|
214
|
+
def relationship_from(friendship_or_something)
|
215
|
+
if friendship_or_something && friendship_or_something.class == Relationship
|
216
|
+
raise(Party::Boy::IdentityTheftError, "#{self.class.name} with id of #{self.id} tried to access Relationship #{friendship_or_something.id}") if !(friendship_or_something.requestor == self || friendship_or_something.requestee == self)
|
217
|
+
friendship_or_something
|
218
|
+
else
|
219
|
+
arr = friendship_or_something && [self.id, super_class_name, super_class_name(friendship_or_something), friendship_or_something.id]
|
220
|
+
arr && Relationship.find(:first, :conditions => [(['(requestor_id = ? AND requestor_type = ? AND requestee_type = ? AND requestee_id = ?)']*2).join(' OR '), arr, arr.reverse].flatten) || nil
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
data/social_lite.gemspec
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{social_lite}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Mike Nelson"]
|
12
|
+
s.date = %q{2010-01-28}
|
13
|
+
s.description = %q{Models relationships between AR models. Allows you to follow, friend, and block other AR's. Consists of two acts_as: acts_as_followable and acts_as_friend. These options allow an AR to inherit either a twitter-like follower system or a facebook-like friend system.}
|
14
|
+
s.email = %q{mdnelson30@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".gitignore",
|
22
|
+
"LICENSE",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"generators/party_boy/party_boy_generator.rb",
|
27
|
+
"generators/party_boy/templates/migration.rb",
|
28
|
+
"generators/party_boy/templates/model.rb",
|
29
|
+
"lib/party_boy.rb",
|
30
|
+
"spec/models/follower_class.rb",
|
31
|
+
"spec/models/friend_class.rb",
|
32
|
+
"spec/party_boy_spec.rb",
|
33
|
+
"spec/schema.rb",
|
34
|
+
"spec/spec.opts",
|
35
|
+
"spec/spec_helper.rb"
|
36
|
+
]
|
37
|
+
s.homepage = %q{http://github.com/mnelson/social_lite}
|
38
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
39
|
+
s.require_paths = ["lib"]
|
40
|
+
s.rubygems_version = %q{1.3.5}
|
41
|
+
s.summary = %q{Models relationships between AR models. Allows you to follow, friend, and block other AR's.}
|
42
|
+
s.test_files = [
|
43
|
+
"spec/models/follower_class.rb",
|
44
|
+
"spec/models/friend_class.rb",
|
45
|
+
"spec/party_boy_spec.rb",
|
46
|
+
"spec/schema.rb",
|
47
|
+
"spec/spec_helper.rb"
|
48
|
+
]
|
49
|
+
|
50
|
+
if s.respond_to? :specification_version then
|
51
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
52
|
+
s.specification_version = 3
|
53
|
+
|
54
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
55
|
+
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
56
|
+
else
|
57
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
58
|
+
end
|
59
|
+
else
|
60
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
@@ -0,0 +1,160 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
|
4
|
+
describe "PartyBoy" do
|
5
|
+
|
6
|
+
it "should integrate follower methods properly" do
|
7
|
+
%w(followers following following? followed_by?).each do |method|
|
8
|
+
FollowerClass.new.methods.include?(method).should be_true
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should integrate friend methods properly" do
|
13
|
+
%w(friends outgoing_friend_requests incoming_friend_requests extended_network).each do |method|
|
14
|
+
FriendClass.new.methods.include?(method).should be_true
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
it "should validate relationships properly" do
|
20
|
+
a = FollowerClass.create
|
21
|
+
b = FollowerClass.create
|
22
|
+
|
23
|
+
r = Relationship.new(:requestor => a, :requestee => b, :restricted => false)
|
24
|
+
r.should be_valid
|
25
|
+
|
26
|
+
r2 = Relationship.new(:requestor => a, :restricted => false)
|
27
|
+
r2.should_not be_valid
|
28
|
+
|
29
|
+
r3 = Relationship.new(:requestee => b, :restricted => true)
|
30
|
+
r3.should_not be_valid
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "PartyBoy -- Follower" do
|
36
|
+
it "should generate relationships and return proper counts" do
|
37
|
+
a = FollowerClass.create
|
38
|
+
b = FollowerClass.create
|
39
|
+
|
40
|
+
a.follow(b)
|
41
|
+
|
42
|
+
r = Relationship.last
|
43
|
+
|
44
|
+
r.requestor.should eql(a)
|
45
|
+
r.requestee.should eql(b)
|
46
|
+
r.blocked.should be_false
|
47
|
+
|
48
|
+
a.following_count.should eql(1)
|
49
|
+
a.follower_count.should eql(0)
|
50
|
+
|
51
|
+
b.following_count.should eql(0)
|
52
|
+
b.follower_count.should eql(1)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should generate and destroy relationships from party_boy models" do
|
56
|
+
a = FollowerClass.create
|
57
|
+
b = FollowerClass.create
|
58
|
+
|
59
|
+
a.follow(b)
|
60
|
+
|
61
|
+
a.following_count.should eql(1)
|
62
|
+
|
63
|
+
a.unfollow(b)
|
64
|
+
a.following_count.should eql(0)
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should restrict relationships properly" do
|
69
|
+
a = FollowerClass.create
|
70
|
+
b = FollowerClass.create
|
71
|
+
|
72
|
+
r = a.follow(b)
|
73
|
+
|
74
|
+
b.block(a)
|
75
|
+
|
76
|
+
a.following_count.should eql(0)
|
77
|
+
b.follower_count.should eql(0)
|
78
|
+
|
79
|
+
r.reload
|
80
|
+
r.blocked.should be_true
|
81
|
+
|
82
|
+
lambda { a.follow(b) }.should raise_error(Party::Boy::StalkerError)
|
83
|
+
|
84
|
+
a.following_count.should eql(0)
|
85
|
+
b.follower_count.should eql(0)
|
86
|
+
|
87
|
+
b.follow(a)
|
88
|
+
|
89
|
+
a.follower_count.should eql(1)
|
90
|
+
b.following_count.should eql(1)
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
it "should collect relationship personnel properly" do
|
96
|
+
a = FollowerClass.create
|
97
|
+
b = FollowerClass.create
|
98
|
+
c = FollowerClass.create
|
99
|
+
d = FollowerClass.create
|
100
|
+
e = FollowerClass.create
|
101
|
+
|
102
|
+
a.follow(b)
|
103
|
+
b.follow(c)
|
104
|
+
a.follow(c)
|
105
|
+
c.follow(d)
|
106
|
+
e.follow(c)
|
107
|
+
|
108
|
+
a.followers.empty?.should be_true
|
109
|
+
a.following.size.should eql(2)
|
110
|
+
a.following.sort{|m,n| m.id <=> n.id}.should eql([b,c])
|
111
|
+
|
112
|
+
c.followers.size.should eql(3)
|
113
|
+
c.followers.sort{|m,n| m.id <=> n.id}.should eql([a,b,e])
|
114
|
+
|
115
|
+
a.extended_network.include?(d).should be_true
|
116
|
+
a.extended_network.include?(e).should be_false
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
class User < FollowerClass; end
|
121
|
+
class Business < FollowerClass; end
|
122
|
+
|
123
|
+
|
124
|
+
it "should handle STI properly" do
|
125
|
+
u = User.create
|
126
|
+
b = Business.create
|
127
|
+
|
128
|
+
u.follow(b)
|
129
|
+
|
130
|
+
b.followers.first.class.name.should eql('User')
|
131
|
+
u.following.first.class.name.should eql('Business')
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should handle method_missing properly" do
|
135
|
+
u = User.create
|
136
|
+
b = Business.create
|
137
|
+
u2 = User.create
|
138
|
+
b2 = Business.create
|
139
|
+
|
140
|
+
u.follow(u2)
|
141
|
+
u.follow(b)
|
142
|
+
u.follow(b2)
|
143
|
+
|
144
|
+
u.following.size.should eql(3)
|
145
|
+
u.following_businesses.size.should eql(2)
|
146
|
+
u.following_users.size.should eql(1)
|
147
|
+
|
148
|
+
b.follow(u)
|
149
|
+
b.follow(u2)
|
150
|
+
b.follow(b2)
|
151
|
+
|
152
|
+
b2.followers.size.should eql(2)
|
153
|
+
b2.user_followers.size.should eql(1)
|
154
|
+
b2.business_followers.size.should eql(1)
|
155
|
+
b2.follower_class_followers.size.should eql(2)
|
156
|
+
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
|
data/spec/schema.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
ActiveRecord::Schema.define :version => 0 do
|
2
|
+
|
3
|
+
create_table :relationships, :force => true do |t|
|
4
|
+
t.references :requestor, :polymorphic => true, :null => false
|
5
|
+
t.references :requestee, :polymorphic => true, :null => false
|
6
|
+
t.boolean :restricted, :default => true
|
7
|
+
t.timestamps
|
8
|
+
end
|
9
|
+
|
10
|
+
create_table :friend_classes, :force => true do |t|
|
11
|
+
t.timestamps
|
12
|
+
end
|
13
|
+
|
14
|
+
create_table :follower_classes, :force => true do |t|
|
15
|
+
t.string :type
|
16
|
+
t.timestamps
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
|
4
|
+
require 'party_boy'
|
5
|
+
require 'rubygems'
|
6
|
+
require 'spec'
|
7
|
+
require 'spec/autorun'
|
8
|
+
require 'active_record'
|
9
|
+
require "#{File.dirname(__FILE__)}/../generators/party_boy/templates/model"
|
10
|
+
require 'models/follower_class'
|
11
|
+
require 'models/friend_class'
|
12
|
+
|
13
|
+
ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + '/debug.log')
|
14
|
+
ActiveRecord::Base.configurations = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
|
15
|
+
ActiveRecord::Base.establish_connection(ENV['DB'] || 'mysql')
|
16
|
+
|
17
|
+
load(File.dirname(__FILE__) + '/schema.rb')
|
18
|
+
|
19
|
+
Spec::Runner.configure do |config|
|
20
|
+
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: party_boy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mike Nelson
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-01-28 00:00:00 -05:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rspec
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.2.9
|
24
|
+
version:
|
25
|
+
description: "Models relationships between AR models. Allows you to follow, friend, and block other AR's. Consists of two acts_as: acts_as_followable and acts_as_friend. These options allow an AR to inherit either a twitter-like follower system or a facebook-like friend system."
|
26
|
+
email: mdnelson30@gmail.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- LICENSE
|
33
|
+
- README.rdoc
|
34
|
+
files:
|
35
|
+
- .document
|
36
|
+
- .gitignore
|
37
|
+
- LICENSE
|
38
|
+
- README.rdoc
|
39
|
+
- Rakefile
|
40
|
+
- VERSION
|
41
|
+
- generators/party_boy/party_boy_generator.rb
|
42
|
+
- generators/party_boy/templates/migration.rb
|
43
|
+
- generators/party_boy/templates/model.rb
|
44
|
+
- lib/party_boy.rb
|
45
|
+
- social_lite.gemspec
|
46
|
+
- spec/models/follower_class.rb
|
47
|
+
- spec/models/friend_class.rb
|
48
|
+
- spec/party_boy_spec.rb
|
49
|
+
- spec/schema.rb
|
50
|
+
- spec/spec.opts
|
51
|
+
- spec/spec_helper.rb
|
52
|
+
has_rdoc: true
|
53
|
+
homepage: http://github.com/mnelson/party_boy
|
54
|
+
licenses: []
|
55
|
+
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options:
|
58
|
+
- --charset=UTF-8
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: "0"
|
66
|
+
version:
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: "0"
|
72
|
+
version:
|
73
|
+
requirements: []
|
74
|
+
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 1.3.5
|
77
|
+
signing_key:
|
78
|
+
specification_version: 3
|
79
|
+
summary: Models relationships between AR models. Allows you to follow, friend, and block other AR's.
|
80
|
+
test_files:
|
81
|
+
- spec/models/follower_class.rb
|
82
|
+
- spec/models/friend_class.rb
|
83
|
+
- spec/party_boy_spec.rb
|
84
|
+
- spec/schema.rb
|
85
|
+
- spec/spec_helper.rb
|