Permalink
Browse files

Refactorization allowing users to define relations

* Relations are not reciprocal anymore.
* Elimination of request ties and granted relations
  • Loading branch information...
1 parent 8f9bcd1 commit e0cb8cdcf235df6c5e6ebabeaf1113b765ea957f @atd atd committed Dec 27, 2010
Showing with 440 additions and 501 deletions.
  1. +2 −3 README.rdoc
  2. +17 −3 app/helpers/ties_helper.rb
  3. +10 −1 app/models/activity.rb
  4. +5 −1 app/models/activity_verb.rb
  5. +83 −36 app/models/actor.rb
  6. +1 −1 app/models/group.rb
  7. +34 −17 app/models/relation.rb
  8. +30 −88 app/models/tie.rb
  9. +1 −6 app/models/user.rb
  10. +20 −0 app/views/actors/_contacts.html.erb
  11. +0 −9 app/views/groups/_follow.html.erb
  12. +0 −16 app/views/groups/_followers.html.erb
  13. +3 −4 app/views/groups/_index.html.erb
  14. +7 −4 app/views/groups/_middle_show.html.erb
  15. +4 −2 app/views/groups/_right_show.html.erb
  16. +1 −1 app/views/groups/show.html.erb
  17. +0 −17 app/views/home/_contacts.html.erb
  18. +1 −1 app/views/home/index.html.erb
  19. +4 −5 app/views/ties/_new.html.erb
  20. +1 −1 app/views/ties/_pendings.html.erb
  21. +1 −1 app/views/ties/_tie.html.erb
  22. +1 −1 app/views/ties/new.js.erb
  23. +0 −19 app/views/users/_contacts.html.erb
  24. +5 −5 app/views/users/_groups.html.erb
  25. +3 −1 app/views/users/_index.html.erb
  26. +3 −3 app/views/users/_right_show.html.erb
  27. +5 −3 app/views/users/show.html.erb
  28. +5 −4 config/locales/en.yml
  29. +2 −10 lib/generators/social_stream/install_generator.rb
  30. +0 −6 lib/generators/social_stream/templates/migration.rb
  31. +41 −0 lib/generators/social_stream/templates/relations.yml
  32. +0 −58 lib/generators/social_stream/templates/seeds.yml
  33. +3 −7 lib/social_stream.rb
  34. +4 −4 lib/social_stream/ability.rb
  35. +4 −26 lib/social_stream/models/actor.rb
  36. +46 −0 lib/social_stream/relations.rb
  37. +0 −49 lib/social_stream/seed.rb
  38. +16 −25 lib/tasks/db/populate.rake
  39. +1 −6 spec/controllers/groups_controller_spec.rb
  40. +19 −0 spec/controllers/ties_controller_spec.rb
  41. +0 −2 spec/controllers/users_controller_spec.rb
  42. +1 −0 spec/dummy/config/relations.yml
  43. +0 −1 spec/dummy/db/seeds.rb
  44. +0 −1 spec/dummy/db/seeds/social_stream.yml
  45. +2 −2 spec/factories/activity.rb
  46. +2 −1 spec/factories/actor.rb
  47. +1 −1 spec/factories/post.rb
  48. +17 −19 spec/factories/tie.rb
  49. +17 −14 spec/models/activity_spec.rb
  50. +3 −0 spec/models/actor_spec.rb
  51. +14 −16 spec/models/tie_spec.rb
View
@@ -43,14 +43,13 @@ This will generate the following:
* A jquery:install generation for jQuery support
* A devise:install generation for authentication support
* An initializer file with configuration for Social Stream.
-* A database seeds file for defining custom Social Stream relations, along with an entry in db/seeds.rb to load it. You can define your own relations at <tt>db/seeds/social_stream.yml</tt>
+* A configuration file for defining custom Social Stream relations. You can define your application default relations at <tt>config/relations.yml</tt>
* A new application layout
* A migration providing the database schema
-Do not forget to migrate and seed your database
+Do not forget to migrate your database
rake db:migrate
- rake db:seed
== Actors and Activity Objects
@@ -3,11 +3,25 @@ def tie_brief(tie)
"N contacts in common"
end
- def tie_link(tie)
- link_to t("contact.new"),
+ # Show current ties from current user to actor, if they exist, or provide a link
+ # to create a new tie to actor
+ def ties_to(a)
+ if user_signed_in?
+ if current_user.ties_to(a).present?
+ current_user.ties_to(a).first.relation_name
+ else
+ new_tie_link(current_user.sent_ties.build :receiver_id => Actor.normalize_id(a))
+ end
+ else
+ link_to t("contact.new.link"), new_user_session_path
+ end
+ end
+
+ def new_tie_link(tie)
+ link_to t("contact.new.link"),
new_tie_path("tie[sender_id]" => tie.sender.id,
"tie[receiver_id]" => tie.receiver.id),
- :title => t("contact.confirm_new",
+ :title => t("contact.new.title",
:name => tie.receiver_subject.name),
:remote => true
end
@@ -26,14 +26,23 @@ class Activity < ActiveRecord::Base
has_many :activity_objects,
:through => :activity_object_activities
- scope :wall, lambda { |ties|
+ scope :home_wall, lambda { |ties|
select("DISTINCT activities.*").
roots.
joins(:tie_activities).
where('tie_activities.tie_id' => ties).
order("created_at desc")
}
+ scope :profile_wall, lambda { |ties|
+ select("DISTINCT activities.*").
+ roots.
+ joins(:tie_activities).
+ where('tie_activities.tie_id' => ties).
+ where('tie_activities.original' => true).
+ order("created_at desc")
+ }
+
# After an activity is created, it is associated to ties
attr_accessor :_tie
after_create :assign_to_ties
@@ -12,7 +12,11 @@ class ActivityVerb < ActiveRecord::Base
class << self
def [] name
- verb_name(name).first
+ if Available.include?(name)
+ find_or_create_by_name name
+ else
+ raise "ActivityVerb not available: #{ name }"
+ end
end
end
end
View
@@ -17,19 +17,35 @@ class Actor < ActiveRecord::Base
:foreign_key => 'sender_id',
:dependent => :destroy
- has_many :senders,
- :through => :received_ties,
- :uniq => true
-
has_many :received_ties,
:class_name => "Tie",
:foreign_key => 'receiver_id',
:dependent => :destroy
+ has_many :senders,
+ :through => :received_ties,
+ :uniq => true
+
has_many :receivers,
:through => :sent_ties,
:uniq => true
+ after_create :initialize_ties
+
+ class << self
+ # Get actor's id from an object, if possible
+ def normalize_id(a)
+ case a
+ when Integer
+ a
+ when Actor
+ a.id
+ else
+ a.actor.id
+ end
+ end
+ end
+
# The subject instance for this actor
def subject
subtype_instance ||
@@ -41,16 +57,28 @@ def ties
Tie.sent_or_received_by(self)
end
- # All the subject actors of class subject_type that send at least one tie
- # to this actor
+ # Relations defined and managed by this actor
+ def relations
+ Relation.includes(:ties) & Tie.sent_by(self)
+ end
+
+ # A given relation defined and managed by this actor
+ def relation(name)
+ relations.find_by_name(name)
+ end
+
+ # All the subject actors that send at least one tie to this actor
#
# Options::
+ # * subject_type: The class of the subjects. Defaults to actor's own subject type
# * relations: Restrict the relations of considered ties
# * include_self: False by default, don't include this actor as subject even they
# have ties with themselves.
- def sender_subjects(subject_type, options = {})
+ def sender_subjects(options = {})
# FIXME: DRY!
- subject_class = subject_type.to_s.classify.constantize
+ options[:subject_type] ||= subject_type
+
+ subject_class = options[:subject_type].to_s.classify.constantize
cs = subject_class.
select("DISTINCT #{ subject_class.quoted_table_name }.*").
@@ -63,22 +91,24 @@ def sender_subjects(subject_type, options = {})
if options[:relations].present?
cs &=
- Tie.related_by(Tie.Relation(options[:relations], :mode => [ subject_class, self.subject.class ]))
+ Tie.related_by(Tie.Relation(options[:relations]))
end
cs
end
- # All the subject actors of class subject_type that receive at least one tie
- # from this actor
+ # All the subject actors that receive at least one tie from this actor
#
# Options::
+ # * subject_type: The class of the subjects. Defaults to actor's own subject type
# * relations: Restrict the relations of considered ties
# * include_self: False by default, don't include this actor as subject even they
# have ties with themselves.
- def receiver_subjects(subject_type, options = {})
+ def receiver_subjects(options = {})
# FIXME: DRY!
- subject_class = subject_type.to_s.classify.constantize
+ options[:subject_type] ||= subject_type
+
+ subject_class = options[:subject_type].to_s.classify.constantize
cs = subject_class.
select("DISTINCT #{ subject_class.quoted_table_name }.*").
@@ -91,18 +121,16 @@ def receiver_subjects(subject_type, options = {})
if options[:relations].present?
cs &=
- Tie.related_by(Tie.Relation(options[:relations], :mode => [ subject.class, subject_class ]))
+ Tie.related_by(Tie.Relation(options[:relations], :sender => sender))
end
cs
end
+ alias :contacts :receiver_subjects
+
# This is an scaffold for a recomendations engine
#
- SuggestedRelations = {
- 'User' => 'friend_request',
- 'Group' => 'follower'
- }
# Make n suggestions
# TODO: make more
@@ -118,16 +146,17 @@ def suggestions(n)
#
# @return [Tie]
def suggestion(options = {})
- candidates_types = options[:type].present? ?
- Array(options[:type].to_s.classify) :
- SuggestedRelations.keys
+ candidates_types =
+ options[:type].present? ?
+ Array(options[:type]) :
+ SocialStream.actors
- candidates_classes = candidates_types.map(&:constantize)
+ candidates_classes = candidates_types.map{ |t| t.to_s.classify.constantize }
# Candidates are all the instance of "type" minus all the subjects
# that are receiving any tie from this actor
candidates = candidates_classes.inject([]) do |cs, klass|
- cs += klass.all - receiver_subjects(klass)
+ cs += klass.all - receiver_subjects(:subject_type => klass)
cs -= Array(subject) if subject.is_a?(klass)
cs
end
@@ -136,38 +165,56 @@ def suggestion(options = {})
return nil unless candidate.present?
- sent_ties.build :receiver_id => candidate.actor.id,
- :relation => Relation.mode(subject_type, candidate.class).find_by_name(SuggestedRelations[candidate.class.to_s])
+ # Building ties with sent_ties catches them and excludes them from pending ties.
+ # An useful side effect for excluding this ones from pending, but can be weird!
+ # Maybe we must use:
+ # Tie.sent_by(self).build :receiver_id => candidate.actor.id
+ sent_ties.build :receiver_id => candidate.actor.id
+ end
+
+ # Set of ties sent by this actor received by a
+ def ties_to(a)
+ sent_ties.received_by(a)
end
# All the ties this actor has with subject that support permission
def sent_ties_allowing(subject, action, objective)
+ return [] if subject.blank?
+
sent_ties.allowing(subject, action, objective)
end
def pending_ties
- #TODO: optimize by SQL
@pending_ties ||=
- received_ties.pending.
- select{ |t| ! receivers.include?(t.sender) }.
- map{ |u| Tie.new :sender_id => u.receiver_id,
- :receiver_id => u.sender_id,
- :relation_id => u.relation.granted_id
- }
+ received_ties.where('ties.sender_id NOT IN (?)', sent_ties.map(&:receiver_id).uniq).map(&:sender_id).uniq.
+ map{ |i| Tie.new :sender => self,
+ :receiver_id => i }
end
# The set of activities in the wall of this actor, includes all the activities
# from the ties the actor has access to
#
- def wall
- Activity.wall Tie.allowing(self, 'read', 'activity')
+ def home_wall
+ Activity.home_wall ties
end
# The set of activities in the wall profile of this actor, includes the activities
# from the ties of this actor that can be read by user
#
- def wall_profile(user)
- Activity.wall ties.allowing(user, 'read', 'activity')
+ def profile_wall(user)
+ # FIXME: show public activities
+ return [] if user.blank?
+
+ Activity.profile_wall ties.allowing(user, 'read', 'activity')
+ end
+
+ private
+
+ def initialize_ties
+ ::SocialStream::Relations.create(subject_type).each do |r|
+ sent_ties.create! :receiver => self,
+ :relation => r
+ end
end
end
View
@@ -1,5 +1,5 @@
class Group < ActiveRecord::Base
def followers
- sender_subjects(:user, :relations => 'follower')
+ sender_subjects(:subject_type => :user)
end
end
@@ -8,36 +8,53 @@
# two actors are stronger than others.
# When a strong tie is established, ties with weaker relations are establised as well
#
-# == Reflexive relations
-# Some relations are set by default for actors with theirselves. This sets some ties
-# for posting in self wall at several visibility levels: only for friends, public and
-# so on
-#
-# == Inverse relations
-# A Relation can have its inverse. When a tie is established, an inverse tie will be
-# established if an inverse relation exists. An example is a relation of friendship,
-# whose inverse relation is itself. When A is friend of B, the inverse tie B is friend of A
-# is establised as well.
-#
-
class Relation < ActiveRecord::Base
acts_as_nested_set
scope :mode, lambda { |st, rt|
where(:sender_type => st, :receiver_type => rt)
}
- belongs_to :inverse,
- :class_name => "Relation"
-
- scope :reflexive, where(:reflexive => true)
-
has_many :relation_permissions, :dependent => :destroy
has_many :permissions, :through => :relation_permissions
has_many :ties, :dependent => :destroy
class << self
+ # Get relation from object, if possible
+ #
+ # Options::
+ # sender:: The sender of the tie
+ def normalize(r, options = {})
+ case r
+ when Relation
+ r
+ when String
+ if options[:sender]
+ options[:sender].relation(r)
+ else
+ raise "Must provide a sender when looking up relations from name: #{ options[:sender] }"
+ end
+ when Integer
+ Relation.find r
+ when Array
+ r.map{ |e| Relation.normalize(e, options) }
+ else
+ raise "Unable to normalize relation #{ r.inspect }"
+ end
+ end
+
+ def normalize_id(r, options = {})
+ case r
+ when Integer
+ r
+ when Array
+ r.map{ |e| Relation.normalize_id(e, options) }
+ else
+ normalize(r, options).id
+ end
+ end
+
# A relation in the top of a strength hierarchy
def strongest
root
Oops, something went wrong.

0 comments on commit e0cb8cd

Please sign in to comment.