From 2afa5c18659931b1ee53b04450222f11f1f209db Mon Sep 17 00:00:00 2001
From: Slava <53832230+slavalamp@users.noreply.github.com>
Date: Wed, 22 Apr 2026 00:13:25 +0000
Subject: [PATCH 1/9] AO3-6022 don't show hidden related works on related work
pages
---
app/controllers/related_works_controller.rb | 13 ++--
app/models/related_work.rb | 16 ++++-
app/views/users/_sidebar.html.erb | 2 +-
features/works/work_related.feature | 76 +++++++++++++++++++++
4 files changed, 99 insertions(+), 8 deletions(-)
diff --git a/app/controllers/related_works_controller.rb b/app/controllers/related_works_controller.rb
index b740940734..20fabe0c14 100644
--- a/app/controllers/related_works_controller.rb
+++ b/app/controllers/related_works_controller.rb
@@ -10,15 +10,20 @@ def index
@remixes_of_user = @user.related_works.posted.where(translation: false)
@translations_by_user = @user.parent_work_relationships.posted.where(translation: true)
@remixes_by_user = @user.parent_work_relationships.posted.where(translation: false)
+ return if logged_in_as_admin?
+ @translations_of_user = @translations_of_user.unhidden
+ @remixes_of_user = @remixes_of_user.unhidden
+ @translations_by_user = @translations_by_user.with_unhidden_parents
+ @remixes_by_user = @remixes_by_user.with_unhidden_parents
return if @user == current_user
# Extra constraints on what we display if someone else is viewing @user's
# related works page:
- @translations_of_user = @translations_of_user.merge(Work.revealed.non_anon).where(reciprocal: true)
- @remixes_of_user = @remixes_of_user.merge(Work.revealed.non_anon).where(reciprocal: true)
- @translations_by_user = @translations_by_user.merge(Work.revealed.non_anon).where(reciprocal: true)
- @remixes_by_user = @remixes_by_user.merge(Work.revealed.non_anon).where(reciprocal: true)
+ @translations_of_user = @translations_of_user.merge(Work.revealed.non_anon.unhidden).where(reciprocal: true)
+ @remixes_of_user = @remixes_of_user.merge(Work.revealed.non_anon.unhidden).where(reciprocal: true)
+ @translations_by_user = @translations_by_user.merge(Work.revealed.non_anon.unhidden).where(reciprocal: true)
+ @remixes_by_user = @remixes_by_user.merge(Work.revealed.non_anon.unhidden).where(reciprocal: true)
end
# GET /related_works/1
diff --git a/app/models/related_work.rb b/app/models/related_work.rb
index 97d7dc4a4d..82427c7bd0 100644
--- a/app/models/related_work.rb
+++ b/app/models/related_work.rb
@@ -7,9 +7,19 @@ class RelatedWork < ActiveRecord::Base
attribute :author, :string
attribute :language_id, :integer
- scope :posted, -> {
- joins("INNER JOIN `works` `child_works` ON `child_works`.`id` = `related_works`.`work_id`").
- where("child_works.posted = 1")
+ scope :posted, lambda {
+ joins("INNER JOIN `works` `child_works` ON `child_works`.`id` = `related_works`.`work_id`")
+ .where("child_works.posted = 1")
+ }
+
+ scope :with_unhidden_parents, lambda {
+ joins("INNER JOIN `works` `parent_works` ON `parent_works`.`id` = `related_works`.`parent_id`")
+ .where("parent_works.hidden_by_admin = false")
+ }
+
+ scope :unhidden, lambda {
+ joins("INNER JOIN `works` `child_works` ON `child_works`.`id` = `related_works`.`work_id`")
+ .where("child_works.hidden_by_admin = false")
}
before_validation :set_parent, if: :new_record?
diff --git a/app/views/users/_sidebar.html.erb b/app/views/users/_sidebar.html.erb
index c5836c303a..2c92d0e01a 100644
--- a/app/views/users/_sidebar.html.erb
+++ b/app/views/users/_sidebar.html.erb
@@ -61,7 +61,7 @@
<%= span_if_current t(".switch.sign_ups", signup_number: @user.challenge_signups.count), user_signups_path(@user) %>
<%= span_if_current t(".switch.assignments", assignment_number: @user.assignments.unposted.undefaulted.count), user_assignments_path(@user) %>
<%= span_if_current t(".switch.claims", claim_number: @user.request_claims.unposted.count), user_claims_path(@user) %>
- <%= span_if_current t(".switch.related_works", related_works_number: (@user.related_works.posted.count + @user.parent_work_relationships.count)), user_related_works_path(@user) %>
+ <%= span_if_current t(".switch.related_works", related_works_number: (@user.related_works.posted.unhidden.count + @user.parent_work_relationships.with_unhidden_parents.count)), user_related_works_path(@user) %>
<% end %>
<%= gifts_link(@user) %>
diff --git a/features/works/work_related.feature b/features/works/work_related.feature
index b68699c495..c91eb5e16a 100644
--- a/features/works/work_related.feature
+++ b/features/works/work_related.feature
@@ -373,6 +373,82 @@ Scenario: Anonymous works listed as inspiration should have links to the authors
Then I should see "Works inspired by this one: Followup by Anonymous"
And I should not see "remixer" within ".afterword .children"
+Scenario: Hidden inspired and inspiring works should not be listed on related work pages for users who can't access them
+ Given I have related works setup
+ And a related work has been posted and approved
+ When I am logged in as a "policy_and_abuse" admin
+ And I hide the work "Followup"
+ And I go to inspiration's related works page
+ Then I should see "Followup"
+ When I go to remixer's related works page
+ Then I should see "Followup"
+ When I am logged in as "remixer"
+ And I view my related works
+ Then I should see "Related Works (1)"
+ And I should see "Followup"
+ When I go to inspiration's related works page
+ Then I should not see "Followup"
+ When I am logged in as "inspiration"
+ And I view my related works
+ Then I should see "Related Works (0)"
+ And I should not see "Followup"
+
+ When I am logged in as a "policy_and_abuse" admin
+ And I unhide the work "Followup"
+ And I hide the work "Worldbuilding"
+ And I go to inspiration's related works page
+ Then I should see "Worldbuilding"
+ When I go to remixer's related works page
+ Then I should see "Worldbuilding"
+ When I am logged in as "inspiration"
+ And I view my related works
+ Then I should see "Related Works (1)"
+ And I should see "Worldbuilding"
+ When I go to remixer's related works page
+ Then I should not see "Worldbuilding"
+ When I am logged in as "remixer"
+ And I view my related works
+ Then I should see "Related Works (0)"
+ And I should not see "Worldbuilding"
+
+Scenario: Hidden translations and translated works should not be listed on related work pages for users who can't access them
+ Given I have related works setup
+ And a translation has been posted and approved
+ When I am logged in as a "policy_and_abuse" admin
+ And I hide the work "Worldbuilding Translated"
+ And I go to inspiration's related works page
+ Then I should see "Worldbuilding Translated"
+ When I go to translator's related works page
+ Then I should see "Worldbuilding Translated"
+ When I am logged in as "translator"
+ And I view my related works
+ Then I should see "Related Works (1)"
+ And I should see "Worldbuilding Translated"
+ When I go to inspiration's related works page
+ Then I should not see "Worldbuilding Translated"
+ When I am logged in as "inspiration"
+ And I view my related works
+ Then I should see "Related Works (0)"
+ And I should not see "Worldbuilding Translated"
+
+ When I am logged in as a "policy_and_abuse" admin
+ And I unhide the work "Worldbuilding Translated"
+ And I hide the work "Worldbuilding"
+ And I go to inspiration's related works page
+ Then I should see "Worldbuilding"
+ When I go to translator's related works page
+ Then I should see "Worldbuilding"
+ When I am logged in as "inspiration"
+ And I view my related works
+ Then I should see "Related Works (1)"
+ And I should see "Worldbuilding"
+ When I go to translator's related works page
+ Then I should not see "Worldbuilding"
+ When I am logged in as "translator"
+ And I view my related works
+ Then I should see "Related Works (0)"
+ And I should not see "Worldbuilding"
+
Scenario: When a user is notified that a co-authored work has been inspired by a work they posted,
the e-mail should link to each author's URL instead of showing escaped HTML
Given I have related works setup
From 981235f31239123a81e4ee68e6828202e0097ba4 Mon Sep 17 00:00:00 2001
From: Slava <53832230+slavalamp@users.noreply.github.com>
Date: Wed, 22 Apr 2026 18:04:31 +0200
Subject: [PATCH 2/9] AO3-6022 a hacky way to make it also work in tests
---
app/controllers/related_works_controller.rb | 23 +++++++++++++--------
app/models/related_work.rb | 13 +++++++++---
2 files changed, 24 insertions(+), 12 deletions(-)
diff --git a/app/controllers/related_works_controller.rb b/app/controllers/related_works_controller.rb
index 20fabe0c14..6d535ea492 100644
--- a/app/controllers/related_works_controller.rb
+++ b/app/controllers/related_works_controller.rb
@@ -12,18 +12,23 @@ def index
@remixes_by_user = @user.parent_work_relationships.posted.where(translation: false)
return if logged_in_as_admin?
+ translations_by_user_local = @translations_by_user.with_unhidden_parents
+ translations_by_user_external = @translations_by_user.with_unhidden_external_parents
+ remixes_by_user_local = @remixes_by_user.with_unhidden_parents
+ remixes_by_user_external = @remixes_by_user.with_unhidden_external_parents
+
@translations_of_user = @translations_of_user.unhidden
@remixes_of_user = @remixes_of_user.unhidden
- @translations_by_user = @translations_by_user.with_unhidden_parents
- @remixes_by_user = @remixes_by_user.with_unhidden_parents
- return if @user == current_user
+ if (@user == current_user)
+ @translations_by_user = (translations_by_user_local + translations_by_user_external).uniq
+ @remixes_by_user = (remixes_by_user_local + remixes_by_user_external).uniq
+ return
+ end
- # Extra constraints on what we display if someone else is viewing @user's
- # related works page:
- @translations_of_user = @translations_of_user.merge(Work.revealed.non_anon.unhidden).where(reciprocal: true)
- @remixes_of_user = @remixes_of_user.merge(Work.revealed.non_anon.unhidden).where(reciprocal: true)
- @translations_by_user = @translations_by_user.merge(Work.revealed.non_anon.unhidden).where(reciprocal: true)
- @remixes_by_user = @remixes_by_user.merge(Work.revealed.non_anon.unhidden).where(reciprocal: true)
+ @translations_of_user = @translations_of_user.visible_to_all
+ @remixes_of_user = @remixes_of_user.visible_to_all
+ @translations_by_user = (translations_by_user_local.visible_to_all + translations_by_user_external.visible_to_all).uniq
+ @remixes_by_user = (remixes_by_user_local.visible_to_all + remixes_by_user_external.visible_to_all).uniq
end
# GET /related_works/1
diff --git a/app/models/related_work.rb b/app/models/related_work.rb
index 82427c7bd0..b90d47b3d8 100644
--- a/app/models/related_work.rb
+++ b/app/models/related_work.rb
@@ -8,20 +8,27 @@ class RelatedWork < ActiveRecord::Base
attribute :language_id, :integer
scope :posted, lambda {
- joins("INNER JOIN `works` `child_works` ON `child_works`.`id` = `related_works`.`work_id`")
+ joins("INNER JOIN works child_works ON child_works.id = related_works.work_id")
.where("child_works.posted = 1")
}
scope :with_unhidden_parents, lambda {
- joins("INNER JOIN `works` `parent_works` ON `parent_works`.`id` = `related_works`.`parent_id`")
+ joins("INNER JOIN works parent_works ON parent_works.id = related_works.parent_id")
+ .where("parent_works.hidden_by_admin = false")
+ }
+
+ scope :with_unhidden_external_parents, lambda {
+ joins("INNER JOIN external_works parent_works ON parent_works.id = related_works.parent_id")
.where("parent_works.hidden_by_admin = false")
}
scope :unhidden, lambda {
- joins("INNER JOIN `works` `child_works` ON `child_works`.`id` = `related_works`.`work_id`")
+ joins("INNER JOIN works child_works ON child_works.id = related_works.work_id")
.where("child_works.hidden_by_admin = false")
}
+ scope :visible_to_all, -> { merge(Work.revealed.non_anon.unhidden).where(reciprocal: true) }
+
before_validation :set_parent, if: :new_record?
def set_parent
return if parent
From e579e38a2256a270b70832e7482962d286f6f1dd Mon Sep 17 00:00:00 2001
From: Slava <53832230+slavalamp@users.noreply.github.com>
Date: Thu, 23 Apr 2026 23:39:50 +0200
Subject: [PATCH 3/9] AO3-6022 refactored + sidebar counts should also be
correct in tests
---
app/controllers/related_works_controller.rb | 26 +++---------
app/models/related_work.rb | 46 ++++++++++++++++-----
app/views/users/_sidebar.html.erb | 2 +-
3 files changed, 43 insertions(+), 31 deletions(-)
diff --git a/app/controllers/related_works_controller.rb b/app/controllers/related_works_controller.rb
index 6d535ea492..1d34715da1 100644
--- a/app/controllers/related_works_controller.rb
+++ b/app/controllers/related_works_controller.rb
@@ -6,29 +6,15 @@ class RelatedWorksController < ApplicationController
def index
@page_subtitle = t(".page_title", login: @user.login)
- @translations_of_user = @user.related_works.posted.where(translation: true)
- @remixes_of_user = @user.related_works.posted.where(translation: false)
- @translations_by_user = @user.parent_work_relationships.posted.where(translation: true)
- @remixes_by_user = @user.parent_work_relationships.posted.where(translation: false)
- return if logged_in_as_admin?
- translations_by_user_local = @translations_by_user.with_unhidden_parents
- translations_by_user_external = @translations_by_user.with_unhidden_external_parents
- remixes_by_user_local = @remixes_by_user.with_unhidden_parents
- remixes_by_user_external = @remixes_by_user.with_unhidden_external_parents
+ @translations_of_user = @user.related_works.translations.visible_on_user_page(@user).visible
+ @remixes_of_user = @user.related_works.remixes.visible_on_user_page(@user).visible
- @translations_of_user = @translations_of_user.unhidden
- @remixes_of_user = @remixes_of_user.unhidden
- if (@user == current_user)
- @translations_by_user = (translations_by_user_local + translations_by_user_external).uniq
- @remixes_by_user = (remixes_by_user_local + remixes_by_user_external).uniq
- return
- end
+ translations_by_user = @user.parent_work_relationships.translations.visible_on_user_page(@user)
+ remixes_by_user = @user.parent_work_relationships.remixes.visible_on_user_page(@user)
- @translations_of_user = @translations_of_user.visible_to_all
- @remixes_of_user = @remixes_of_user.visible_to_all
- @translations_by_user = (translations_by_user_local.visible_to_all + translations_by_user_external.visible_to_all).uniq
- @remixes_by_user = (remixes_by_user_local.visible_to_all + remixes_by_user_external.visible_to_all).uniq
+ @translations_by_user = (translations_by_user.of_visible_local_works + translations_by_user.of_visible_external_works).sort
+ @remixes_by_user = (remixes_by_user.of_visible_local_works + remixes_by_user.of_visible_external_works).sort
end
# GET /related_works/1
diff --git a/app/models/related_work.rb b/app/models/related_work.rb
index b90d47b3d8..7ed2952c93 100644
--- a/app/models/related_work.rb
+++ b/app/models/related_work.rb
@@ -7,27 +7,53 @@ class RelatedWork < ActiveRecord::Base
attribute :author, :string
attribute :language_id, :integer
+ scope :translations, -> { where(translation: true) }
+ scope :remixes, -> { where(translation: false) }
+
scope :posted, lambda {
joins("INNER JOIN works child_works ON child_works.id = related_works.work_id")
.where("child_works.posted = 1")
}
- scope :with_unhidden_parents, lambda {
- joins("INNER JOIN works parent_works ON parent_works.id = related_works.parent_id")
- .where("parent_works.hidden_by_admin = false")
- }
-
- scope :with_unhidden_external_parents, lambda {
- joins("INNER JOIN external_works parent_works ON parent_works.id = related_works.parent_id")
- .where("parent_works.hidden_by_admin = false")
- }
+ scope :reciprocal, -> { where(reciprocal: true) }
scope :unhidden, lambda {
joins("INNER JOIN works child_works ON child_works.id = related_works.work_id")
.where("child_works.hidden_by_admin = false")
}
- scope :visible_to_all, -> { merge(Work.revealed.non_anon.unhidden).where(reciprocal: true) }
+ def self.visible_on_user_page(user)
+ if User.current_user.is_a?(Admin) || user == User.current_user
+ posted
+ else
+ posted.reciprocal.merge(Work.revealed.non_anon.unhidden)
+ end
+ end
+
+ scope :visible, -> { unhidden unless User.current_user.is_a?(Admin) }
+
+ scope :of_local_works, -> { where(parent_type: Work) }
+ scope :of_external_works, -> { where(parent_type: ExternalWork) }
+
+ scope :of_visible_local_works, lambda {
+ if User.current_user.is_a? Admin
+ of_local_works
+ else
+ of_local_works
+ .joins("INNER JOIN works parent_works ON parent_works.id = related_works.parent_id")
+ .where("parent_works.hidden_by_admin = false")
+ end
+ }
+
+ scope :of_visible_external_works, lambda {
+ if User.current_user.is_a? Admin
+ of_external_works
+ else
+ of_external_works
+ .joins("INNER JOIN external_works parent_works ON parent_works.id = related_works.parent_id")
+ .where("parent_works.hidden_by_admin = false")
+ end
+ }
before_validation :set_parent, if: :new_record?
def set_parent
diff --git a/app/views/users/_sidebar.html.erb b/app/views/users/_sidebar.html.erb
index 2c92d0e01a..c06ae80515 100644
--- a/app/views/users/_sidebar.html.erb
+++ b/app/views/users/_sidebar.html.erb
@@ -61,7 +61,7 @@
<%= span_if_current t(".switch.sign_ups", signup_number: @user.challenge_signups.count), user_signups_path(@user) %>
<%= span_if_current t(".switch.assignments", assignment_number: @user.assignments.unposted.undefaulted.count), user_assignments_path(@user) %>
<%= span_if_current t(".switch.claims", claim_number: @user.request_claims.unposted.count), user_claims_path(@user) %>
- <%= span_if_current t(".switch.related_works", related_works_number: (@user.related_works.posted.unhidden.count + @user.parent_work_relationships.with_unhidden_parents.count)), user_related_works_path(@user) %>
+ <%= span_if_current t(".switch.related_works", related_works_number: (@user.related_works.visible_on_user_page(@user).visible.count + @user.parent_work_relationships.visible_on_user_page(@user).of_visible_local_works.count + @user.parent_work_relationships.visible_on_user_page(@user).of_visible_external_works.count)), user_related_works_path(@user) %>
<% end %>
<%= gifts_link(@user) %>
From fb05ddf0abcef1de6becc20724bc3c3821ae9fc4 Mon Sep 17 00:00:00 2001
From: Slava <53832230+slavalamp@users.noreply.github.com>
Date: Tue, 12 May 2026 23:38:38 +0200
Subject: [PATCH 4/9] AO3-6022 hide hidden works from everyone
---
app/controllers/related_works_controller.rb | 15 +++++----
app/helpers/related_works_helper.rb | 10 ++++++
app/models/related_work.rb | 24 +++++---------
app/views/users/_sidebar.html.erb | 2 +-
features/works/work_related.feature | 36 ++++++++++-----------
5 files changed, 45 insertions(+), 42 deletions(-)
create mode 100644 app/helpers/related_works_helper.rb
diff --git a/app/controllers/related_works_controller.rb b/app/controllers/related_works_controller.rb
index 1d34715da1..d189c8544b 100644
--- a/app/controllers/related_works_controller.rb
+++ b/app/controllers/related_works_controller.rb
@@ -7,14 +7,15 @@ class RelatedWorksController < ApplicationController
def index
@page_subtitle = t(".page_title", login: @user.login)
- @translations_of_user = @user.related_works.translations.visible_on_user_page(@user).visible
- @remixes_of_user = @user.related_works.remixes.visible_on_user_page(@user).visible
+ related_works = @user.related_works.visible_on_user_page(@user).visible_works
+ parent_work_relationships = @user.parent_work_relationships.visible_on_user_page(@user)
+ local_parent_work_relationships = parent_work_relationships.of_visible_local_works
+ external_parent_work_relationships = parent_work_relationships.of_visible_external_works
- translations_by_user = @user.parent_work_relationships.translations.visible_on_user_page(@user)
- remixes_by_user = @user.parent_work_relationships.remixes.visible_on_user_page(@user)
-
- @translations_by_user = (translations_by_user.of_visible_local_works + translations_by_user.of_visible_external_works).sort
- @remixes_by_user = (remixes_by_user.of_visible_local_works + remixes_by_user.of_visible_external_works).sort
+ @translations_of_user = related_works.translations
+ @remixes_of_user = related_works.remixes
+ @translations_by_user = (local_parent_work_relationships.translations + external_parent_work_relationships.translations).sort
+ @remixes_by_user = (local_parent_work_relationships.remixes + external_parent_work_relationships.remixes).sort
end
# GET /related_works/1
diff --git a/app/helpers/related_works_helper.rb b/app/helpers/related_works_helper.rb
new file mode 100644
index 0000000000..c289ec8f95
--- /dev/null
+++ b/app/helpers/related_works_helper.rb
@@ -0,0 +1,10 @@
+module RelatedWorksHelper
+ def related_works_count(user)
+ related_works = user.related_works.visible_on_user_page(user).visible_works
+ parent_work_relationships = user.parent_work_relationships.visible_on_user_page(user)
+ local_parent_work_relationships = parent_work_relationships.of_visible_local_works
+ external_parent_work_relationships = parent_work_relationships.of_visible_external_works
+
+ return related_works.count + local_parent_work_relationships.count + external_parent_work_relationships.count
+ end
+end
diff --git a/app/models/related_work.rb b/app/models/related_work.rb
index 7ed2952c93..ed4be88e15 100644
--- a/app/models/related_work.rb
+++ b/app/models/related_work.rb
@@ -24,35 +24,27 @@ class RelatedWork < ActiveRecord::Base
def self.visible_on_user_page(user)
if User.current_user.is_a?(Admin) || user == User.current_user
- posted
+ posted.merge(Work.unhidden)
else
posted.reciprocal.merge(Work.revealed.non_anon.unhidden)
end
end
- scope :visible, -> { unhidden unless User.current_user.is_a?(Admin) }
+ scope :visible_works, -> { unhidden }
scope :of_local_works, -> { where(parent_type: Work) }
scope :of_external_works, -> { where(parent_type: ExternalWork) }
scope :of_visible_local_works, lambda {
- if User.current_user.is_a? Admin
- of_local_works
- else
- of_local_works
- .joins("INNER JOIN works parent_works ON parent_works.id = related_works.parent_id")
- .where("parent_works.hidden_by_admin = false")
- end
+ of_local_works
+ .joins("INNER JOIN works parent_works ON parent_works.id = related_works.parent_id")
+ .where("parent_works.hidden_by_admin = false")
}
scope :of_visible_external_works, lambda {
- if User.current_user.is_a? Admin
- of_external_works
- else
- of_external_works
- .joins("INNER JOIN external_works parent_works ON parent_works.id = related_works.parent_id")
- .where("parent_works.hidden_by_admin = false")
- end
+ of_external_works
+ .joins("INNER JOIN external_works parent_works ON parent_works.id = related_works.parent_id")
+ .where("parent_works.hidden_by_admin = false")
}
before_validation :set_parent, if: :new_record?
diff --git a/app/views/users/_sidebar.html.erb b/app/views/users/_sidebar.html.erb
index c06ae80515..7b23c7b158 100644
--- a/app/views/users/_sidebar.html.erb
+++ b/app/views/users/_sidebar.html.erb
@@ -61,7 +61,7 @@
<%= span_if_current t(".switch.sign_ups", signup_number: @user.challenge_signups.count), user_signups_path(@user) %>
<%= span_if_current t(".switch.assignments", assignment_number: @user.assignments.unposted.undefaulted.count), user_assignments_path(@user) %>
<%= span_if_current t(".switch.claims", claim_number: @user.request_claims.unposted.count), user_claims_path(@user) %>
- <%= span_if_current t(".switch.related_works", related_works_number: (@user.related_works.visible_on_user_page(@user).visible.count + @user.parent_work_relationships.visible_on_user_page(@user).of_visible_local_works.count + @user.parent_work_relationships.visible_on_user_page(@user).of_visible_external_works.count)), user_related_works_path(@user) %>
+ <%= span_if_current t(".switch.related_works", related_works_number: related_works_count(@user)), user_related_works_path(@user) %>
<% end %>
<%= gifts_link(@user) %>
diff --git a/features/works/work_related.feature b/features/works/work_related.feature
index c91eb5e16a..7ab816f4ec 100644
--- a/features/works/work_related.feature
+++ b/features/works/work_related.feature
@@ -373,19 +373,19 @@ Scenario: Anonymous works listed as inspiration should have links to the authors
Then I should see "Works inspired by this one: Followup by Anonymous"
And I should not see "remixer" within ".afterword .children"
-Scenario: Hidden inspired and inspiring works should not be listed on related work pages for users who can't access them
+Scenario: Hidden inspired and inspiring works should not be listed on related work pages
Given I have related works setup
And a related work has been posted and approved
When I am logged in as a "policy_and_abuse" admin
And I hide the work "Followup"
And I go to inspiration's related works page
- Then I should see "Followup"
+ Then I should not see "Followup"
When I go to remixer's related works page
- Then I should see "Followup"
+ Then I should not see "Followup"
When I am logged in as "remixer"
And I view my related works
- Then I should see "Related Works (1)"
- And I should see "Followup"
+ Then I should see "Related Works (0)"
+ And I should not see "Followup"
When I go to inspiration's related works page
Then I should not see "Followup"
When I am logged in as "inspiration"
@@ -397,13 +397,13 @@ Scenario: Hidden inspired and inspiring works should not be listed on related wo
And I unhide the work "Followup"
And I hide the work "Worldbuilding"
And I go to inspiration's related works page
- Then I should see "Worldbuilding"
+ Then I should not see "Worldbuilding"
When I go to remixer's related works page
- Then I should see "Worldbuilding"
+ Then I should not see "Worldbuilding"
When I am logged in as "inspiration"
And I view my related works
- Then I should see "Related Works (1)"
- And I should see "Worldbuilding"
+ Then I should see "Related Works (0)"
+ And I should not see "Worldbuilding"
When I go to remixer's related works page
Then I should not see "Worldbuilding"
When I am logged in as "remixer"
@@ -411,19 +411,19 @@ Scenario: Hidden inspired and inspiring works should not be listed on related wo
Then I should see "Related Works (0)"
And I should not see "Worldbuilding"
-Scenario: Hidden translations and translated works should not be listed on related work pages for users who can't access them
+Scenario: Hidden translations and translated works should not be listed on related work pages
Given I have related works setup
And a translation has been posted and approved
When I am logged in as a "policy_and_abuse" admin
And I hide the work "Worldbuilding Translated"
And I go to inspiration's related works page
- Then I should see "Worldbuilding Translated"
+ Then I should not see "Worldbuilding Translated"
When I go to translator's related works page
- Then I should see "Worldbuilding Translated"
+ Then I should not see "Worldbuilding Translated"
When I am logged in as "translator"
And I view my related works
- Then I should see "Related Works (1)"
- And I should see "Worldbuilding Translated"
+ Then I should see "Related Works (0)"
+ And I should not see "Worldbuilding Translated"
When I go to inspiration's related works page
Then I should not see "Worldbuilding Translated"
When I am logged in as "inspiration"
@@ -435,13 +435,13 @@ Scenario: Hidden translations and translated works should not be listed on relat
And I unhide the work "Worldbuilding Translated"
And I hide the work "Worldbuilding"
And I go to inspiration's related works page
- Then I should see "Worldbuilding"
+ Then I should not see "Worldbuilding"
When I go to translator's related works page
- Then I should see "Worldbuilding"
+ Then I should not see "Worldbuilding"
When I am logged in as "inspiration"
And I view my related works
- Then I should see "Related Works (1)"
- And I should see "Worldbuilding"
+ Then I should see "Related Works (0)"
+ And I should not see "Worldbuilding"
When I go to translator's related works page
Then I should not see "Worldbuilding"
When I am logged in as "translator"
From 28cddbbb674cec2e5b736e591f7ba9f4fe944992 Mon Sep 17 00:00:00 2001
From: Slava <53832230+slavalamp@users.noreply.github.com>
Date: Wed, 13 May 2026 17:06:16 +0200
Subject: [PATCH 5/9] AO3-6022 add comment + slightly rearrange the scopes
---
app/models/related_work.rb | 17 ++++++++++-------
1 file changed, 10 insertions(+), 7 deletions(-)
diff --git a/app/models/related_work.rb b/app/models/related_work.rb
index ed4be88e15..0b2a2e1b84 100644
--- a/app/models/related_work.rb
+++ b/app/models/related_work.rb
@@ -9,19 +9,13 @@ class RelatedWork < ActiveRecord::Base
scope :translations, -> { where(translation: true) }
scope :remixes, -> { where(translation: false) }
+ scope :reciprocal, -> { where(reciprocal: true) }
scope :posted, lambda {
joins("INNER JOIN works child_works ON child_works.id = related_works.work_id")
.where("child_works.posted = 1")
}
- scope :reciprocal, -> { where(reciprocal: true) }
-
- scope :unhidden, lambda {
- joins("INNER JOIN works child_works ON child_works.id = related_works.work_id")
- .where("child_works.hidden_by_admin = false")
- }
-
def self.visible_on_user_page(user)
if User.current_user.is_a?(Admin) || user == User.current_user
posted.merge(Work.unhidden)
@@ -30,8 +24,17 @@ def self.visible_on_user_page(user)
end
end
+ scope :unhidden, lambda {
+ joins("INNER JOIN works child_works ON child_works.id = related_works.work_id")
+ .where("child_works.hidden_by_admin = false")
+ }
+
scope :visible_works, -> { unhidden }
+ # Separate scopes for local and external works to make tests work
+ # (the join in of_visible_local_works gets both local and external works
+ # in web environments, but only local ones in automated tests)
+
scope :of_local_works, -> { where(parent_type: Work) }
scope :of_external_works, -> { where(parent_type: ExternalWork) }
From 047be41f3872dcacd0d7562fb4641b021f610a08 Mon Sep 17 00:00:00 2001
From: Slava <53832230+slavalamp@users.noreply.github.com>
Date: Wed, 13 May 2026 19:25:46 +0000
Subject: [PATCH 6/9] AO3-5550 hide relations that include restricted works
from guests
---
app/models/related_work.rb | 31 +++++++++++++++----
features/works/work_related.feature | 46 +++++++++++++++++++++++++++++
2 files changed, 72 insertions(+), 5 deletions(-)
diff --git a/app/models/related_work.rb b/app/models/related_work.rb
index 0b2a2e1b84..c7778f158d 100644
--- a/app/models/related_work.rb
+++ b/app/models/related_work.rb
@@ -19,8 +19,10 @@ class RelatedWork < ActiveRecord::Base
def self.visible_on_user_page(user)
if User.current_user.is_a?(Admin) || user == User.current_user
posted.merge(Work.unhidden)
- else
+ elsif User.current_user.is_a?(User)
posted.reciprocal.merge(Work.revealed.non_anon.unhidden)
+ else
+ posted.reciprocal.merge(Work.revealed.non_anon.unhidden.unrestricted)
end
end
@@ -29,21 +31,40 @@ def self.visible_on_user_page(user)
.where("child_works.hidden_by_admin = false")
}
- scope :visible_works, -> { unhidden }
+ scope :unrestricted, lambda {
+ joins("INNER JOIN works child_works ON child_works.id = related_works.work_id")
+ .where("child_works.restricted = false")
+ }
- # Separate scopes for local and external works to make tests work
- # (the join in of_visible_local_works gets both local and external works
+ scope :visible_works, -> {
+ if User.current_user.present?
+ unhidden
+ else
+ unhidden.unrestricted
+ end
+ }
+
+ # Separate scopes for local and external parent works to make tests work
+ # (the join in of_unhidden_local_works gets both local and external works
# in web environments, but only local ones in automated tests)
scope :of_local_works, -> { where(parent_type: Work) }
scope :of_external_works, -> { where(parent_type: ExternalWork) }
- scope :of_visible_local_works, lambda {
+ scope :of_unhidden_local_works, lambda {
of_local_works
.joins("INNER JOIN works parent_works ON parent_works.id = related_works.parent_id")
.where("parent_works.hidden_by_admin = false")
}
+ scope :of_visible_local_works, lambda {
+ if User.current_user.present?
+ of_unhidden_local_works
+ else
+ of_unhidden_local_works.where("parent_works.restricted = false")
+ end
+ }
+
scope :of_visible_external_works, lambda {
of_external_works
.joins("INNER JOIN external_works parent_works ON parent_works.id = related_works.parent_id")
diff --git a/features/works/work_related.feature b/features/works/work_related.feature
index 7ab816f4ec..7457e25e39 100644
--- a/features/works/work_related.feature
+++ b/features/works/work_related.feature
@@ -349,6 +349,52 @@ Scenario: Restricted works listed as Inspiration show up [Restricted] for guests
And I view the work "Followup"
Then I should see "Inspired by [Restricted Work] by inspiration"
+Scenario: Restricted inspired and inspiring works should not be listed on related work pages for guests
+ Given I have related works setup
+ And a related work has been posted and approved
+ When I am logged in as "remixer"
+ And I lock the work "Followup"
+ When I am logged out
+ And I go to inspiration's related works page
+ Then I should not see "Followup"
+ And I should not see "Worldbuilding"
+ When I go to remixer's related works page
+ Then I should not see "Followup"
+ And I should not see "Worldbuilding"
+
+ When I am logged in as "remixer"
+ And I unlock the work "Followup"
+ When I am logged in as "inspiration"
+ And I lock the work "Worldbuilding"
+ When I am logged out
+ And I go to inspiration's related works page
+ Then I should not see "Followup"
+ And I should not see "Worldbuilding"
+ When I go to remixer's related works page
+ Then I should not see "Followup"
+ And I should not see "Worldbuilding"
+
+Scenario: Restricted inspired and inspiring works should not be listed on related work pages for guests
+ Given I have related works setup
+ And a translation has been posted and approved
+ When I am logged in as "translator"
+ And I lock the work "Worldbuilding Translated"
+ When I am logged out
+ And I go to inspiration's related works page
+ Then I should not see "Worldbuilding"
+ When I go to translator's related works page
+ Then I should not see "Worldbuilding"
+
+ When I am logged in as "translator"
+ And I unlock the work "Worldbuilding Translated"
+ When I am logged in as "inspiration"
+ And I lock the work "Worldbuilding"
+ When I am logged out
+ And I go to inspiration's related works page
+ Then I should not see "Worldbuilding"
+ When I go to translator's related works page
+ Then I should not see "Worldbuilding"
+
Scenario: Anonymous works listed as inspiration should have links to the authors,
but only for the authors themselves and admins
Given I have related works setup
From 60aa30253f6e6a4e11218ee8bf8e07473559aaf1 Mon Sep 17 00:00:00 2001
From: Slava <53832230+slavalamp@users.noreply.github.com>
Date: Fri, 15 May 2026 02:20:11 +0000
Subject: [PATCH 7/9] AO3-6022 rubocop
---
app/helpers/related_works_helper.rb | 2 +-
app/models/related_work.rb | 11 ++++++-----
2 files changed, 7 insertions(+), 6 deletions(-)
diff --git a/app/helpers/related_works_helper.rb b/app/helpers/related_works_helper.rb
index c289ec8f95..d08df01c19 100644
--- a/app/helpers/related_works_helper.rb
+++ b/app/helpers/related_works_helper.rb
@@ -5,6 +5,6 @@ def related_works_count(user)
local_parent_work_relationships = parent_work_relationships.of_visible_local_works
external_parent_work_relationships = parent_work_relationships.of_visible_external_works
- return related_works.count + local_parent_work_relationships.count + external_parent_work_relationships.count
+ related_works.count + local_parent_work_relationships.count + external_parent_work_relationships.count
end
end
diff --git a/app/models/related_work.rb b/app/models/related_work.rb
index c7778f158d..064497572e 100644
--- a/app/models/related_work.rb
+++ b/app/models/related_work.rb
@@ -17,12 +17,13 @@ class RelatedWork < ActiveRecord::Base
}
def self.visible_on_user_page(user)
- if User.current_user.is_a?(Admin) || user == User.current_user
+ case User.current_user
+ when user || is_a?(Admin)
posted.merge(Work.unhidden)
- elsif User.current_user.is_a?(User)
- posted.reciprocal.merge(Work.revealed.non_anon.unhidden)
+ when is_a?(User)
+ posted.reciprocal.merge(Work.unhidden.revealed.non_anon)
else
- posted.reciprocal.merge(Work.revealed.non_anon.unhidden.unrestricted)
+ posted.reciprocal.merge(Work.unhidden.revealed.non_anon.unrestricted)
end
end
@@ -36,7 +37,7 @@ def self.visible_on_user_page(user)
.where("child_works.restricted = false")
}
- scope :visible_works, -> {
+ scope :visible_works, lambda {
if User.current_user.present?
unhidden
else
From b26e0bed7ff8f50436e19606cbeb2f204cb064f7 Mon Sep 17 00:00:00 2001
From: Slava <53832230+slavalamp@users.noreply.github.com>
Date: Sat, 16 May 2026 04:50:28 +0200
Subject: [PATCH 8/9] AO3-6022 test improvements
---
features/works/work_related.feature | 30 ++++++++++++++++-------------
1 file changed, 17 insertions(+), 13 deletions(-)
diff --git a/features/works/work_related.feature b/features/works/work_related.feature
index 7457e25e39..78d905f1d0 100644
--- a/features/works/work_related.feature
+++ b/features/works/work_related.feature
@@ -335,38 +335,39 @@ Scenario: Restricted works listed as Inspiration show up [Restricted] for guests
And a related work has been posted and approved
When I am logged in as "remixer"
And I lock the work "Followup"
- When I am logged out
+ When I log out
And I view the work "Worldbuilding"
Then I should see "[Restricted Work] by remixer"
When I am logged in as "remixer"
And I unlock the work "Followup"
- When I am logged out
+ When I log out
And I view the work "Followup"
Then I should see "Inspired by Worldbuilding by inspiration"
When I am logged in as "inspiration"
And I lock the work "Worldbuilding"
- When I am logged out
+ When I log out
And I view the work "Followup"
Then I should see "Inspired by [Restricted Work] by inspiration"
Scenario: Restricted inspired and inspiring works should not be listed on related work pages for guests
Given I have related works setup
And a related work has been posted and approved
+ # Restricted inspired work
When I am logged in as "remixer"
And I lock the work "Followup"
- When I am logged out
+ When I log out
And I go to inspiration's related works page
Then I should not see "Followup"
And I should not see "Worldbuilding"
When I go to remixer's related works page
Then I should not see "Followup"
And I should not see "Worldbuilding"
-
+ # Restricted inspiration
When I am logged in as "remixer"
And I unlock the work "Followup"
When I am logged in as "inspiration"
And I lock the work "Worldbuilding"
- When I am logged out
+ When I log out
And I go to inspiration's related works page
Then I should not see "Followup"
And I should not see "Worldbuilding"
@@ -374,22 +375,23 @@ Scenario: Restricted inspired and inspiring works should not be listed on relate
Then I should not see "Followup"
And I should not see "Worldbuilding"
-Scenario: Restricted inspired and inspiring works should not be listed on related work pages for guests
+Scenario: Restricted translations and translated works should not be listed on related work pages for guests
Given I have related works setup
And a translation has been posted and approved
+ # Restricted translation
When I am logged in as "translator"
And I lock the work "Worldbuilding Translated"
- When I am logged out
+ When I log out
And I go to inspiration's related works page
Then I should not see "Worldbuilding"
When I go to translator's related works page
Then I should not see "Worldbuilding"
-
+ # Restricted translated work
When I am logged in as "translator"
And I unlock the work "Worldbuilding Translated"
When I am logged in as "inspiration"
And I lock the work "Worldbuilding"
- When I am logged out
+ When I log out
And I go to inspiration's related works page
Then I should not see "Worldbuilding"
When I go to translator's related works page
@@ -414,7 +416,7 @@ Scenario: Anonymous works listed as inspiration should have links to the authors
When I follow "remixer" within ".afterword .children"
Then I should be on the dashboard page for user "remixer" with pseud "remixer"
- When I am logged out
+ When I log out
And I view the work "Worldbuilding"
Then I should see "Works inspired by this one: Followup by Anonymous"
And I should not see "remixer" within ".afterword .children"
@@ -422,6 +424,7 @@ Scenario: Anonymous works listed as inspiration should have links to the authors
Scenario: Hidden inspired and inspiring works should not be listed on related work pages
Given I have related works setup
And a related work has been posted and approved
+ # Hidden inspired work
When I am logged in as a "policy_and_abuse" admin
And I hide the work "Followup"
And I go to inspiration's related works page
@@ -438,7 +441,7 @@ Scenario: Hidden inspired and inspiring works should not be listed on related wo
And I view my related works
Then I should see "Related Works (0)"
And I should not see "Followup"
-
+ # Hidden inspiration
When I am logged in as a "policy_and_abuse" admin
And I unhide the work "Followup"
And I hide the work "Worldbuilding"
@@ -460,6 +463,7 @@ Scenario: Hidden inspired and inspiring works should not be listed on related wo
Scenario: Hidden translations and translated works should not be listed on related work pages
Given I have related works setup
And a translation has been posted and approved
+ # Hidden translation
When I am logged in as a "policy_and_abuse" admin
And I hide the work "Worldbuilding Translated"
And I go to inspiration's related works page
@@ -476,7 +480,7 @@ Scenario: Hidden translations and translated works should not be listed on relat
And I view my related works
Then I should see "Related Works (0)"
And I should not see "Worldbuilding Translated"
-
+ # Hidden translated work
When I am logged in as a "policy_and_abuse" admin
And I unhide the work "Worldbuilding Translated"
And I hide the work "Worldbuilding"
From 20201876bc09d2474a02b0db24c7046c2c4f87eb Mon Sep 17 00:00:00 2001
From: Slava <53832230+slavalamp@users.noreply.github.com>
Date: Sat, 16 May 2026 04:54:44 +0200
Subject: [PATCH 9/9] AO3-6022 ...undo the changes that i'm already doing in
the other pr anyway
---
features/works/work_related.feature | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/features/works/work_related.feature b/features/works/work_related.feature
index 78d905f1d0..8e783abd52 100644
--- a/features/works/work_related.feature
+++ b/features/works/work_related.feature
@@ -335,17 +335,17 @@ Scenario: Restricted works listed as Inspiration show up [Restricted] for guests
And a related work has been posted and approved
When I am logged in as "remixer"
And I lock the work "Followup"
- When I log out
+ When I am logged out
And I view the work "Worldbuilding"
Then I should see "[Restricted Work] by remixer"
When I am logged in as "remixer"
And I unlock the work "Followup"
- When I log out
+ When I am logged out
And I view the work "Followup"
Then I should see "Inspired by Worldbuilding by inspiration"
When I am logged in as "inspiration"
And I lock the work "Worldbuilding"
- When I log out
+ When I am logged out
And I view the work "Followup"
Then I should see "Inspired by [Restricted Work] by inspiration"
@@ -416,7 +416,7 @@ Scenario: Anonymous works listed as inspiration should have links to the authors
When I follow "remixer" within ".afterword .children"
Then I should be on the dashboard page for user "remixer" with pseud "remixer"
- When I log out
+ When I am logged out
And I view the work "Worldbuilding"
Then I should see "Works inspired by this one: Followup by Anonymous"
And I should not see "remixer" within ".afterword .children"