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



131
132
133
134
135
# File 'app/models/actor.rb', line 131

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

  find_by_slug! $2
end

.normalize(a) ⇒ Object

Get actor from object, if possible



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'app/models/actor.rb', line 114

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



103
104
105
106
107
108
109
110
111
112
# File 'app/models/actor.rb', line 103

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



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

def activity_relations(subject, options = {})
  return relations if Actor.normalize(subject) == self

  Array.new
end

#activity_relations?(*args) ⇒ Boolean

Are there any activity_relations present?

Returns:

  • (Boolean)


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

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

#avatar!Object



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

def avatar!
  avatars.active.first || 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)


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

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?



330
331
332
333
# File 'app/models/actor.rb', line 330

def comment_relations(activity)
  activity.relations.select{ |r| r.is_a?(Relation::Public) } |
    Relation.allow(self, 'create', 'activity', :in => activity.relations)
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. Defaults to relations of custom type

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



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'app/models/actor.rb', line 181

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("DISTINCT actors.*").
             where('actors.subject_type' => subject_classes).
             includes(subject_types)
  
  
  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 "contact actors in both directions are not supported yet"
  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'))
  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



218
219
220
221
222
223
224
225
226
# File 'app/models/actor.rb', line 218

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.



229
230
231
# File 'app/models/actor.rb', line 229

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



234
235
236
237
# File 'app/models/actor.rb', line 234

def contact_to!(subject)
  contact_to(subject) ||
    sent_contacts.create!(:receiver_id => Actor.normalize_id(subject))
end

#liked_by(subject) ⇒ Object

:nodoc:



404
405
406
# File 'app/models/actor.rb', line 404

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)


409
410
411
# File 'app/models/actor.rb', line 409

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

#likesObject

The ‘like’ qualifications emmited to this actor



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

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

#logoObject



390
391
392
# File 'app/models/actor.rb', line 390

def 
  avatar!.
end

#mailboxer_emailObject

Returns the email used for Mailboxer



139
140
141
142
143
144
145
146
147
148
149
150
# File 'app/models/actor.rb', line 139

def mailboxer_email    
  return email if email.present?
  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|
      emails << receiver.email
    end
    return emails
  end
end

#new_like(subject) ⇒ Object

Build a new activity where subject like this



414
415
416
417
418
419
420
421
422
# File 'app/models/actor.rb', line 414

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



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

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

#relation_custom(name) ⇒ Object

A given relation defined and managed by this actor



164
165
166
# File 'app/models/actor.rb', line 164

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

#relation_customsObject

All the relations defined by this Actor



159
160
161
# File 'app/models/actor.rb', line 159

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

#relation_publicObject

The Relation::Public for this Actor



169
170
171
# File 'app/models/actor.rb', line 169

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

#represented_by?(subject) ⇒ Boolean

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

Returns:

  • (Boolean)


293
294
295
296
297
298
299
300
301
302
# File 'app/models/actor.rb', line 293

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

#should_email?(object) ⇒ Boolean

Returning whether an email should be sent for this object (Message or Notification). Required by Mailboxer gem.

Returns:

  • (Boolean)


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

def should_email?(object)
  return notify_by_email
end

#subjectObject

The subject instance for this actor



153
154
155
156
# File 'app/models/actor.rb', line 153

def subject
  subtype_instance ||
  activity_object.try(:object)
end

#suggestion(options = {}) ⇒ Contact

By now, it returns a tie suggesting a relation from SuggestedRelations to another subject without any current relation

Options
  • type: the class of the recommended subject

Returns:



255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'app/models/actor.rb', line 255

def suggestion(options = {})
  candidates_types =
    ( options[:type].present? ?
        Array(options[:type]) :
        self.class.subtypes )
  
  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 - contact_subjects(:type => klass.to_s.underscore, :direction => :sent, :relations => relations.to_a)
    cs -= Array(subject) if subject.is_a?(klass)
    cs
  end
  
  candidate = candidates[rand(candidates.size)]
  
  return nil unless candidate.present?
  
  contact_to!(candidate)
end

#suggestions(n) ⇒ Object

Make n suggestions TODO: make more



244
245
246
# File 'app/models/actor.rb', line 244

def suggestions(n)
  n.times.map{ |m| suggestion }
end

#ties_to(subject) ⇒ Object

Set of ties sent by this actor received by subject



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

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

#ties_to?(a) ⇒ Boolean

Returns:

  • (Boolean)


283
284
285
# File 'app/models/actor.rb', line 283

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

#to_paramObject

Use slug as parameter



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

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.



356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
# File 'app/models/actor.rb', line 356

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