Class: Actor

Inherits:
ActiveRecord::Base
  • Object
show all
Includes:
SocialStream::Models::Object, SocialStream::Models::Supertype
Defined in:
app/models/actor.rb

Overview

An Actor is a social entity. This includes individuals, but also groups, departments, organizations even nations or states.

Actors are the nodes of a social network. Two actors are linked by a Tie. The type of a tie is a Relation. Each actor can define and customize their relations.

Actor subtypes

An actor subtype is called a Subject. SocialStream provides 2 actor subtypes, User and Group, but the application developer can define as many actor subtypes as required. Actors subtypes are added to config/initializers/social_stream.rb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.find_by_webfinger!(link) ⇒ Object



142
143
144
145
146
# File 'app/models/actor.rb', line 142

def find_by_webfinger!(link)
  link =~ /(acct:)?(.*)@/

  find_by_slug! $2
end

.normalize(a) ⇒ Object

Get actor from object, if possible



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'app/models/actor.rb', line 125

def normalize(a)
  case a
  when Actor
    a
  when Integer
    Actor.find a
  when Array
    a.map{ |e| Actor.normalize(e) }
  else
    begin
      a.actor
    rescue
      raise "Unable to normalize actor #{ a.inspect }"        
    end
  end
end

.normalize_id(a) ⇒ Object

Get actor’s id from an object, if possible



114
115
116
117
118
119
120
121
122
123
# File 'app/models/actor.rb', line 114

def normalize_id(a)
  case a
  when Integer
    a
  when Array
    a.map{ |e| normalize_id(e) }
  else
    Actor.normalize(a).id
  end
end

Instance Method Details

#activity_relations(subject, options = {}) ⇒ Object

An Activity can be shared with multiple Audience, which corresponds to a Relation.

This method returns all the Relation that this actor can use to broadcast an Activity



347
348
349
350
351
352
353
# File 'app/models/actor.rb', line 347

def activity_relations(subject, options = {})
  if Actor.normalize(subject) == self
    return relation_customs + Array.wrap(relation_public)
  else
    Array.new
  end
end

#activity_relations?(*args) ⇒ Boolean

Are there any activity_relations present?

Returns:

  • (Boolean)


356
357
358
# File 'app/models/actor.rb', line 356

def activity_relations?(*args)
  activity_relations(*args).any?
end

#avatar!Object



447
448
449
# File 'app/models/actor.rb', line 447

def avatar!
  avatar || avatars.build
end

#can_comment?(activity) ⇒ Boolean

Is this Actor allowed to create a comment on activity?

We are allowing comments from everyone signed in by now

Returns:

  • (Boolean)


363
364
365
366
367
# File 'app/models/actor.rb', line 363

def can_comment?(activity)
  return true

  comment_relations(activity).any?
end

#comment_relations(activity) ⇒ Object

Are there any relations that allow this actor to create a comment on activity?



370
371
372
373
# File 'app/models/actor.rb', line 370

def comment_relations(activity)
  activity.relations.select{ |r| r.is_a?(Relation::Public) } |
    Relation.allow(self, 'create', 'activity', :in => activity.relations)
end

#common_contacts_count(subject) ⇒ Object

Count the contacts in common between this Actor and subject



392
393
394
# File 'app/models/actor.rb', line 392

def common_contacts_count(subject)
  (sent_active_contact_ids & subject.sent_active_contact_ids).size
end

#contact_actors(options = {}) ⇒ Object

All the actors this one has relation with

Options:

  • type: Filter by the class of the contacts.

  • direction: sent or received

  • relations: Restrict the relations of considered ties. In the case of both directions, only relations belonging to Actor are valid. It defaults to relations of custom and public types

  • include_self: False by default, don’t include this actor as subject even they have ties with themselves.

  • load_subjects: True by default, make the queries for eager loading of contacts



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'app/models/actor.rb', line 215

def contact_actors(options = {})
  subject_types   = Array(options[:type] || self.class.subtypes)
  subject_classes = subject_types.map{ |s| s.to_s.classify }
  
  as = Actor.select('actors.*').
             # PostgreSQL requires that all the columns must be included in the GROUP BY
             group((Actor.columns.map(&:name).map{ |c| "actors.#{ c }" } + [ "contacts.created_at" ]).join(", ")).
             where('actors.subject_type' => subject_classes)

  if options[:load_subjects].nil? || options[:load_subjects]
    as = as.includes(subject_types)
  end
  
  # Make another query for getting the actors in the other way
  if options[:direction].blank?
    rcv_opts = options.dup
    rcv_opts[:direction] = :received
    rcv_opts[:load_subjects] = false

    sender_ids = contact_actors(rcv_opts).map(&:id)

    as = as.where(:id => sender_ids)

    options[:direction] = :sent
  end
  
  case options[:direction]
  when :sent
    as = as.joins(:received_ties => :relation).merge(Contact.sent_by(self))
  when :received
    as = as.joins(:sent_ties => :relation).merge(Contact.received_by(self))
  else
    raise "How do you get here?!"
  end
  
  if options[:include_self].blank?
    as = as.where("actors.id != ?", self.id)
  end
  
  if options[:relations].present?
    as = as.merge(Tie.related_by(options[:relations]))
  else
    as = as.merge(Relation.where(:type => ['Relation::Custom', 'Relation::Public']))
  end
  
  as
end

#contact_subjects(options = {}) ⇒ Object

All the subjects that send or receive at least one Tie to this Actor

When passing a block, it will be evaluated for building the actors query, allowing to add options before the mapping to subjects

See #contact_actors for options



269
270
271
272
273
274
275
276
277
# File 'app/models/actor.rb', line 269

def contact_subjects(options = {})
  as = contact_actors(options)
  
  if block_given?
    as = yield(as)
  end
  
  as.map(&:subject)
end

#contact_to(subject) ⇒ Object

Return a contact to subject.



280
281
282
# File 'app/models/actor.rb', line 280

def contact_to(subject)
  sent_contacts.received_by(subject).first
end

#contact_to!(subject) ⇒ Object

Return a contact to subject. Create it if it does not exist



285
286
287
288
# File 'app/models/actor.rb', line 285

def contact_to!(subject)
  contact_to(subject) ||
    sent_contacts.create!(:receiver => Actor.normalize(subject))
end

#ego_contactObject

The Contact of this Actor to self (totally close!)



291
292
293
# File 'app/models/actor.rb', line 291

def ego_contact
  contact_to!(self)
end

#egocentric_tiesObject

The ties sent by this actor, plus the second grade ties



324
325
326
327
# File 'app/models/actor.rb', line 324

def egocentric_ties
  @egocentric_ties ||=
    load_egocentric_ties
end

#liked_by(subject) ⇒ Object

:nodoc:



457
458
459
# File 'app/models/actor.rb', line 457

def liked_by(subject) #:nodoc:
  likes.joins(:contact).merge(Contact.sent_by(subject))
end

#liked_by?(subject) ⇒ Boolean

Does subject like this Actor?

Returns:

  • (Boolean)


462
463
464
# File 'app/models/actor.rb', line 462

def liked_by?(subject)
  liked_by(subject).present?
end

#likesObject

The ‘like’ qualifications emmited to this actor



452
453
454
455
# File 'app/models/actor.rb', line 452

def likes
  Activity.joins(:activity_verb).where('activity_verbs.name' => "like").
           joins(:activity_objects).where('activity_objects.id' => activity_object_id)
end

#logoObject



443
444
445
# File 'app/models/actor.rb', line 443

def 
  avatar!.
end

#mailboxer_email(object) ⇒ Object

Returning the email address of the model if an email should be sent for this object (Message or Notification). If the actor is a Group and has no email address, an array with the email of the highest rank members will be returned isntead.

If no mail has to be sent, return nil.



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'app/models/actor.rb', line 154

def mailboxer_email(object)
  #If actor has disabled the emails, return nil.
  return nil if !notify_by_email
  #If actor has enabled the emails and has email
  return "#{name} <#{email}>" if email.present?
  #If actor is a Group, has enabled emails but no mail we return the highest_rank ones.
  if (group = self.subject).is_a? Group
    relation = group.relation_customs.sort.first
    receivers = group.contact_actors(:direction => :sent, :relations => relation)
    emails = Array.new
    receivers.each do |receiver|
      receiver_emails = receiver.mailboxer_email(object)
      case receiver_emails
      when String
        emails << receiver_emails
      when Array
        receiver_emails.each do |receiver_email|
          emails << receiver_email
        end
      end
    end
  return emails
  end
end

#new_like(subject) ⇒ Object

Build a new activity where subject like this



467
468
469
470
471
472
473
474
475
# File 'app/models/actor.rb', line 467

def new_like(subject)
  a = Activity.new :verb => "like",
                   :contact => subject.contact_to!(self),
                   :relation_ids => Array(subject.relation_public.id)
  
  a.activity_objects << activity_object           
                  
  a             
end

#pending_contactsObject

Build a new Contact from each that has not inverse



384
385
386
387
388
389
# File 'app/models/actor.rb', line 384

def pending_contacts
  received_contacts.pending.includes(:inverse).all.map do |c|
    c.inverse ||
      c.receiver.contact_to!(c.sender)
  end
end

#pending_contacts?Boolean

Returns:

  • (Boolean)


379
380
381
# File 'app/models/actor.rb', line 379

def pending_contacts?
  pending_contacts_count > 0
end

#pending_contacts_countObject



375
376
377
# File 'app/models/actor.rb', line 375

def pending_contacts_count
  received_contacts.not_reflexive.pending.count
end

#relation_custom(name) ⇒ Object

A given relation defined and managed by this actor



190
191
192
# File 'app/models/actor.rb', line 190

def relation_custom(name)
  relation_customs.find_by_name(name)
end

#relation_customsObject

All the relations defined by this Actor



185
186
187
# File 'app/models/actor.rb', line 185

def relation_customs
  relations.where(:type => 'Relation::Custom')
end

#relation_publicObject

The Relation::Public for this Actor



195
196
197
# File 'app/models/actor.rb', line 195

def relation_public
  Relation::Public.of(self)
end

#relation_rejectObject

The Relation::Reject for this Actor



200
201
202
# File 'app/models/actor.rb', line 200

def relation_reject
  Relation::Reject.of(self)
end

#represented_by?(subject) ⇒ Boolean

Can this actor be represented by subject. Does she has permissions for it?

Returns:

  • (Boolean)


331
332
333
334
335
336
337
338
339
340
# File 'app/models/actor.rb', line 331

def represented_by?(subject)
  return false if subject.blank?

  self.class.normalize(subject) == self ||
    sent_ties.
      merge(Contact.received_by(subject)).
      joins(:relation => :permissions).
      merge(Permission.represent).
      any?
end

#sent_active_contact_idsObject



295
296
297
298
# File 'app/models/actor.rb', line 295

def sent_active_contact_ids
  @sent_active_contact_ids ||=
    load_sent_active_contact_ids
end

#subjectObject

The subject instance for this actor



180
181
182
# File 'app/models/actor.rb', line 180

def subject
  subtype_instance
end

#suggestions(size = 1) ⇒ Contact

By now, it returns a suggested Contact to another Actor without any current Tie

Returns:



303
304
305
306
307
308
309
310
311
# File 'app/models/actor.rb', line 303

def suggestions(size = 1)
  candidates = Actor.where(Actor.arel_table[:id].not_in(sent_active_contact_ids + [id]))

  size.times.map {
    candidates.delete_at rand(candidates.size)
  }.compact.map { |a|
    contact_to! a
  }
end

#ties_to(subject) ⇒ Object

Set of ties sent by this actor received by subject



314
315
316
# File 'app/models/actor.rb', line 314

def ties_to(subject)
  sent_ties.merge(Contact.received_by(subject))
end

#ties_to?(subject) ⇒ Boolean

Is there any Tie sent by this actor and received by subject

Returns:

  • (Boolean)


319
320
321
# File 'app/models/actor.rb', line 319

def ties_to?(subject)
  ties_to(subject).present?
end

#to_paramObject

Use slug as parameter



478
479
480
# File 'app/models/actor.rb', line 478

def to_param
  slug
end

#wall(type, options = {}) ⇒ Object

The set of activities in the wall of this Actor.

There are two types of walls:

home

includes all the activities from this Actor and their followed actors

See {Permission permissions} for more information on the following support
profile

The set of activities in the wall profile of this Actor, it includes only the activities from the ties of this actor that can be read by the subject

Options:

:for

the subject that is accessing the wall

:relation

show only activities that are attached at this relation level. For example, the wall for members of the group.



409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
# File 'app/models/actor.rb', line 409

def wall(type, options = {})
  args = {}

  args[:type]  = type
  args[:owner] = self
  # Preserve this options
  [ :for, :object_type ].each do |opt|
    args[opt]   = options[opt]
  end

  if type == :home
    args[:followed] = Actor.followed_by(self).map(&:id)
  end

  # TODO: this is not scalling for sure. We must use a fact table in the future
  args[:relation_ids] =
    case type
    when :home
      # The relations from followings that can be read
      Relation.allow(self, 'read', 'activity').map(&:id)
    when :profile
      # FIXME: options[:relation]
      #
      # The relations that can be read by options[:for]
      options[:for].present? ?
        Relation.allow(options[:for], 'read', 'activity').map(&:id) :
        []
    else
      raise "Unknown type of wall: #{ type }"
    end

  Activity.wall args
end