Class: Actor

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

Overview

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

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

Every Actor has an Avatar, a Profile with personal or group information, contact data, etc.

Actors perform actions (like, suscribe, etc.) on activity objects (posts, commments, pictures, events..)

Actor subtypes

An actor subtype is called a Subject. SocialStream::Base provides two actor subtypes, User and Group, but the application developer can define as many actor subtypes as required. Besides including the SocialStream::Models::Subject module, Actor subtypes must added to config/initializers/social_stream.rb

Instance Method Summary collapse

Instance Method Details

#action_to(activity_object) ⇒ Object

Return the ActivityAction model to an ActivityObject



333
334
335
# File 'app/models/actor.rb', line 333

def action_to(activity_object)
  sent_actions.received_by(activity_object).first
end

#action_to!(activity_object) ⇒ Object

Return the ActivityAction model to an ActivityObject. Create it if it does not exist



338
339
340
341
# File 'app/models/actor.rb', line 338

def action_to!(activity_object)
  action_to(activity_object) ||
    sent_actions.create!(:activity_object => ActivityObject.normalize(activity_object))
end

#activity_relation_idsObject

The ids of the default Relations for sharing an Activity owned by this Actor



401
402
403
# File 'app/models/actor.rb', line 401

def activity_relation_ids
  activity_relations.map(&:id)
end

#activity_relationsObject

The default Relations for sharing an Activity owned by this Actor



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

def activity_relations
  SocialStream.relation_model == :custom ?
    relations.
      allowing('read', 'activity') :
    [ Relation::Public.instance ]
end

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

This method returns all the Relation that subject can choose to broadcast an Activity in this Actor‘s wall

See Activity on how they can be shared with multiple Audience, which corresponds to a Relation.



409
410
411
412
413
414
415
# File 'app/models/actor.rb', line 409

def activity_relations_for(subject, options = {})
  if Actor.normalize(subject) == self
    return relation_customs + Array.wrap(Relation::Public.instance)
  else
    Array.new
  end
end

#activity_relations_for?(subject, options = {}) ⇒ Boolean

Are #activity_relations available for subject?

Returns:

  • (Boolean)


418
419
420
# File 'app/models/actor.rb', line 418

def activity_relations_for?(subject, options = {})
  activity_relations(subject, options).any?
end

#allow?(subject, action, object) ⇒ Boolean

Does this Actor allow subject to perform action on object?

Returns:

  • (Boolean)


328
329
330
# File 'app/models/actor.rb', line 328

def allow?(subject, action, object)
  ties_to(subject).with_permissions(action, object).any?
end

#as_object_typeObject

FIXME SocialStream::ActivityStreams::Supertype should take precedence over SocialStream::ActivityStreams::Subtype



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

def as_object_type
  subtype_instance.as_object_type
end

#avatar!Object



502
503
504
# File 'app/models/actor.rb', line 502

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)


425
426
427
428
429
# File 'app/models/actor.rb', line 425

def can_comment?(activity)
  return true

  comment_relations(activity).any?
end

#cheesecake_jsonObject

JSON compatible with SocialCheesecake



540
541
542
543
544
545
546
547
# File 'app/models/actor.rb', line 540

def cheesecake_json
  {
    :sectors =>
      relation_customs.includes(:ties => :contact).map { |r|
        r.to_cheesecake_hash
      }
  }.to_json
end

#comment_relations(activity) ⇒ Object

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



432
433
434
435
# File 'app/models/actor.rb', line 432

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



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

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 ties with:

actor.contact_actors #=> array of actors that sent and receive ties from actor

There are several options available to refine the query:

type

Filter by the class of the contacts (User, Group, etc.)

actor.contact_actors(:type => :user) #=> array of user actors. Exclude groups, etc.
direction

:sent leaves only the actors this one has ties to. :received gets the actors sending ties to this actor, whether this actor added them or not

actor.contact_actors(:direction => :sent) #=> all the receivers of ties from actor
relations

Restrict to ties made up with relations. In the case of both directions, only relations belonging to Actor are considered. It defaults to actor’s custom relations

actor.contact_actors(:relations => [2]) #=> actors tied with relation #2
include_self

False by default, do not include this actor even they have ties with themselves.

load_subjects

True by default, make the queries for eager loading of Subject



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
262
263
264
265
266
267
268
269
270
# File 'app/models/actor.rb', line 219

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
  
  # A blank :direction means reciprocate contacts, there must be ties in both directions
  #
  # This is achieved by getting the id of all the contacts that are sending ties
  # Then, we filter the sent contacts query to only those contacts
  if options[:direction].blank?
    rcv_opts = options.dup
    rcv_opts[:direction] = :received
    rcv_opts[:load_subjects] = false

    # Get the id of actors that are sending to this one
    sender_ids = contact_actors(rcv_opts).map(&:id)

    # Filter the sent query with these ids
    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



278
279
280
281
282
283
284
285
286
# File 'app/models/actor.rb', line 278

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.



289
290
291
# File 'app/models/actor.rb', line 289

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



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

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

#egocentric_tiesObject

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



372
373
374
375
# File 'app/models/actor.rb', line 372

def egocentric_ties
  @egocentric_ties ||=
    load_egocentric_ties
end

#following_actor_and_self_idsObject

An array with the ids of Actors followed by this Actor plus the id from this Actor



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

def following_actor_and_self_ids
  following_actor_ids + [ id ]
end

#following_actor_idsObject

An array with the ids of Actors followed by this Actor



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

def following_actor_ids
  following_actor_objects.
    includes(:actor).
    map(&:actor).
    map(&:id)
end

#following_actor_objectsObject

The ActivityObjects followed by this Actor that are Actors



308
309
310
311
# File 'app/models/actor.rb', line 308

def following_actor_objects
  followings.
    where('activity_objects.object_type' => "Actor")
end

#liked_by(subject) ⇒ Object

:nodoc:



512
513
514
# File 'app/models/actor.rb', line 512

def liked_by(subject) #:nodoc:
  likes.authored_by(subject)
end

#liked_by?(subject) ⇒ Boolean

Does subject like this Actor?

Returns:

  • (Boolean)


517
518
519
# File 'app/models/actor.rb', line 517

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

#likesObject

The ‘like’ qualifications emmited to this actor



507
508
509
510
# File 'app/models/actor.rb', line 507

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

#logoObject



498
499
500
# File 'app/models/actor.rb', line 498

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.



151
152
153
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 151

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
    emails = Array.new
    group.relation_notifys.each do |relation|
      receivers = group.contact_actors(:direction => :sent, :relations => relation)
      receivers.each do |receiver|
        next unless Actor.normalize(receiver).subject_type.eql?("User")

        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
    end
  return emails
  end
end

#new_like(subject, user) ⇒ Object

Build a new activity where subject like this



522
523
524
525
526
527
528
529
530
531
532
# File 'app/models/actor.rb', line 522

def new_like(subject, user)
  a = Activity.new :verb           => "like",
                   :author_id      => Actor.normalize_id(subject),
                   :user_author_id => Actor.normalize_id(user),
                   :owner_id       => id,
                   :relation_ids   => Array(Relation::Public.instance.id)
  
  a.activity_objects << activity_object           
                  
  a             
end

#pending_contactsObject

Build a new Contact from each that has not inverse



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

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)


441
442
443
# File 'app/models/actor.rb', line 441

def pending_contacts?
  pending_contacts_count > 0
end

#pending_contacts_countObject



437
438
439
# File 'app/models/actor.rb', line 437

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_notifysObject

All relations with the ‘notify’ permission



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

def relation_notifys
  relations.joins(:relation_permissions => :permission).where('permissions.action' => 'notify')
end

#represented_by?(subject) ⇒ Boolean

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

Returns:

  • (Boolean)


379
380
381
382
383
384
385
386
387
388
# File 'app/models/actor.rb', line 379

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

#self_contactObject Also known as: ego_contact

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



300
301
302
# File 'app/models/actor.rb', line 300

def self_contact
  contact_to!(self)
end

#sent_active_contact_idsObject



343
344
345
346
# File 'app/models/actor.rb', line 343

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:



351
352
353
354
355
356
357
358
359
# File 'app/models/actor.rb', line 351

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



362
363
364
# File 'app/models/actor.rb', line 362

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)


367
368
369
# File 'app/models/actor.rb', line 367

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

#to_paramObject

Use slug as parameter



535
536
537
# File 'app/models/actor.rb', line 535

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.



471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
# File 'app/models/actor.rb', line 471

def wall(type, options = {})
  options[:for] = self if type == :home

  wall =
    Activity.
      select("DISTINCT activities.*").
      roots.
      includes(:author, :user_author, :owner, :activity_objects, :activity_verb, :relations)

  actor_ids =
    case type
    when :home
      following_actor_and_self_ids
    when :profile
      id
    else
      raise "Unknown type of wall: #{ type }"
    end

  wall = wall.authored_or_owned_by(actor_ids)

  # Authentication
  wall = wall.shared_with(options[:for])

  wall = wall.order("created_at desc")
end