social_stream-base 0.12.1 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
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 %>