Module: Moderate::Actor

Extended by:
ActiveSupport::Concern
Includes:
Reportable
Defined in:
lib/moderate/models/concerns/actor.rb

Overview

The “person who acts” in the Trust & Safety system: the model that reports other content, blocks other actors, gets reported, gets banned. Backs the ‘has_reporting_and_blocking` macro (and its documented equivalent, `include Moderate::Actor`).

This is the one model the gem treats as the actor/identity, configured via ‘config.user_class`. There’s normally exactly one such model per app (“User”, “Account”, “Member”), and it is BOTH an actor and itself reportable — Apple 1.2 and Google Play UGC both require reporting and blocking users, not just content (docs/compliance.md). So including Actor also includes Moderate::Reportable, giving a user the reportable contract with sensible actor-flavored defaults (a user IS its own ‘reported_owner`).

Everything host-specific (what “banned” means, whether a block tears down a pending invite) is delegated to the configured hooks via the Moderate facade, never hard-coded here — keeping the actor host-agnostic.

Instance Method Summary collapse

Methods included from Reportable

#flagged?, #moderation_admin_path, #moderation_flags, #moderation_label, #moderation_return_path, #moderation_snapshot, #moderation_subject_url, #open_reports, #pending_moderation_flags, #removable_reported_field?, #remove_reported_field!, #reportable_field_allowed?, #reported?

Instance Method Details

#block!(other) ⇒ Object

Block ‘other` (idempotent, audited, fires the `on_block` hook). The actual create/audit/notify is owned by Moderate::Block.block! so there’s one block write path for the whole gem.



122
123
124
# File 'lib/moderate/models/concerns/actor.rb', line 122

def block!(other)
  Moderate::Block.block!(blocker: self, blocked: other)
end

#blocked_by?(other) ⇒ Boolean

Did they block me? (the other direction)

Returns:

  • (Boolean)


140
141
142
143
144
# File 'lib/moderate/models/concerns/actor.rb', line 140

def blocked_by?(other)
  return false if other.blank?

  moderate_received_blocks.exists?(blocker_id: other.id)
end

#blocked_with?(other) ⇒ Boolean

Is there a block edge in EITHER direction? This is the predicate to check in product code (“can these two interact?”) — blocking is symmetric for visibility/reachability even though only one side pressed the button. A self-check is never “blocked.”

Returns:

  • (Boolean)


150
151
152
153
154
# File 'lib/moderate/models/concerns/actor.rb', line 150

def blocked_with?(other)
  return false if other.blank? || other.id == id

  blocks?(other) || blocked_by?(other)
end

#blocks?(other) ⇒ Boolean

Did I block them? (one direction)

Returns:

  • (Boolean)


133
134
135
136
137
# File 'lib/moderate/models/concerns/actor.rb', line 133

def blocks?(other)
  return false if other.blank?

  moderate_initiated_blocks.exists?(blocked_id: other.id)
end

#report!(reportable, category:, details: nil, **attributes) ⇒ Object

File a report from this actor against a piece of content (or another actor —a user with ‘has_reporting_and_blocking` is itself reportable).

current_user.report!(@message, category: :harassment, details: "...")
current_user.report!(@other_user, category: :impersonation)

We build and persist a Moderate::Report; the Report model owns the rest of the lifecycle the README promises — snapshotting the offending content so evidence survives edits/deletes, inferring the responsible owner, sending the reporter a receipt, and dropping it into the queue. Keeping that logic IN the model (not here) means the public DSA notice intake and this in-app path share one source of truth.

‘details:` is the README’s name for the reporter’s free-text reason; it maps onto the Report’s ‘message`. Extra keyword args (e.g. `field:`) pass straight through, so this stays forward-compatible with the Report model’s attributes.

Raises:

  • (ActiveRecord::RecordInvalid)


78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/moderate/models/concerns/actor.rb', line 78

def report!(reportable, category:, details: nil, **attributes)
  attributes[:message] = details if details && !attributes.key?(:message)
  reported_field = attributes.delete(:reported_field)
  field = attributes.delete(:field)
  reported_field ||= field

  # An in-app reporter attests to good faith IMPLICITLY by choosing to report —
  # there's no separate checkbox in the in-app flow (that's the public DSA notice
  # form's job). The Report model requires `good_faith_confirmed` to be truthy
  # (Art. 16(2)(d)), so we set it here for the community path unless the caller
  # already passed it. (A host that wants an explicit in-app attestation can still
  # override by passing `good_faith_confirmed:` in `attributes`.)
  attributes[:good_faith_confirmed] = true unless attributes.key?(:good_faith_confirmed)

  report = Moderate::Report.new(
    reporter: self,
    reportable: reportable,
    category: category.to_s,
    intake_kind: "community",
    **attributes
  )

  intake = Moderate::Services::IntakeReport.new(
    report: report,
    reporter: self,
    reportable: reportable,
    reported_field: reported_field
  )
  return report if intake.save

  raise ActiveRecord::RecordInvalid, report
end

#report_visible_to?(viewer, field:) ⇒ Boolean

You can never report yourself, and a field still has to be reportable. This tightens Reportable’s default with the self-report guard, so the ‘moderate_report_link` helper hides the control on your own profile.

Returns:

  • (Boolean)


170
171
172
# File 'lib/moderate/models/concerns/actor.rb', line 170

def report_visible_to?(viewer, field:)
  viewer.present? && viewer.id != id && reportable_field_allowed?(field)
end

#reported_ownerObject

A user is responsible for themselves — so a report against a user (e.g. for impersonation) attributes to and notifies that same user.



163
164
165
# File 'lib/moderate/models/concerns/actor.rb', line 163

def reported_owner
  self
end

#unblock!(other) ⇒ Object

Lift a block this actor placed on ‘other`. No-op (returns false) if no such block exists.



128
129
130
# File 'lib/moderate/models/concerns/actor.rb', line 128

def unblock!(other)
  Moderate::Block.unblock!(blocker: self, blocked: other)
end