social_stream-base 0.12.1 → 0.13.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.
Files changed (59) hide show
  1. data/app/assets/images/flags/en.png +0 -0
  2. data/app/assets/images/flags/es.png +0 -0
  3. data/app/assets/javascripts/toolbar.js +22 -1
  4. data/app/assets/stylesheets/activities.css.scss +1 -1
  5. data/app/assets/stylesheets/cheesecake.css.scss +46 -4
  6. data/app/assets/stylesheets/settings.css +11 -0
  7. data/app/controllers/cheesecake_controller.rb +15 -3
  8. data/app/controllers/contacts_controller.rb +2 -0
  9. data/app/controllers/conversations_controller.rb +5 -5
  10. data/app/controllers/likes_controller.rb +1 -1
  11. data/app/helpers/notifications_helper.rb +10 -0
  12. data/app/models/activity.rb +55 -36
  13. data/app/models/activity_object.rb +3 -23
  14. data/app/models/actor.rb +20 -4
  15. data/app/models/channel.rb +13 -1
  16. data/app/models/comment.rb +4 -0
  17. data/app/models/contact.rb +55 -19
  18. data/app/models/group.rb +5 -2
  19. data/app/models/like.rb +2 -2
  20. data/app/models/relation/custom.rb +1 -1
  21. data/app/models/tie.rb +4 -3
  22. data/app/views/cheesecake/_cheesecake.html.erb +63 -0
  23. data/app/views/cheesecake/_index.html.erb +43 -86
  24. data/app/views/cheesecake/index.html.erb +2 -0
  25. data/app/views/cheesecake/update.js.erb +3 -0
  26. data/app/views/devise/passwords/new.html.erb +1 -1
  27. data/app/views/devise/registrations/new.html.erb +1 -1
  28. data/app/views/devise/sessions/new.html.erb +1 -1
  29. data/app/{assets/stylesheets/0_devise_sign.css → views/layouts/_devise_style.html.erb} +3 -1
  30. data/app/views/layouts/_header_signed_in.erb +1 -1
  31. data/app/views/message_mailer/new_message_email.html.erb +2 -1
  32. data/app/views/message_mailer/new_message_email.text.erb +2 -1
  33. data/app/views/message_mailer/reply_message_email.html.erb +2 -1
  34. data/app/views/message_mailer/reply_message_email.text.erb +2 -1
  35. data/app/views/notification_mailer/new_notification_email.html.erb +2 -1
  36. data/app/views/notification_mailer/new_notification_email.text.erb +2 -1
  37. data/app/views/notifications/activities/_post.html.erb +24 -1
  38. data/app/views/notifications/activities/_post.text.erb +22 -3
  39. data/app/views/settings/_language.html.erb +1 -1
  40. data/config/locales/en.yml +3 -2
  41. data/config/locales/es.yml +4 -3
  42. data/config/routes.rb +1 -0
  43. data/db/migrate/20120111141717_activity_channels.rb +74 -0
  44. data/lib/social_stream-base.rb +1 -0
  45. data/lib/social_stream/base/engine.rb +2 -1
  46. data/lib/social_stream/base/version.rb +1 -1
  47. data/lib/social_stream/models/channeled.rb +50 -0
  48. data/lib/social_stream/models/object.rb +4 -13
  49. data/lib/social_stream/toolbar_config/base.rb +1 -1
  50. data/lib/tasks/db/populate.rake +1 -1
  51. data/social_stream-base.gemspec +2 -2
  52. data/spec/controllers/comments_controller_spec.rb +3 -3
  53. data/spec/factories/activity.rb +3 -3
  54. data/spec/factories/contact.rb +2 -0
  55. data/spec/models/activity_authorization_spec.rb +5 -1
  56. data/spec/models/like_spec.rb +3 -3
  57. data/spec/models/tie_spec.rb +4 -4
  58. metadata +70 -64
  59. data/vendor/assets/javascripts/menu.js +0 -25
data/app/models/actor.rb CHANGED
@@ -316,10 +316,17 @@ class Actor < ActiveRecord::Base
316
316
  end
317
317
 
318
318
  # The {Contact} of this {Actor} to self (totally close!)
319
- def ego_contact
319
+ def self_contact
320
320
  contact_to!(self)
321
321
  end
322
322
 
323
+ alias_method :ego_contact, :self_contact
324
+
325
+ # The {Channel} of this {Actor} to self (totally close!)
326
+ def self_channel
327
+ Channel.find_or_create_by_author_id_and_user_author_id_and_owner_id id, id, id
328
+ end
329
+
323
330
  def sent_active_contact_ids
324
331
  @sent_active_contact_ids ||=
325
332
  load_sent_active_contact_ids
@@ -483,7 +490,7 @@ class Actor < ActiveRecord::Base
483
490
  end
484
491
 
485
492
  def liked_by(subject) #:nodoc:
486
- likes.joins(:contact).merge(Contact.sent_by(subject))
493
+ likes.joins(:channel).merge(Channel.subject_authored_by(subject))
487
494
  end
488
495
 
489
496
  # Does subject like this {Actor}?
@@ -492,9 +499,14 @@ class Actor < ActiveRecord::Base
492
499
  end
493
500
 
494
501
  # Build a new activity where subject like this
495
- def new_like(subject)
502
+ def new_like(subject, user)
503
+ channel =
504
+ Channel.
505
+ find_or_create_by_author_id_and_user_author_id_and_owner_id Actor.normalize_id(subject),
506
+ Actor.normalize_id(user),
507
+ id
496
508
  a = Activity.new :verb => "like",
497
- :contact => subject.contact_to!(self),
509
+ :channel => channel,
498
510
  :relation_ids => Array(subject.relation_public.id)
499
511
 
500
512
  a.activity_objects << activity_object
@@ -556,6 +568,10 @@ class Actor < ActiveRecord::Base
556
568
  def load_sent_active_contact_ids
557
569
  sent_contacts.active.map(&:receiver_id)
558
570
  end
571
+
572
+ def unread_messages_count
573
+ mailbox.inbox(:unread => true).count(:id, :distinct => true)
574
+ end
559
575
  end
560
576
 
561
577
  ActiveSupport.run_load_hooks(:actor, Actor)
@@ -19,7 +19,8 @@ class Channel < ActiveRecord::Base
19
19
  belongs_to :user_author,
20
20
  :class_name => "Actor"
21
21
 
22
- has_many :activity_objects
22
+ has_many :activity_objects, :dependent => :destroy
23
+ has_many :activities, :dependent => :destroy
23
24
 
24
25
  validates_uniqueness_of :author_id, :scope => [ :owner_id, :user_author_id ]
25
26
  validates_uniqueness_of :owner_id, :scope => [ :author_id, :user_author_id ]
@@ -31,6 +32,12 @@ class Channel < ActiveRecord::Base
31
32
  where(arel_table[:author_id].eq(id).or(arel_table[:user_author_id].eq(id)))
32
33
  }
33
34
 
35
+ scope :subject_authored_by, lambda { |subject|
36
+ id = Actor.normalize_id subject
37
+
38
+ where(:author_id => id)
39
+ }
40
+
34
41
  # The {SocialStream::Models::Subject subject} author
35
42
  def author_subject
36
43
  author.subject
@@ -45,4 +52,9 @@ class Channel < ActiveRecord::Base
45
52
  def user_author_subject
46
53
  user_author.subject
47
54
  end
55
+
56
+ # Does this {Channel} have the same sender and receiver?
57
+ def reflexive?
58
+ author_id == owner_id
59
+ end
48
60
  end
@@ -3,6 +3,10 @@ class Comment < ActiveRecord::Base
3
3
 
4
4
  validates_presence_of :text
5
5
 
6
+ def parent_post
7
+ self.post_activity.parent.direct_object
8
+ end
9
+
6
10
  def _activity_parent_id=(id)
7
11
  self._relation_ids = Activity.find(id).relation_ids
8
12
  @_activity_parent_id = id
@@ -5,21 +5,6 @@
5
5
  # for instance), and they do not mean that there is a real link between those two
6
6
  # {SocialStream::Models::Subject Subjects}. Link existance is stored as {Tie Ties}.
7
7
  #
8
- # = {Contact Contacts} and {Activity activities}
9
- #
10
- # WARNING: This will be change soon to direct references to author, owner and user_author,
11
- # in the same way as {ActivityObject}
12
- #
13
- # Each {Activity} is attached to a {Contact}. When _Alice_ post in _Bob_'s wall,
14
- # the {Activity} is attached to the {Contact} from _Alice_ to _Bob_
15
- #
16
- # * The sender of the {Contact} is the author of the {Activity}.
17
- # It is the user that uploads a resource to the website or the social entity that
18
- # originates the {Activity} (for example: add as contact).
19
- #
20
- # * The receiver {Actor} of the {Contact} is the receiver of the {Activity}.
21
- # The {Activity} will appear in the wall of the receiver, depending on the permissions
22
- #
23
8
  class Contact < ActiveRecord::Base
24
9
  # Send a message when this contact is created or updated
25
10
  attr_accessor :message
@@ -33,13 +18,12 @@ class Contact < ActiveRecord::Base
33
18
  :class_name => "Actor"
34
19
 
35
20
  has_many :ties,
36
- :dependent => :destroy
21
+ :dependent => :destroy,
22
+ :before_add => :set_user_author
23
+
37
24
  has_many :relations,
38
25
  :through => :ties
39
26
 
40
- has_many :activities,
41
- :dependent => :destroy
42
-
43
27
  scope :sent_by, lambda { |a|
44
28
  where(:sender_id => Actor.normalize_id(a))
45
29
  }
@@ -131,11 +115,27 @@ class Contact < ActiveRecord::Base
131
115
  # so follower_count will not be updated
132
116
  #
133
117
  # We need to update that status here
118
+ #
119
+ # FIXME: use :after_remove callback
120
+ # http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Association+callbacks
134
121
  def relation_ids=(ids)
135
122
  remove_follower(ids)
136
123
  association(:relations).ids_writer(ids)
137
124
  end
138
125
 
126
+ # Record who creates ties in behalf of a group or organization
127
+ #
128
+ # Defaults to the sender actor, if it is a user
129
+ def user_author
130
+ @user_author ||
131
+ build_user_author
132
+ end
133
+
134
+ # Set who creates ties in behalf of a group or organization
135
+ def user_author= subject
136
+ @user_author = (subject.nil? ? nil : Actor.normalize(subject))
137
+ end
138
+
139
139
  # Is this {Contact} +new+ or +edit+ for {SocialStream::Models::Subject subject} ?
140
140
  #
141
141
  # action is +new+ when, despite of being created, it has not {Tie ties} or it has a {Tie} with a
@@ -161,8 +161,44 @@ class Contact < ActiveRecord::Base
161
161
  end
162
162
  end
163
163
 
164
+ # The related {Channel} to this {Contact}.
165
+ #
166
+ # If the sender of this {Contact} is a user, the {Channel} is defined. If it is
167
+ # other kind of {SocialStream::Models::Subject}, the {Channel#user_author} must
168
+ # be provided.
169
+ def channel(user = nil)
170
+ user_id =
171
+ if sender.subject_type == "User"
172
+ sender_id
173
+ elsif user.present? && Actor.normalize(user).subject_type == "User"
174
+ Actor.normalize_id(user)
175
+ else
176
+ raise "Invalid channel user_author: #{ user.inspect }"
177
+ end
178
+
179
+ Channel.
180
+ find_or_create_by_author_id_and_user_author_id_and_owner_id sender_id,
181
+ user_id,
182
+ receiver_id
183
+ end
184
+
164
185
  private
165
186
 
187
+ def build_user_author
188
+ return sender if sender.subject_type == "User"
189
+
190
+ raise "Cannot determine user_author for #{ sender.inspect }"
191
+ end
192
+
193
+ # user_author is not preserved when the associated tie is build, in:
194
+ #
195
+ # contact.ties.build
196
+ #
197
+ # so we need to preserve so the tie activity is recorded
198
+ def set_user_author(tie)
199
+ tie.contact.user_author = @user_author
200
+ end
201
+
166
202
  def remove_follower(ids)
167
203
  # There was no follower
168
204
  return if relation_ids.blank?
data/app/models/group.rb CHANGED
@@ -43,8 +43,11 @@ class Group < ActiveRecord::Base
43
43
 
44
44
  # Creates the ties from the group to the participants
45
45
  def create_ties_to_participants
46
- ([ author_id, user_author_id ].uniq | Array.wrap(@_participants)).uniq.each do |a|
47
- sent_contacts.create! :receiver_id => a,
46
+ participant_ids = ([ author_id, user_author_id ] | Array.wrap(@_participants)).uniq
47
+
48
+ participant_ids.each do |a|
49
+ sent_contacts.create! :receiver_id => a,
50
+ :user_author => user_author,
48
51
  :relation_ids => Array(relation_customs.sort.first.id)
49
52
  end
50
53
  end
data/app/models/like.rb CHANGED
@@ -17,8 +17,8 @@ class Like
17
17
  raise(ActiveRecord::RecordNotFound)
18
18
  end
19
19
 
20
- def build(subject, object)
21
- new object.new_like(subject)
20
+ def build(subject, user, object)
21
+ new object.new_like(subject, user)
22
22
  end
23
23
  end
24
24
 
@@ -101,7 +101,7 @@ class Relation::Custom < Relation
101
101
  def to_cheesecake_hash(options = {})
102
102
  {:id => id, :name => name}.tap do |hash|
103
103
  if options[:subsector]
104
- hash[:actors] = ties.map{ |t| [t.contact.receiver_id, t.contact.receiver.name] }.uniq
104
+ hash[:actors] = ties.map{ |t| [t.contact.receiver_id, t.contact.receiver.name, t.contact_id] }.uniq
105
105
  else
106
106
  hash[:subsectors] = ( weaker.present? ?
107
107
  weaker.map{ |w| w.to_cheesecake_hash(:subsector => true) } :
data/app/models/tie.rb CHANGED
@@ -22,7 +22,6 @@
22
22
  # integer, array
23
23
  #
24
24
  class Tie < ActiveRecord::Base
25
-
26
25
  belongs_to :contact, :counter_cache => true
27
26
 
28
27
  has_one :sender, :through => :contact
@@ -98,8 +97,10 @@ class Tie < ActiveRecord::Base
98
97
  def create_activity
99
98
  return if contact.reload.ties_count != 1 || relation.is_a?(Relation::Reject)
100
99
 
101
- Activity.create! :contact => contact,
102
- :relation_ids => contact.relation_ids,
100
+ Activity.create! :author => contact.sender,
101
+ :user_author => contact.user_author,
102
+ :owner => contact.receiver,
103
+ :relation_ids => contact.relation_ids,
103
104
  :activity_verb => ActivityVerb[contact.verb]
104
105
  end
105
106
 
@@ -0,0 +1,63 @@
1
+ $(function(){
2
+ var cheesecakeData = {
3
+ container: {
4
+ id: "contacts_cheesecake",
5
+ width: 440,
6
+ height: 440
7
+ },
8
+ grid: {
9
+ id: "contacts_grid",
10
+ divIdPrefix: "actor_"
11
+ },
12
+ rMax : 200,
13
+ center: {x : 220, y : 220}
14
+ };
15
+ cheesecakeData.highlightedSectorCallback = function(cheesecake){
16
+ $("#contacts_grid").css("overflow-y","hidden");
17
+ var sector = cheesecake.highlightedSector;
18
+ var actors = [];
19
+ var visibles = 0;
20
+ var extra_text = ["<%= t('cheesecake.hidden_contact.one')%>","<%= t('cheesecake.hidden_contact.other')%>"];
21
+ if(sector){
22
+ actors = sector.actors;
23
+ }else{
24
+ actors = cheesecake.grid.actors;
25
+ }
26
+ for(var i in actors){
27
+ if(actors[i].isVisible()){
28
+ visibles++;
29
+ }
30
+ }
31
+ if(visibles <= 30){
32
+ $("#contacts_grid_extra").hide();
33
+ }else{
34
+ $("#contacts_grid_extra_total").html(visibles - 30);
35
+ if((visibles - 30) == 1){
36
+ $("#contacts_grid_extra_text").html(extra_text[0]);
37
+ }else{
38
+ $("#contacts_grid_extra_text").html(extra_text[1]);
39
+ }
40
+ $("#contacts_grid_extra").show();
41
+ $("#contacts_grid").scrollTo(0, 1000);
42
+ }
43
+ }
44
+ cheesecakeData.sectors = <%= raw(current_subject.cheesecake_json) %>.sectors;
45
+ var cheese = new socialCheesecake.Cheesecake(cheesecakeData);
46
+ $("#contacts_filter_input").keyup(function(){
47
+ cheese.searchEngine.filter($("#contacts_filter_input").val());
48
+ cheesecakeData.highlightedSectorCallback(cheese);
49
+ });
50
+ cheese.onChange = function(cheesecake){
51
+ $("#contacts_save_changes").val(JSON.stringify(cheesecake.getChanges()));
52
+ }
53
+ if(cheese.grid.actors.length > 30){
54
+ var extra_text = ["<%= t('cheesecake.hidden_contact.one')%>","<%= t('cheesecake.hidden_contact.other')%>"];
55
+ $("#contacts_grid_extra_total").html(cheese.grid.actors.length - 30);
56
+ if((cheese.grid.actors.length - 30) == 1){
57
+ $("#contacts_grid_extra_text").html(extra_text[0]);
58
+ }else{
59
+ $("#contacts_grid_extra_text").html(extra_text[1]);
60
+ }
61
+ $("#contacts_grid_extra").show();
62
+ }
63
+ });
@@ -1,90 +1,47 @@
1
- <div id="cheesecake">
2
- <div id="contacts_cheesecake"></div>
3
- <div id="contacts_filter">
4
- <%= text_field_tag :filter_query, nil,:autocomplete => :off, :id => :contacts_filter_input %>
5
- </div>
6
- <div id="contacts_grid">
7
- <% @actors.each do |actor|%>
8
- <%= render :partial => "actors/actor_cheesecake", :locals => {:actor => actor} %>
9
- <% end %>
1
+
2
+ <div id="contacts_cheesecake"></div>
3
+ <div id="contacts_filter">
4
+ <%= text_field_tag :filter_query, nil,:autocomplete => :off, :id => :contacts_filter_input %>
5
+ </div>
6
+ <div id="contacts_grid">
7
+ <% @actors.each do |actor|%>
8
+ <%= render :partial => "actors/actor_cheesecake", :locals => {:actor => actor} %>
9
+ <% end %>
10
+ </div>
11
+ <div id="contacts_grid_extra" style="display:none;">
12
+ <span id="contacts_grid_extra_total"></span> <span id="contacts_grid_extra_text"></span>
13
+ </div>
14
+ <div id="contacts_save">
15
+ <form action="<%= update_cheesecake_path %>" method="get" data-remote="true" id="contacts_save_form">
16
+ <input type="submit" id="contacts_save_button" value="Save changes">
17
+ <input type="hidden" id="contacts_save_changes" name="contacts_save_changes">
18
+ </form>
19
+ </div>
20
+ <br class="clearfloat">
21
+ <div id="contacts_changes">
22
+ <div id="contacts_changes_title">
23
+ Changes details
10
24
  </div>
11
- <div id="contacts_grid_extra" style="display:none;">
12
- <span id="contacts_grid_extra_total"></span> <span id="contacts_grid_extra_text"></span>
25
+ <div id="contacts_changes_details">
26
+ <ul>
27
+ <li>Working on it :)</li>
28
+ </ul>
13
29
  </div>
14
- <%= javascript_tag do %>
15
- $("#contacts_grid_extra").click(function(){
16
- $("#contacts_grid").css("overflow-y","scroll");
17
- $("#contacts_grid").scrollTo("+=308px", 800, {axis:'y'});
18
- $("#contacts_grid_extra").hide();
30
+ </div>
31
+ <%= javascript_tag do %>
32
+ $("#center_body").css("width","782px");
33
+ $("#center_body").css("max-width","782px");
34
+ $("#content").css("width","782px");
35
+ $("#contacts_filter_input").Watermark("<%= escape_javascript(I18n.t('search.name')) %>");
36
+ $("#contacts_grid_extra").click(function(){
37
+ $("#contacts_grid").css("overflow-y","scroll");
38
+ $("#contacts_grid").scrollTo("+=308px", 800, {axis:'y'});
39
+ $("#contacts_grid_extra").hide();
40
+ });
41
+ $("#contacts_changes_title").click(function(){
42
+ $("#contacts_changes_details").slideToggle("slow", function(){
43
+ $.scrollTo('#contacts_changes_details' ,300,{axis:'y'});
19
44
  });
20
- <% end %>
21
- <br class="clearfloat">
22
- <%= javascript_tag do %>
23
- $(function(){
24
- $("#center_body").css("width","782px");
25
- $("#center_body").css("max-width","782px");
26
- $("#content").css("width","782px");
27
- $("#contacts_filter_input").Watermark("<%= escape_javascript(I18n.t('search.name')) %>");
28
45
  });
29
- window.onload = function() {
30
- var cheesecakeData = {
31
- container: {
32
- id: "contacts_cheesecake",
33
- width: 440,
34
- height: 440
35
- },
36
- grid: {
37
- id: "contacts_grid",
38
- divIdPrefix: "actor_"
39
- },
40
- rMax : 200,
41
- center: {x : 220, y : 220}
42
- };
43
- cheesecakeData.highlightedSectorCallback = function(cheesecake){
44
- $("#contacts_grid").css("overflow-y","hidden");
45
- var sector = cheesecake.highlightedSector;
46
- var actors = [];
47
- var visibles = 0;
48
- var extra_text = ["<%= t('cheesecake.hidden_contact.one')%>","<%= t('cheesecake.hidden_contact.other')%>"];
49
- if(sector){
50
- actors = sector.actors;
51
- }else{
52
- actors = cheesecake.grid.actors;
53
- }
54
- for(var i in actors){
55
- if(actors[i].isVisible()){
56
- visibles++;
57
- }
58
- }
59
- if(visibles <= 30){
60
- $("#contacts_grid_extra").hide();
61
- }else{
62
- $("#contacts_grid_extra_total").html(visibles - 30);
63
- if((visibles - 30) == 1){
64
- $("#contacts_grid_extra_text").html(extra_text[0]);
65
- }else{
66
- $("#contacts_grid_extra_text").html(extra_text[1]);
67
- }
68
- $("#contacts_grid_extra").show();
69
- $("#contacts_grid").scrollTo(0, 0);
70
- }
71
- }
72
- cheesecakeData.sectors = <%= raw(current_subject.cheesecake_json) %>.sectors;
73
- var cheese = new socialCheesecake.Cheesecake(cheesecakeData);
74
- $("#contacts_filter_input").keyup(function(){
75
- cheese.searchEngine.filter($("#contacts_filter_input").val());
76
- cheesecakeData.highlightedSectorCallback(cheese);
77
- });
78
- if(cheese.grid.actors.length > 30){
79
- var extra_text = ["<%= t('cheesecake.hidden_contact.one')%>","<%= t('cheesecake.hidden_contact.other')%>"];
80
- $("#contacts_grid_extra_total").html(cheese.grid.actors.length - 30);
81
- if((cheese.grid.actors.length - 30) == 1){
82
- $("#contacts_grid_extra_text").html(extra_text[0]);
83
- }else{
84
- $("#contacts_grid_extra_text").html(extra_text[1]);
85
- }
86
- $("#contacts_grid_extra").show();
87
- }
88
- }
89
- <% end %>
90
- </div>
46
+ <%= render :partial => 'cheesecake' %>
47
+ <% end %>