Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 190 lines (133 sloc) 7.27 kb
63b63e86 »
2008-05-23 initial commit of acts_as_network to git
1 = acts_as_network
2
eeab13f0 »
2008-05-23 updated readme to include instructions for git
3 acts_as_network is intended to simplify the definition
63b63e86 »
2008-05-23 initial commit of acts_as_network to git
4 and storage of reciprocal relationships between entities using
5 ActiveRecord, exposing a "network" of 2-way connections between
6 records. It does this in DRY way using only <b>a single record</b>
7 in a <tt>has_and_belongs_to_many</tt> join table or <tt>has_many :through</tt>
8 join model. Thus, there is no redundancy and you need only one instance of
9 an association or join model to represent both directions of the relationship.
10
11 This is especially useful for social networks where
12 a "friend" relationship in one direction implies the reverse
13 relationship (when Jack is a friend of Jane then Jane should also
14 be a friend of Jack).
15
eeab13f0 »
2008-05-23 updated readme to include instructions for git
16 {Zetetic LLC}[http://www.zetetic.net] extracted acts_as_network from
63b63e86 »
2008-05-23 initial commit of acts_as_network to git
17 PingMe[http://www.gopingme.com] where it drives the social
18 networking features of the site.
19
eeab13f0 »
2008-05-23 updated readme to include instructions for git
20 == INSTALLATION (git on edge rails)
63b63e86 »
2008-05-23 initial commit of acts_as_network to git
21
eeab13f0 »
2008-05-23 updated readme to include instructions for git
22 % cd rails_project_path
23 % ./script/plugin install git://github.com/sjlombardo/acts_as_network.git
63b63e86 »
2008-05-23 initial commit of acts_as_network to git
24 % rake doc:plugins
25
eeab13f0 »
2008-05-23 updated readme to include instructions for git
26 == INSTALLATION (subversion, rails <= 2.0.2))
27
28 % cd rails_project_path
63b63e86 »
2008-05-23 initial commit of acts_as_network to git
29 % script/plugin source http://actsasnetwork.rubyforge.org/svn/plugins
30 % script/plugin install acts_as_network
eeab13f0 »
2008-05-23 updated readme to include instructions for git
31 % rake doc:plugins
32
33 == GitHub
63b63e86 »
2008-05-23 initial commit of acts_as_network to git
34
eeab13f0 »
2008-05-23 updated readme to include instructions for git
35 http://github.com/sjlombardo/acts_as_network/tree/master
63b63e86 »
2008-05-23 initial commit of acts_as_network to git
36
37 = INTRODUCTION
38
39 The usual way of representing network relationships in a database is
40 to use an intermediate, often self-referential, join table (HABTM).
41 For example one might define a simple person type
42
43 create_table :people, :force => true do |t|
44 t.column :name, :string
45 end
46
47 and then a join table to store the friendship relation
48
49 create_table :friends, {:id => false} do |t|
50 t.column :person_id, :integer, :null => false
51 t.column :person_id_friend, :integer, :null => false # target of the relationship
52 end
53
54 Unfortunately this model requires TWO rows in the intermediate table to
55 make a relationship bi-directional
56
57 jane = Person.create(:name => 'Jane')
58 jack = Person.create(:name => 'Jack')
59
60 jane.friends << jack # Jack is Janes friend
61 jane.friends.include?(jack) => true
62
63 Clearly Jack is Jane's friend, yet Jane is *not* Jack's friend
64
65 jack.friends.include?(jane) => false
66
67 unless you need to explicitly define the reverse relation
68
69 jack.friends << jane
70
71 Of course, this isn't horrible, and can in fact be implemented
72 in a fairly DRY way using association callbacks. However, things get
73 more complicated when you consider disassociation (what to do when Jane
74 doesn't want to be friends with Jack any more), or the very common
75 case where you want to express the relationship through a more complicated
76 join model via <tt>has_many :through</tt>
77
78 create_table :invites do |t|
79 t.column :person_id, :integer, :null => false # source of the relationship
80 t.column :person_id_friend, :integer, :null => false # target of the relationship
81 t.column :code, :string # random invitation code
82 t.column :message, :text # invitation message
83 t.column :is_accepted, :boolean
84 t.column :accepted_at, :timestamp # when did they accept?
85 end
86
87 In this case creating a reverse relationship is painful, and depending on
88 validations might require the duplication of multiple values, making the
89 data model decidedly un-DRY.
90
91 == Using acts_as_network
92
93 Acts As Network DRYs things up by representing only a single record
94 in a <tt>has_and_belongs_to_many</tt> join table or <tt>has_many :through</tt>
95 join model. Thus, you only need one instance of an association or join model to
96 represent both directions of the relationship.
97
98 == With HABTM
99
100 For a HABTM style relationship, it's as simple as
101
102 class Person < ActiveRecord::Base
103 acts_as_network :friends, :join_table => :friends
104 end
105
106 In this case <tt>acts_as_network</tt> will expose three new properies
107 on the Person model
108
109 me.friends_out # friends where I have originated the friendship relationship
110 # target in another entry (people I consider friends)
111
112 me.friends_in # friends where a different entry has originated the freindship
113 # with me (people who consider me a friend)
114
115 me.friends # the union of the two sets, that is all people who I consider
116 # friends and all those who consider me a friend
117
118 Thus
119
120 jane = Person.create(:name => 'Jane')
121 jack = Person.create(:name => 'Jack')
122
123 jane.friends_out << jack # Jane adds Jack as a friend
124 jane.friends.include?(jack) => true # Jack is Janes friend
125 jack.friends.include?(jane) => true # Jane is also Jack's friend!
126
127 == With a join model
128
129 This may seem more natural when considering a join style with a proper Invite model. In this case
130 one person will "invite" another person to be friends.
131
132 class Invite < ActiveRecord::Base
133 belongs_to :person
134 belongs_to :person_target, :class_name => 'Person', :foreign_key => 'person_id_target' # the target of the friend relationship
135 validates_presence_of :person, :person_target
136 end
137
138 class Person < ActiveRecord::Base
139 acts_as_network :friends, :through => :invites, :conditions => "is_accepted = 't'"
140 end
141
142 In this case <tt>acts_as_network</tt> implicitly defines five new properies
143 on the Person model
144
145 person.invites_out # has_many invites originating from me to others
146 person.invites_in # has_many invites orginiating from others to me
147 person.friends_out # has_many friends :through outbound accepted invites from me to others
148 person.friends_in # has_many friends :through inbound accepted invites from others to me
149 person.friends # the union of the two friend sets - all people who I have
150 # invited and all the people who have invited me
151
152 Thus
153
154 jane = Person.create(:name => 'Jane')
155 jack = Person.create(:name => 'Jack')
156
157 # Jane invites Jack to be friends
158 invite = Invite.create(:person => jane, :person_target => jack, :message => "let's be friends!")
159
160 jane.friends.include?(jack) => false # Jack is not yet Jane's friend
161 jack.friends.include?(jane) => false # Jane is not yet Jack's friend either
162
163 invite.is_accepted = true # Now Jack accepts the invite
164 invite.save and jane.reload and jack.reload
165
166 jane.friends.include?(jack) => true # Jack is Janes friend now
167 jack.friends.include?(jane) => true # Jane is also Jacks friend
168
169 For more details and specific options see Zetetic::Acts::Network::ClassMethods
170
171 The applications of this plugin to social network situations are fairly obvious,
172 but it should also be usable in the general case to represent inherant
173 bi-directional relationships between entities.
174
175 = TESTS
176
177 The plugin's unit tests are located in +test+ directory under
178 <tt>vendor/plugins/acts_as_network</tt>. Run:
179
180 [%] cd vendor/plugins/acts_as_network
181 [%] ruby test/network_test.rb
182
183 This will create a temporary sqlite3 database, a number of tables,
184 fixture data, and run the tests. You can delete the sqlite database
185 when you are done.
186
187 [%] rm acts_as_network.test.db
188
189 The test suite requires sqlite3.
Something went wrong with that request. Please try again.