diff --git a/app/models/concerns/account/associations.rb b/app/models/concerns/account/associations.rb index cafb2d151c..280f0bdca0 100644 --- a/app/models/concerns/account/associations.rb +++ b/app/models/concerns/account/associations.rb @@ -45,6 +45,9 @@ module Account::Associations has_many :targeted_moderation_notes, class_name: 'AccountModerationNote' has_many :targeted_reports, class_name: 'Report' end + + # Tagging applied to account + has_many :taggings, as: :taggable end # Status records pinned by the account @@ -60,7 +63,7 @@ module Account::Associations belongs_to :moved_to_account, class_name: 'Account', optional: true # Tag records applied to account - has_and_belongs_to_many :tags # rubocop:disable Rails/HasAndBelongsToMany + has_many :tags, through: :taggings # FollowRecommendation for account (surfaced via view) has_one :follow_recommendation, inverse_of: :account, dependent: nil diff --git a/app/models/status.rb b/app/models/status.rb index 5a81b00773..2a8b07494e 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -84,7 +84,8 @@ class Status < ApplicationRecord has_many :local_reblogged, -> { merge(Account.local) }, through: :reblogs, source: :account has_many :local_bookmarked, -> { merge(Account.local) }, through: :bookmarks, source: :account - has_and_belongs_to_many :tags # rubocop:disable Rails/HasAndBelongsToMany + has_many :taggings, as: :taggable, dependent: :destroy + has_many :tags, through: :taggings has_one :preview_cards_status, inverse_of: :status, dependent: :delete diff --git a/app/models/tag.rb b/app/models/tag.rb index 67fa9e5d3a..cb9185ad1f 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -23,10 +23,11 @@ class Tag < ApplicationRecord include Paginable include Reviewable - # rubocop:disable Rails/HasAndBelongsToMany - has_and_belongs_to_many :statuses - has_and_belongs_to_many :accounts - # rubocop:enable Rails/HasAndBelongsToMany + has_many :taggings, dependent: :destroy + with_options through: :taggings, source: :taggable do + has_many :statuses, source_type: 'Status' + has_many :accounts, source_type: 'Account' + end has_many :passive_relationships, class_name: 'TagFollow', inverse_of: :tag, dependent: :destroy has_many :featured_tags, dependent: :destroy, inverse_of: :tag diff --git a/db/migrate/20240731194619_create_taggings.rb b/db/migrate/20240731194619_create_taggings.rb index db2a4820b2..c7b8b73712 100644 --- a/db/migrate/20240731194619_create_taggings.rb +++ b/db/migrate/20240731194619_create_taggings.rb @@ -1,7 +1,30 @@ # frozen_string_literal: true class CreateTaggings < ActiveRecord::Migration[7.1] - def change + def up + create_taggings + + safety_assured do + convert_status_taggings + convert_account_taggings + end + + drop_table :accounts_tags + drop_table :statuses_tags + end + + def down + restore_join_tables + + safety_assured do + restore_account_taggings + restore_status_taggings + end + + drop_table :taggings + end + + def create_taggings create_table :taggings do |t| t.references :tag, null: false, foreign_key: true t.references :taggable, polymorphic: true, null: false @@ -9,4 +32,54 @@ class CreateTaggings < ActiveRecord::Migration[7.1] t.timestamps end end + + def restore_join_tables + create_table :statuses_tags, primary_key: [:tag_id, :status_id], force: :cascade do |t| + t.bigint :status_id, null: false + t.bigint :tag_id, null: false + t.index :status_id + end + + create_table :accounts_tags, primary_key: [:tag_id, :account_id], force: :cascade do |t| + t.bigint :account_id, null: false + t.bigint :tag_id, null: false + t.index [:account_id, :tag_id] + end + end + + def convert_status_taggings + execute <<~SQL.squish + INSERT INTO taggings(tag_id, taggable_type, taggable_id, created_at, updated_at) + SELECT statuses_tags.tag_id, 'Status', statuses_tags.status_id, statuses.updated_at, statuses.updated_at + FROM statuses_tags + JOIN statuses ON statuses.id = statuses_tags.status_id + SQL + end + + def convert_account_taggings + execute <<~SQL.squish + INSERT INTO taggings(tag_id, taggable_type, taggable_id, created_at, updated_at) + SELECT accounts_tags.tag_id, 'Account', accounts_tags.account_id, accounts.updated_at, accounts.updated_at + FROM accounts_tags + JOIN accounts ON accounts.id = accounts_tags.account_id + SQL + end + + def restore_account_taggings + execute <<~SQL.squish + INSERT INTO accounts_tags(account_id, tag_id) + SELECT taggable_id, tag_id + FROM taggings + WHERE taggable_type = 'Account' + SQL + end + + def restore_status_taggings + execute <<~SQL.squish + INSERT INTO statuses_tags(status_id, tag_id) + SELECT taggable_id, tag_id + FROM taggings + WHERE taggable_type = 'Status' + SQL + end end diff --git a/db/schema.rb b/db/schema.rb index bc73b7d792..6a1ff05b42 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -206,12 +206,6 @@ ActiveRecord::Schema[7.2].define(version: 2024_11_04_082851) do t.index ["url"], name: "index_accounts_on_url", opclass: :text_pattern_ops, where: "(url IS NOT NULL)" end - create_table "accounts_tags", primary_key: ["tag_id", "account_id"], force: :cascade do |t| - t.bigint "account_id", null: false - t.bigint "tag_id", null: false - t.index ["account_id", "tag_id"], name: "index_accounts_tags_on_account_id_and_tag_id" - end - create_table "admin_action_logs", force: :cascade do |t| t.bigint "account_id" t.string "action", default: "", null: false @@ -1065,12 +1059,6 @@ ActiveRecord::Schema[7.2].define(version: 2024_11_04_082851) do t.index ["uri"], name: "index_statuses_on_uri", unique: true, opclass: :text_pattern_ops, where: "(uri IS NOT NULL)" end - create_table "statuses_tags", primary_key: ["tag_id", "status_id"], force: :cascade do |t| - t.bigint "status_id", null: false - t.bigint "tag_id", null: false - t.index ["status_id"], name: "index_statuses_tags_on_status_id" - end - create_table "tag_follows", force: :cascade do |t| t.bigint "tag_id", null: false t.bigint "account_id", null: false @@ -1349,8 +1337,6 @@ ActiveRecord::Schema[7.2].define(version: 2024_11_04_082851) do add_foreign_key "statuses", "accounts", name: "fk_9bda1543f7", on_delete: :cascade add_foreign_key "statuses", "statuses", column: "in_reply_to_id", on_delete: :nullify add_foreign_key "statuses", "statuses", column: "reblog_of_id", on_delete: :cascade - add_foreign_key "statuses_tags", "statuses", on_delete: :cascade - add_foreign_key "statuses_tags", "tags", name: "fk_3081861e21", on_delete: :cascade add_foreign_key "tag_follows", "accounts", on_delete: :cascade add_foreign_key "tag_follows", "tags", on_delete: :cascade add_foreign_key "taggings", "tags"