about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2019-03-27 15:55:23 +0100
committerGitHub <noreply@github.com>2019-03-27 15:55:23 +0100
commit11fe293e1b318a12b75f0c5d1bb208fdbb46417e (patch)
treeb60386ef14602dd63e669d95f990d11625a8db48
parenta91acf79b53307ada584f449da1369c0216a24d1 (diff)
Remove unused ActivityPub `@context` values depending on response (#10378)
Fix #8078
-rw-r--r--app/lib/activitypub/adapter.rb75
-rw-r--r--app/lib/activitypub/serializer.rb30
-rw-r--r--app/serializers/activitypub/accept_follow_serializer.rb2
-rw-r--r--app/serializers/activitypub/activity_serializer.rb2
-rw-r--r--app/serializers/activitypub/actor_serializer.rb13
-rw-r--r--app/serializers/activitypub/add_serializer.rb2
-rw-r--r--app/serializers/activitypub/block_serializer.rb2
-rw-r--r--app/serializers/activitypub/collection_serializer.rb2
-rw-r--r--app/serializers/activitypub/delete_actor_serializer.rb2
-rw-r--r--app/serializers/activitypub/delete_serializer.rb6
-rw-r--r--app/serializers/activitypub/emoji_serializer.rb4
-rw-r--r--app/serializers/activitypub/flag_serializer.rb2
-rw-r--r--app/serializers/activitypub/follow_serializer.rb2
-rw-r--r--app/serializers/activitypub/image_serializer.rb4
-rw-r--r--app/serializers/activitypub/like_serializer.rb2
-rw-r--r--app/serializers/activitypub/note_serializer.rb15
-rw-r--r--app/serializers/activitypub/public_key_serializer.rb4
-rw-r--r--app/serializers/activitypub/reject_follow_serializer.rb2
-rw-r--r--app/serializers/activitypub/remove_serializer.rb2
-rw-r--r--app/serializers/activitypub/undo_announce_serializer.rb2
-rw-r--r--app/serializers/activitypub/undo_block_serializer.rb2
-rw-r--r--app/serializers/activitypub/undo_follow_serializer.rb2
-rw-r--r--app/serializers/activitypub/undo_like_serializer.rb2
-rw-r--r--app/serializers/activitypub/update_poll_serializer.rb2
-rw-r--r--app/serializers/activitypub/update_serializer.rb2
-rw-r--r--app/serializers/activitypub/vote_serializer.rb4
-rw-r--r--config/initializers/active_model_serializers.rb19
-rw-r--r--spec/lib/activitypub/adapter_spec.rb88
28 files changed, 235 insertions, 61 deletions
diff --git a/app/lib/activitypub/adapter.rb b/app/lib/activitypub/adapter.rb
index 99f4d9305..7e0b16c25 100644
--- a/app/lib/activitypub/adapter.rb
+++ b/app/lib/activitypub/adapter.rb
@@ -1,30 +1,23 @@
 # frozen_string_literal: true
 
 class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base
-  CONTEXT = {
-    '@context': [
-      'https://www.w3.org/ns/activitystreams',
-      'https://w3id.org/security/v1',
-
-      {
-        'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers',
-        'sensitive'                 => 'as:sensitive',
-        'movedTo'                   => { '@id' => 'as:movedTo', '@type' => '@id' },
-        'alsoKnownAs'               => { '@id' => 'as:alsoKnownAs', '@type' => '@id' },
-        'Hashtag'                   => 'as:Hashtag',
-        'ostatus'                   => 'http://ostatus.org#',
-        'atomUri'                   => 'ostatus:atomUri',
-        'inReplyToAtomUri'          => 'ostatus:inReplyToAtomUri',
-        'conversation'              => 'ostatus:conversation',
-        'toot'                      => 'http://joinmastodon.org/ns#',
-        'Emoji'                     => 'toot:Emoji',
-        'focalPoint'                => { '@container' => '@list', '@id' => 'toot:focalPoint' },
-        'featured'                  => { '@id' => 'toot:featured', '@type' => '@id' },
-        'schema'                    => 'http://schema.org#',
-        'PropertyValue'             => 'schema:PropertyValue',
-        'value'                     => 'schema:value',
-      },
-    ],
+  NAMED_CONTEXT_MAP = {
+    activitystreams: 'https://www.w3.org/ns/activitystreams',
+    security: 'https://w3id.org/security/v1',
+  }.freeze
+
+  CONTEXT_EXTENSION_MAP = {
+    manually_approves_followers: { 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers' },
+    sensitive: { 'sensitive' => 'as:sensitive' },
+    hashtag: { 'Hashtag' => 'as:Hashtag' },
+    moved_to: { 'movedTo' => { '@id' => 'as:movedTo', '@type' => '@id' } },
+    also_known_as: { 'alsoKnownAs' => { '@id' => 'as:alsoKnownAs', '@type' => '@id' } },
+    emoji: { 'toot' => 'http://joinmastodon.org/ns#', 'Emoji' => 'toot:Emoji' },
+    featured: { 'toot' => 'http://joinmastodon.org/ns#', 'featured' => { '@id' => 'toot:featured', '@type' => '@id' } },
+    property_value: { 'schema' => 'http://schema.org#', 'PropertyValue' => 'schema:PropertyValue', 'value' => 'schema:value' },
+    atom_uri: { 'ostatus' => 'http://ostatus.org#', 'atomUri' => 'ostatus:atomUri' },
+    conversation: { 'ostatus' => 'http://ostatus.org#', 'inReplyToAtomUri' => 'ostatus:inReplyToAtomUri', 'conversation' => 'ostatus:conversation' },
+    focal_point: { 'toot' => 'http://joinmastodon.org/ns#', 'focalPoint' => { '@container' => '@list', '@id' => 'toot:focalPoint' } },
   }.freeze
 
   def self.default_key_transform
@@ -36,8 +29,36 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base
   end
 
   def serializable_hash(options = nil)
-    options = serialization_options(options)
-    serialized_hash = ActiveModelSerializers::Adapter::Attributes.new(serializer, instance_options).serializable_hash(options)
-    CONTEXT.merge(self.class.transform_key_casing!(serialized_hash, instance_options))
+    options         = serialization_options(options)
+    serialized_hash = serializer.serializable_hash(options)
+    serialized_hash = self.class.transform_key_casing!(serialized_hash, instance_options)
+
+    { '@context' => serialized_context }.merge(serialized_hash)
+  end
+
+  private
+
+  def serialized_context
+    context_array = []
+
+    serializer_options = serializer.send(:instance_options) || {}
+    named_contexts     = [:activitystreams] + serializer._named_contexts.keys + serializer_options.fetch(:named_contexts, {}).keys
+    context_extensions = serializer._context_extensions.keys + serializer_options.fetch(:context_extensions, {}).keys
+
+    named_contexts.each do |key|
+      context_array << NAMED_CONTEXT_MAP[key]
+    end
+
+    extensions = context_extensions.each_with_object({}) do |key, h|
+      h.merge!(CONTEXT_EXTENSION_MAP[key])
+    end
+
+    context_array << extensions unless extensions.empty?
+
+    if context_array.size == 1
+      context_array.first
+    else
+      context_array
+    end
   end
 end
diff --git a/app/lib/activitypub/serializer.rb b/app/lib/activitypub/serializer.rb
new file mode 100644
index 000000000..07bd8c494
--- /dev/null
+++ b/app/lib/activitypub/serializer.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+class ActivityPub::Serializer < ActiveModel::Serializer
+  with_options instance_writer: false, instance_reader: true do |serializer|
+    serializer.class_attribute :_named_contexts
+    serializer.class_attribute :_context_extensions
+
+    self._named_contexts     ||= {}
+    self._context_extensions ||= {}
+  end
+
+  def self.inherited(base)
+    super
+
+    base._named_contexts     = _named_contexts.dup
+    base._context_extensions = _context_extensions.dup
+  end
+
+  def self.context(*named_contexts)
+    named_contexts.each do |context|
+      _named_contexts[context] = true
+    end
+  end
+
+  def self.context_extensions(*extension_names)
+    extension_names.each do |extension_name|
+      _context_extensions[extension_name] = true
+    end
+  end
+end
diff --git a/app/serializers/activitypub/accept_follow_serializer.rb b/app/serializers/activitypub/accept_follow_serializer.rb
index 3e23591a5..1c1c6ab73 100644
--- a/app/serializers/activitypub/accept_follow_serializer.rb
+++ b/app/serializers/activitypub/accept_follow_serializer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ActivityPub::AcceptFollowSerializer < ActiveModel::Serializer
+class ActivityPub::AcceptFollowSerializer < ActivityPub::Serializer
   attributes :id, :type, :actor
 
   has_one :object, serializer: ActivityPub::FollowSerializer
diff --git a/app/serializers/activitypub/activity_serializer.rb b/app/serializers/activitypub/activity_serializer.rb
index c001e28aa..c06d5c87c 100644
--- a/app/serializers/activitypub/activity_serializer.rb
+++ b/app/serializers/activitypub/activity_serializer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ActivityPub::ActivitySerializer < ActiveModel::Serializer
+class ActivityPub::ActivitySerializer < ActivityPub::Serializer
   attributes :id, :type, :actor, :published, :to, :cc
 
   has_one :proper, key: :object, serializer: ActivityPub::NoteSerializer, if: :serialize_object?
diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb
index 6746c1782..4b982b955 100644
--- a/app/serializers/activitypub/actor_serializer.rb
+++ b/app/serializers/activitypub/actor_serializer.rb
@@ -1,8 +1,13 @@
 # frozen_string_literal: true
 
-class ActivityPub::ActorSerializer < ActiveModel::Serializer
+class ActivityPub::ActorSerializer < ActivityPub::Serializer
   include RoutingHelper
 
+  context :security
+
+  context_extensions :manually_approves_followers, :featured, :also_known_as,
+                     :moved_to, :property_value, :hashtag, :emoji
+
   attributes :id, :type, :following, :followers,
              :inbox, :outbox, :featured,
              :preferred_username, :name, :summary,
@@ -16,7 +21,7 @@ class ActivityPub::ActorSerializer < ActiveModel::Serializer
   attribute :moved_to, if: :moved?
   attribute :also_known_as, if: :also_known_as?
 
-  class EndpointsSerializer < ActiveModel::Serializer
+  class EndpointsSerializer < ActivityPub::Serializer
     include RoutingHelper
 
     attributes :shared_inbox
@@ -124,7 +129,7 @@ class ActivityPub::ActorSerializer < ActiveModel::Serializer
   class CustomEmojiSerializer < ActivityPub::EmojiSerializer
   end
 
-  class TagSerializer < ActiveModel::Serializer
+  class TagSerializer < ActivityPub::Serializer
     include RoutingHelper
 
     attributes :type, :href, :name
@@ -142,7 +147,7 @@ class ActivityPub::ActorSerializer < ActiveModel::Serializer
     end
   end
 
-  class Account::FieldSerializer < ActiveModel::Serializer
+  class Account::FieldSerializer < ActivityPub::Serializer
     attributes :type, :name, :value
 
     def type
diff --git a/app/serializers/activitypub/add_serializer.rb b/app/serializers/activitypub/add_serializer.rb
index c0906e8d0..6f5aab17f 100644
--- a/app/serializers/activitypub/add_serializer.rb
+++ b/app/serializers/activitypub/add_serializer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ActivityPub::AddSerializer < ActiveModel::Serializer
+class ActivityPub::AddSerializer < ActivityPub::Serializer
   include RoutingHelper
 
   attributes :type, :actor, :target
diff --git a/app/serializers/activitypub/block_serializer.rb b/app/serializers/activitypub/block_serializer.rb
index 624ce2fce..e6c69329d 100644
--- a/app/serializers/activitypub/block_serializer.rb
+++ b/app/serializers/activitypub/block_serializer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ActivityPub::BlockSerializer < ActiveModel::Serializer
+class ActivityPub::BlockSerializer < ActivityPub::Serializer
   attributes :id, :type, :actor
   attribute :virtual_object, key: :object
 
diff --git a/app/serializers/activitypub/collection_serializer.rb b/app/serializers/activitypub/collection_serializer.rb
index b03609957..da1ba735f 100644
--- a/app/serializers/activitypub/collection_serializer.rb
+++ b/app/serializers/activitypub/collection_serializer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ActivityPub::CollectionSerializer < ActiveModel::Serializer
+class ActivityPub::CollectionSerializer < ActivityPub::Serializer
   def self.serializer_for(model, options)
     return ActivityPub::NoteSerializer if model.class.name == 'Status'
     return ActivityPub::CollectionSerializer if model.class.name == 'ActivityPub::CollectionPresenter'
diff --git a/app/serializers/activitypub/delete_actor_serializer.rb b/app/serializers/activitypub/delete_actor_serializer.rb
index ddf59be97..a6c5e2385 100644
--- a/app/serializers/activitypub/delete_actor_serializer.rb
+++ b/app/serializers/activitypub/delete_actor_serializer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ActivityPub::DeleteActorSerializer < ActiveModel::Serializer
+class ActivityPub::DeleteActorSerializer < ActivityPub::Serializer
   attributes :id, :type, :actor, :to
   attribute :virtual_object, key: :object
 
diff --git a/app/serializers/activitypub/delete_serializer.rb b/app/serializers/activitypub/delete_serializer.rb
index 5012a8383..a7d5bd469 100644
--- a/app/serializers/activitypub/delete_serializer.rb
+++ b/app/serializers/activitypub/delete_serializer.rb
@@ -1,7 +1,9 @@
 # frozen_string_literal: true
 
-class ActivityPub::DeleteSerializer < ActiveModel::Serializer
-  class TombstoneSerializer < ActiveModel::Serializer
+class ActivityPub::DeleteSerializer < ActivityPub::Serializer
+  class TombstoneSerializer < ActivityPub::Serializer
+    context_extensions :atom_uri
+
     attributes :id, :type, :atom_uri
 
     def id
diff --git a/app/serializers/activitypub/emoji_serializer.rb b/app/serializers/activitypub/emoji_serializer.rb
index 7b06b1e5d..4dc38f3ea 100644
--- a/app/serializers/activitypub/emoji_serializer.rb
+++ b/app/serializers/activitypub/emoji_serializer.rb
@@ -1,8 +1,10 @@
 # frozen_string_literal: true
 
-class ActivityPub::EmojiSerializer < ActiveModel::Serializer
+class ActivityPub::EmojiSerializer < ActivityPub::Serializer
   include RoutingHelper
 
+  context_extensions :emoji
+
   attributes :id, :type, :name, :updated
 
   has_one :icon, serializer: ActivityPub::ImageSerializer
diff --git a/app/serializers/activitypub/flag_serializer.rb b/app/serializers/activitypub/flag_serializer.rb
index 1e7a46dd9..2f2a707d3 100644
--- a/app/serializers/activitypub/flag_serializer.rb
+++ b/app/serializers/activitypub/flag_serializer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ActivityPub::FlagSerializer < ActiveModel::Serializer
+class ActivityPub::FlagSerializer < ActivityPub::Serializer
   attributes :id, :type, :actor, :content
   attribute :virtual_object, key: :object
 
diff --git a/app/serializers/activitypub/follow_serializer.rb b/app/serializers/activitypub/follow_serializer.rb
index bb204ee8f..9228d7716 100644
--- a/app/serializers/activitypub/follow_serializer.rb
+++ b/app/serializers/activitypub/follow_serializer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ActivityPub::FollowSerializer < ActiveModel::Serializer
+class ActivityPub::FollowSerializer < ActivityPub::Serializer
   attributes :id, :type, :actor
   attribute :virtual_object, key: :object
 
diff --git a/app/serializers/activitypub/image_serializer.rb b/app/serializers/activitypub/image_serializer.rb
index 3c08f77e8..1060f9691 100644
--- a/app/serializers/activitypub/image_serializer.rb
+++ b/app/serializers/activitypub/image_serializer.rb
@@ -1,8 +1,10 @@
 # frozen_string_literal: true
 
-class ActivityPub::ImageSerializer < ActiveModel::Serializer
+class ActivityPub::ImageSerializer < ActivityPub::Serializer
   include RoutingHelper
 
+  context_extensions :focal_point
+
   attributes :type, :media_type, :url
   attribute :focal_point, if: :focal_point?
 
diff --git a/app/serializers/activitypub/like_serializer.rb b/app/serializers/activitypub/like_serializer.rb
index c1a7ff6f6..0f170ddb4 100644
--- a/app/serializers/activitypub/like_serializer.rb
+++ b/app/serializers/activitypub/like_serializer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ActivityPub::LikeSerializer < ActiveModel::Serializer
+class ActivityPub::LikeSerializer < ActivityPub::Serializer
   attributes :id, :type, :actor
   attribute :virtual_object, key: :object
 
diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb
index 553f333d8..0666bea5a 100644
--- a/app/serializers/activitypub/note_serializer.rb
+++ b/app/serializers/activitypub/note_serializer.rb
@@ -1,6 +1,9 @@
 # frozen_string_literal: true
 
-class ActivityPub::NoteSerializer < ActiveModel::Serializer
+class ActivityPub::NoteSerializer < ActivityPub::Serializer
+  context_extensions :atom_uri, :conversation, :sensitive,
+                     :hashtag, :emoji, :focal_point
+
   attributes :id, :type, :summary,
              :in_reply_to, :published, :url,
              :attributed_to, :to, :cc, :sensitive,
@@ -147,7 +150,7 @@ class ActivityPub::NoteSerializer < ActiveModel::Serializer
     object.poll&.expired?
   end
 
-  class MediaAttachmentSerializer < ActiveModel::Serializer
+  class MediaAttachmentSerializer < ActivityPub::Serializer
     include RoutingHelper
 
     attributes :type, :media_type, :url, :name
@@ -178,7 +181,7 @@ class ActivityPub::NoteSerializer < ActiveModel::Serializer
     end
   end
 
-  class MentionSerializer < ActiveModel::Serializer
+  class MentionSerializer < ActivityPub::Serializer
     attributes :type, :href, :name
 
     def type
@@ -194,7 +197,7 @@ class ActivityPub::NoteSerializer < ActiveModel::Serializer
     end
   end
 
-  class TagSerializer < ActiveModel::Serializer
+  class TagSerializer < ActivityPub::Serializer
     include RoutingHelper
 
     attributes :type, :href, :name
@@ -215,8 +218,8 @@ class ActivityPub::NoteSerializer < ActiveModel::Serializer
   class CustomEmojiSerializer < ActivityPub::EmojiSerializer
   end
 
-  class OptionSerializer < ActiveModel::Serializer
-    class RepliesSerializer < ActiveModel::Serializer
+  class OptionSerializer < ActivityPub::Serializer
+    class RepliesSerializer < ActivityPub::Serializer
       attributes :type, :total_items
 
       def type
diff --git a/app/serializers/activitypub/public_key_serializer.rb b/app/serializers/activitypub/public_key_serializer.rb
index 38e9e93ba..62ed49e81 100644
--- a/app/serializers/activitypub/public_key_serializer.rb
+++ b/app/serializers/activitypub/public_key_serializer.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
-class ActivityPub::PublicKeySerializer < ActiveModel::Serializer
+class ActivityPub::PublicKeySerializer < ActivityPub::Serializer
+  context :security
+
   attributes :id, :owner, :public_key_pem
 
   def id
diff --git a/app/serializers/activitypub/reject_follow_serializer.rb b/app/serializers/activitypub/reject_follow_serializer.rb
index 7814f4f57..4996c9a3c 100644
--- a/app/serializers/activitypub/reject_follow_serializer.rb
+++ b/app/serializers/activitypub/reject_follow_serializer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ActivityPub::RejectFollowSerializer < ActiveModel::Serializer
+class ActivityPub::RejectFollowSerializer < ActivityPub::Serializer
   attributes :id, :type, :actor
 
   has_one :object, serializer: ActivityPub::FollowSerializer
diff --git a/app/serializers/activitypub/remove_serializer.rb b/app/serializers/activitypub/remove_serializer.rb
index c2a5ae1b3..7fefda59d 100644
--- a/app/serializers/activitypub/remove_serializer.rb
+++ b/app/serializers/activitypub/remove_serializer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ActivityPub::RemoveSerializer < ActiveModel::Serializer
+class ActivityPub::RemoveSerializer < ActivityPub::Serializer
   include RoutingHelper
 
   attributes :type, :actor, :target
diff --git a/app/serializers/activitypub/undo_announce_serializer.rb b/app/serializers/activitypub/undo_announce_serializer.rb
index 4fc042727..6758af679 100644
--- a/app/serializers/activitypub/undo_announce_serializer.rb
+++ b/app/serializers/activitypub/undo_announce_serializer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ActivityPub::UndoAnnounceSerializer < ActiveModel::Serializer
+class ActivityPub::UndoAnnounceSerializer < ActivityPub::Serializer
   attributes :id, :type, :actor, :to
 
   has_one :object, serializer: ActivityPub::ActivitySerializer
diff --git a/app/serializers/activitypub/undo_block_serializer.rb b/app/serializers/activitypub/undo_block_serializer.rb
index 2f43d8402..b4f049377 100644
--- a/app/serializers/activitypub/undo_block_serializer.rb
+++ b/app/serializers/activitypub/undo_block_serializer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ActivityPub::UndoBlockSerializer < ActiveModel::Serializer
+class ActivityPub::UndoBlockSerializer < ActivityPub::Serializer
   attributes :id, :type, :actor
 
   has_one :object, serializer: ActivityPub::BlockSerializer
diff --git a/app/serializers/activitypub/undo_follow_serializer.rb b/app/serializers/activitypub/undo_follow_serializer.rb
index e5b7f143d..9b3e0ca3c 100644
--- a/app/serializers/activitypub/undo_follow_serializer.rb
+++ b/app/serializers/activitypub/undo_follow_serializer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ActivityPub::UndoFollowSerializer < ActiveModel::Serializer
+class ActivityPub::UndoFollowSerializer < ActivityPub::Serializer
   attributes :id, :type, :actor
 
   has_one :object, serializer: ActivityPub::FollowSerializer
diff --git a/app/serializers/activitypub/undo_like_serializer.rb b/app/serializers/activitypub/undo_like_serializer.rb
index 25f4ccaae..20c786cb7 100644
--- a/app/serializers/activitypub/undo_like_serializer.rb
+++ b/app/serializers/activitypub/undo_like_serializer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ActivityPub::UndoLikeSerializer < ActiveModel::Serializer
+class ActivityPub::UndoLikeSerializer < ActivityPub::Serializer
   attributes :id, :type, :actor
 
   has_one :object, serializer: ActivityPub::LikeSerializer
diff --git a/app/serializers/activitypub/update_poll_serializer.rb b/app/serializers/activitypub/update_poll_serializer.rb
index f7933346f..a9a09747f 100644
--- a/app/serializers/activitypub/update_poll_serializer.rb
+++ b/app/serializers/activitypub/update_poll_serializer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ActivityPub::UpdatePollSerializer < ActiveModel::Serializer
+class ActivityPub::UpdatePollSerializer < ActivityPub::Serializer
   attributes :id, :type, :actor, :to
 
   has_one :object, serializer: ActivityPub::NoteSerializer
diff --git a/app/serializers/activitypub/update_serializer.rb b/app/serializers/activitypub/update_serializer.rb
index 48d7a1929..a5eb857d3 100644
--- a/app/serializers/activitypub/update_serializer.rb
+++ b/app/serializers/activitypub/update_serializer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ActivityPub::UpdateSerializer < ActiveModel::Serializer
+class ActivityPub::UpdateSerializer < ActivityPub::Serializer
   attributes :id, :type, :actor, :to
 
   has_one :object, serializer: ActivityPub::ActorSerializer
diff --git a/app/serializers/activitypub/vote_serializer.rb b/app/serializers/activitypub/vote_serializer.rb
index 248190404..71ef5c77e 100644
--- a/app/serializers/activitypub/vote_serializer.rb
+++ b/app/serializers/activitypub/vote_serializer.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
-class ActivityPub::VoteSerializer < ActiveModel::Serializer
-  class NoteSerializer < ActiveModel::Serializer
+class ActivityPub::VoteSerializer < ActivityPub::Serializer
+  class NoteSerializer < ActivityPub::Serializer
     attributes :id, :type, :name, :attributed_to,
                :in_reply_to, :to
 
diff --git a/config/initializers/active_model_serializers.rb b/config/initializers/active_model_serializers.rb
index 0e69e1d96..329a5fb2c 100644
--- a/config/initializers/active_model_serializers.rb
+++ b/config/initializers/active_model_serializers.rb
@@ -3,3 +3,22 @@ ActiveModelSerializers.config.tap do |config|
 end
 
 ActiveSupport::Notifications.unsubscribe(ActiveModelSerializers::Logging::RENDER_EVENT)
+
+class ActiveModel::Serializer::Reflection
+  # We monkey-patch this method so that when we include associations in a serializer,
+  # the nested serializers can send information about used contexts upwards back to
+  # the root. We do this via instance_options because the nesting can be dynamic.
+  def build_association(parent_serializer, parent_serializer_options, include_slice = {})
+    serializer = options[:serializer]
+
+    parent_serializer_options.merge!(named_contexts: serializer._named_contexts, context_extensions: serializer._context_extensions) if serializer.respond_to?(:_named_contexts)
+
+    association_options = {
+      parent_serializer: parent_serializer,
+      parent_serializer_options: parent_serializer_options,
+      include_slice: include_slice,
+    }
+
+    ActiveModel::Serializer::Association.new(self, association_options)
+  end
+end
diff --git a/spec/lib/activitypub/adapter_spec.rb b/spec/lib/activitypub/adapter_spec.rb
new file mode 100644
index 000000000..ea03797aa
--- /dev/null
+++ b/spec/lib/activitypub/adapter_spec.rb
@@ -0,0 +1,88 @@
+require 'rails_helper'
+
+RSpec.describe ActivityPub::Adapter do
+  class TestObject < ActiveModelSerializers::Model
+    attributes :foo
+  end
+
+  class TestWithBasicContextSerializer < ActivityPub::Serializer
+    attributes :foo
+  end
+
+  class TestWithNamedContextSerializer < ActivityPub::Serializer
+    context :security
+    attributes :foo
+  end
+
+  class TestWithNestedNamedContextSerializer < ActivityPub::Serializer
+    attributes :foo
+
+    has_one :virtual_object, key: :baz, serializer: TestWithNamedContextSerializer
+
+    def virtual_object
+      object
+    end
+  end
+
+  class TestWithContextExtensionSerializer < ActivityPub::Serializer
+    context_extensions :sensitive
+    attributes :foo
+  end
+
+  class TestWithNestedContextExtensionSerializer < ActivityPub::Serializer
+    context_extensions :manually_approves_followers
+    attributes :foo
+
+    has_one :virtual_object, key: :baz, serializer: TestWithContextExtensionSerializer
+
+    def virtual_object
+      object
+    end
+  end
+
+  describe '#serializable_hash' do
+    let(:serializer_class) {}
+
+    subject { ActiveModelSerializers::SerializableResource.new(TestObject.new(foo: 'bar'), serializer: serializer_class, adapter: described_class).as_json }
+
+    context 'when serializer defines no context' do
+      let(:serializer_class) { TestWithBasicContextSerializer }
+
+      it 'renders a basic @context' do
+        expect(subject).to include({ '@context' => 'https://www.w3.org/ns/activitystreams' })
+      end
+    end
+
+    context 'when serializer defines a named context' do
+      let(:serializer_class) { TestWithNamedContextSerializer }
+
+      it 'renders a @context with both items' do
+        expect(subject).to include({ '@context' => ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'] })
+      end
+    end
+
+    context 'when serializer has children that define a named context' do
+      let(:serializer_class) { TestWithNestedNamedContextSerializer }
+
+      it 'renders a @context with both items' do
+        expect(subject).to include({ '@context' => ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'] })
+      end
+    end
+
+    context 'when serializer defines context extensions' do
+      let(:serializer_class) { TestWithContextExtensionSerializer }
+
+      it 'renders a @context with the extension' do
+        expect(subject).to include({ '@context' => ['https://www.w3.org/ns/activitystreams', { 'sensitive' => 'as:sensitive' }] })
+      end
+    end
+
+    context 'when serializer has children that define context extensions' do
+      let(:serializer_class) { TestWithNestedContextExtensionSerializer }
+
+      it 'renders a @context with both extensions' do
+        expect(subject).to include({ '@context' => ['https://www.w3.org/ns/activitystreams', { 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers', 'sensitive' => 'as:sensitive' }] })
+      end
+    end
+  end
+end