summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/models/page.rb65
-rw-r--r--test/models/page_test.rb99
2 files changed, 152 insertions, 12 deletions
diff --git a/app/models/page.rb b/app/models/page.rb
index f796228..e5a8d9d 100644
--- a/app/models/page.rb
+++ b/app/models/page.rb
@@ -176,18 +176,34 @@ class Page < ApplicationRecord
176 return nil unless page 176 return nil unless page
177 177
178 self.reload 178 self.reload
179 page.translations.reload
179 180
180 # Clone untranslated attributes 181 # Clone untranslated attributes
181 self.tag_list = page.tag_list 182 self.tag_list = page.tag_list
182 self.template_name ||= page.template_name 183 self.template_name ||= page.template_name
183 self.published_at = page.published_at 184 self.published_at = page.published_at
184 185
185 # Getting rid of the auto-generated empty translations 186 # Clone translated attributes -- update each locale in place rather
186 self.translations.delete_all 187 # than delete-and-recreate, so a locale whose content is genuinely
188 # unchanged keeps its real created_at/updated_at instead of looking
189 # freshly touched on every single save (which was silently defeating
190 # Page.find_with_outdated_translations' whole staleness comparison).
191 # search_vector is excluded deliberately: it's DB-trigger-maintained
192 # from title/abstract, not real content, and comparing a precomputed
193 # tsvector risked a false "changed" from representation noise alone.
194 source_locales = page.translations.map(&:locale)
195 self.translations.where.not(:locale => source_locales).destroy_all
187 196
188 # Clone translated attributes 197 page.translations.each do |source_translation|
189 page.translations.each do |translation| 198 attrs = source_translation.attributes.except("id", "page_id", "created_at", "updated_at", "search_vector")
190 self.translations.create!(translation.attributes.except("id", "page_id", "created_at", "updated_at")) 199 mine = self.translations.find_by(:locale => source_translation.locale)
200
201 if mine
202 changed_attrs = attrs.reject { |k, v| mine.public_send(k) == v }
203 mine.update!(changed_attrs) if changed_attrs.any?
204 else
205 self.translations.create!(attrs)
206 end
191 end 207 end
192 208
193 # Clone asset references 209 # Clone asset references
@@ -196,19 +212,28 @@ class Page < ApplicationRecord
196 self.save 212 self.save
197 end 213 end
198 214
199 def diff_against other, view: :inline 215 def diff_against other, view: :inline, locale: nil
216 if locale
217 mine, theirs = translations.find_by(:locale => locale), other.translations.find_by(:locale => locale)
218 my_title, my_abstract, my_body = mine&.title, mine&.abstract, mine&.body
219 their_title, their_abstract, their_body = theirs&.title, theirs&.abstract, theirs&.body
220 else
221 my_title, my_abstract, my_body = title, abstract, body
222 their_title, their_abstract, their_body = other.title, other.abstract, other.body
223 end
224
200 text_diffs = 225 text_diffs =
201 if view == :side_by_side 226 if view == :side_by_side
202 { 227 {
203 title: HtmlWordDiff.side_by_side(other.title.to_s, title.to_s), 228 title: HtmlWordDiff.side_by_side(their_title.to_s, my_title.to_s),
204 abstract: HtmlWordDiff.side_by_side(other.abstract.to_s, abstract.to_s), 229 abstract: HtmlWordDiff.side_by_side(their_abstract.to_s, my_abstract.to_s),
205 body: HtmlWordDiff.side_by_side(other.body.to_s, body.to_s) 230 body: HtmlWordDiff.side_by_side(their_body.to_s, my_body.to_s)
206 } 231 }
207 else 232 else
208 { 233 {
209 title: HtmlWordDiff.inline(other.title.to_s, title.to_s), 234 title: HtmlWordDiff.inline(their_title.to_s, my_title.to_s),
210 abstract: HtmlWordDiff.inline(other.abstract.to_s, abstract.to_s), 235 abstract: HtmlWordDiff.inline(their_abstract.to_s, my_abstract.to_s),
211 body: HtmlWordDiff.inline(other.body.to_s, body.to_s) 236 body: HtmlWordDiff.inline(their_body.to_s, my_body.to_s)
212 } 237 }
213 end 238 end
214 239
@@ -219,6 +244,22 @@ class Page < ApplicationRecord
219 ) 244 )
220 end 245 end
221 246
247 def locale_diff_summary other
248 (translated_locales | other.translated_locales).sort_by(&:to_s).map do |locale|
249 mine = translations.find_by(:locale => locale)
250 theirs = other.translations.find_by(:locale => locale)
251 {
252 :locale => locale,
253 :exists_here => mine.present?,
254 :exists_there => theirs.present?,
255 :changed => mine.present? != theirs.present? ||
256 mine&.title != theirs&.title ||
257 mine&.abstract != theirs&.abstract ||
258 mine&.body != theirs&.body
259 }
260 end
261 end
262
222 def public? 263 def public?
223 published_at.nil? ? true : published_at < Time.now 264 published_at.nil? ? true : published_at < Time.now
224 end 265 end
diff --git a/test/models/page_test.rb b/test/models/page_test.rb
index bcddc89..24bc7c5 100644
--- a/test/models/page_test.rb
+++ b/test/models/page_test.rb
@@ -177,6 +177,56 @@ class PageTest < ActiveSupport::TestCase
177 assert first_page.published_at.present? 177 assert first_page.published_at.present?
178 end 178 end
179 179
180 test "clone_attributes_from preserves an unchanged locale's original timestamp" do
181 n = Node.root.children.create!(:slug => "clone_preserve_timestamp_test")
182 source = n.draft
183 Globalize.with_locale(:de) { source.update!(:title => "Deutscher Titel") }
184 Globalize.with_locale(:en) { source.update!(:title => "English Title") }
185
186 target = Page.create!
187 target.clone_attributes_from(source)
188 original_en_updated_at = target.translations.find_by(:locale => :en).updated_at
189
190 Globalize.with_locale(:de) { source.update!(:title => "Deutscher Titel (bearbeitet)") }
191 target.clone_attributes_from(source)
192
193 en_translation = target.translations.find_by(:locale => :en)
194 assert_equal "English Title", en_translation.title
195 assert_equal original_en_updated_at, en_translation.updated_at
196 end
197
198 test "clone_attributes_from gives a genuinely changed locale a fresh timestamp" do
199 n = Node.root.children.create!(:slug => "clone_fresh_timestamp_test")
200 source = n.draft
201 Globalize.with_locale(:de) { source.update!(:title => "Erste Version") }
202
203 target = Page.create!
204 target.clone_attributes_from(source)
205 original_de_updated_at = target.translations.find_by(:locale => :de).updated_at
206
207 Globalize.with_locale(:de) { source.update!(:title => "Zweite Version") }
208 target.clone_attributes_from(source)
209
210 de_translation = target.translations.find_by(:locale => :de)
211 assert_equal "Zweite Version", de_translation.title
212 assert_operator de_translation.updated_at, :>, original_de_updated_at
213 end
214
215 test "clone_attributes_from removes a locale no longer present in the source" do
216 n = Node.root.children.create!(:slug => "clone_removed_locale_test")
217 source = n.draft
218 Globalize.with_locale(:en) { source.update!(:title => "English Title") }
219
220 target = Page.create!
221 target.clone_attributes_from(source)
222 assert_includes target.translations.map(&:locale), :en
223
224 source.translations.where(:locale => :en).delete_all
225 target.clone_attributes_from(source)
226
227 assert_not_includes target.reload.translations.map(&:locale), :en
228 end
229
180 def test_diff_against_inline_keeps_tags_and_marks_only_the_changed_word 230 def test_diff_against_inline_keeps_tags_and_marks_only_the_changed_word
181 n = Node.root.children.create! :slug => "diff_against_test" 231 n = Node.root.children.create! :slug => "diff_against_test"
182 d = n.find_or_create_draft @user1 232 d = n.find_or_create_draft @user1
@@ -271,4 +321,53 @@ class PageTest < ActiveSupport::TestCase
271 assert_equal [added_asset], diff[:assets][:added] 321 assert_equal [added_asset], diff[:assets][:added]
272 assert_equal [removed_asset], diff[:assets][:removed] 322 assert_equal [removed_asset], diff[:assets][:removed]
273 end 323 end
324
325 test "diff_against with an explicit locale compares that locale's own translation on each side" do
326 n = Node.root.children.create!(:slug => "diff_locale_test")
327 d = n.find_or_create_draft(@user1)
328 Globalize.with_locale(:en) { d.update!(:title => "Old English") }
329 d.save!
330 n.publish_draft!
331
332 d2 = n.find_or_create_draft(@user1)
333 Globalize.with_locale(:en) { d2.update!(:title => "New English") }
334 d2.save!
335
336 diff = d2.diff_against(n.head, :locale => :en)
337
338 assert_match "<del>Old</del>", diff[:title]
339 assert_match "<ins>New</ins>", diff[:title]
340 end
341
342 test "diff_against with an explicit locale ignores content in other locales entirely" do
343 n = Node.root.children.create!(:slug => "diff_locale_isolation_test")
344 d = n.find_or_create_draft(@user1)
345 d.save!
346 n.publish_draft!
347
348 d2 = n.find_or_create_draft(@user1)
349 Globalize.with_locale(:de) { d2.update!(:title => "Nur Deutsch geƤndert") }
350 d2.save!
351
352 diff = d2.diff_against(n.head, :locale => :en)
353
354 assert_no_match(/Deutsch/, diff[:title])
355 end
356
357 test "locale_diff_summary flags a locale that only exists on one side as changed" do
358 n = Node.root.children.create!(:slug => "diff_locale_summary_test")
359 d = n.find_or_create_draft(@user1)
360 d.save!
361 n.publish_draft!
362
363 d2 = n.find_or_create_draft(@user1)
364 Globalize.with_locale(:en) { d2.update!(:title => "New English translation") }
365 d2.save!
366
367 summary = d2.locale_diff_summary(n.head)
368 en_entry = summary.find { |s| s[:locale] == :en }
369
370 assert en_entry[:changed]
371 refute en_entry[:exists_there]
372 end
274end 373end