/
tag_mover.rb
208 lines (177 loc) · 6.64 KB
/
tag_mover.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
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
# frozen_string_literal: true
# Rename a tag and move everything associated with it. Used when renaming or
# aliasing a tag in a bulk update request. Moves everything associated with the
# tag, including aliases, implications, *_(cosplay) and *_(style) tags, artist
# and wiki pages, saved searches, blacklists, and retags the posts themselves.
class TagMover
attr_reader :old_tag, :new_tag, :user
# Initialize a tag move.
# @param old_name [String] the name of the tag to move
# @param new_name [String] the new tag name
# @param user [User] the user to credit for all edits in moving the tag
def initialize(old_name, new_name, user: User.system)
@old_tag = Tag.find_or_create_by_name(old_name)
@new_tag = Tag.find_or_create_by_name(new_name)
@user = user
end
# Perform the tag move.
def move!
CurrentUser.scoped(user) do
move_tag_category!
move_aliases!
move_implications!
move_cosplay_tag!
move_style_tag!
move_artist!
move_wiki!
move_saved_searches!
move_blacklists!
rewrite_wiki_links!
move_posts!
end
end
# Sync the category of both tags, if one is a general tag and the other is non-general.
def move_tag_category!
if old_tag.general? && !new_tag.general?
old_tag.update!(category: new_tag.category, updater: user)
elsif new_tag.general? && !old_tag.general?
new_tag.update!(category: old_tag.category, updater: user)
end
end
# Move the artist entry from the old tag to the new tag, merging it into the
# new tag's artist entry if it already has an artist entry.
def move_artist!
return unless old_tag.artist? && old_artist.present? && !old_artist.is_deleted?
if new_artist.nil?
old_artist.name = new_tag.name
old_artist.other_names += [old_tag.name]
old_artist.save!
else
merge_artists!
end
end
# Move the wiki from the old tag to the new tag, merging it into the new tag's
# wiki if it already has a wiki.
def move_wiki!
return unless old_wiki.present? && !old_wiki.is_deleted?
if new_wiki.nil?
old_wiki.update!(title: new_tag.name)
else
merge_wikis!
end
end
# Retag the posts from the old tag to the new tag.
def move_posts!
old_tag.posts.parallel_find_each do |post|
DanbooruLogger.info("post ##{post.id}: moving #{old_tag.name} -> #{new_tag.name}")
post.with_lock do
post.remove_tag(old_tag.name)
post.add_tag(new_tag.name)
post.save!
end
end
end
# Transfer any aliases pointing to the old tag to point to the new tag.
def move_aliases!
old_tag.consequent_aliases.each do |tag_alias|
tag_alias.update!(consequent_name: new_tag.name)
end
end
# Transfer any implications from the old tag to the new tag.
def move_implications!
# When renaming A to B, remove any existing A -> ??? implications and
# replace them with B -> ???, unless they already exist.
old_tag.antecedent_implications.each do |tag_implication|
tag_implication.update!(status: :deleted)
unless TagImplication.active.exists?(antecedent_name: new_tag.name, consequent_name: tag_implication.consequent_name)
TagImplication.create!(antecedent_name: new_tag.name, consequent_name: tag_implication.consequent_name, creator: user, approver: user, status: :active)
end
end
# When renaming A to B, remove any existing ??? -> A implications and
# replace them with ??? -> B, unless they already exist.
old_tag.consequent_implications.each do |tag_implication|
tag_implication.update!(status: :deleted)
unless TagImplication.active.exists?(antecedent_name: tag_implication.antecedent_name, consequent_name: new_tag.name)
TagImplication.create!(antecedent_name: tag_implication.antecedent_name, consequent_name: new_tag.name, creator: user, approver: user, status: :active)
end
end
end
# Move the character's *_(cosplay) tag if it exists.
def move_cosplay_tag!
old_cosplay_tag = "#{old_tag.name}_(cosplay)"
new_cosplay_tag = "#{new_tag.name}_(cosplay)"
if Tag.nonempty.exists?(name: old_cosplay_tag)
TagMover.new(old_cosplay_tag, new_cosplay_tag).move!
end
end
# Move the artist's *_(style) tag if it exists.
def move_style_tag!
old_style_tag = "#{old_tag.name}_(style)"
new_style_tag = "#{new_tag.name}_(style)"
if old_tag.artist? && Tag.nonempty.exists?(name: old_style_tag)
TagMover.new(old_style_tag, new_style_tag).move!
end
end
# Update all saved searches to use the new tag.
def move_saved_searches!
SavedSearch.rewrite_queries!(old_tag.name, new_tag.name)
end
# Update all blacklists to use the new tag.
def move_blacklists!
User.rewrite_blacklists!(old_tag.name, new_tag.name)
end
# Update any wiki pages or pools linking to the old tag, to link to the new tag.
def rewrite_wiki_links!
WikiPage.rewrite_wiki_links!(old_tag.name, new_tag.name)
Pool.rewrite_wiki_links!(old_tag.name, new_tag.name)
end
# Merge two artist entries, copying everything from the old entry to the new
# one. Duplicate information will be automatically stripped when the artist
# is saved.
def merge_artists!
ApplicationRecord.transaction do
[old_artist, new_artist].sort_by(&:id).each(&:lock!)
new_artist.other_names += old_artist.other_names
new_artist.other_names += [old_artist.name]
new_artist.group_name = old_artist.group_name unless new_artist.group_name.present?
new_artist.url_string += "\n" + old_artist.url_string
new_artist.is_deleted = false
new_artist.is_banned = old_artist.is_banned || new_artist.is_banned
new_artist.save!
old_artist.other_names = [new_artist.name]
old_artist.group_name = ""
old_artist.url_string = ""
old_artist.is_deleted = true
old_artist.is_banned = false
old_artist.save!
end
end
# Merge the other names from both wikis. Transfer the body from the old wiki
# to the new wiki if the new wiki has an empty body. Then mark the old wiki
# as deleted.
def merge_wikis!
ApplicationRecord.transaction do
[old_wiki, new_wiki].sort_by(&:id).each(&:lock!)
new_wiki.other_names += old_wiki.other_names
new_wiki.is_deleted = false
new_wiki.body = old_wiki.body if new_wiki.body.blank? && old_wiki.body.present?
new_wiki.save!
old_wiki.body = "This tag has been moved to [[#{new_wiki.title}]]."
old_wiki.other_names = []
old_wiki.is_deleted = true
old_wiki.save!
end
end
def old_wiki
old_tag.wiki_page
end
def new_wiki
new_tag.wiki_page
end
def old_artist
old_tag.artist
end
def new_artist
new_tag.artist
end
end