From c51c4a59c507460bbd2470c0d5676fefd3e5b8a6 Mon Sep 17 00:00:00 2001
From: Arnaud Levy <contact@arnaudlevy.com>
Date: Thu, 27 Apr 2023 16:18:30 +0200
Subject: [PATCH] Cleanup!
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: Sébastien Gaya <sebastien.gaya@gmail.com>
Co-authored-by: Pierre-André Boissinot <pierreandre.boissinot@noesya.coop>
---
 app/models/communication/block.rb             |  5 --
 .../website/with_connected_objects.rb         |  8 +++
 app/models/concerns/as_direct_object.rb       |  2 +-
 app/models/concerns/as_indirect_object.rb     |  4 +-
 app/models/concerns/with_dependencies.rb      | 57 +++++++++++++++--
 .../with_dependencies_synchronization.rb      | 39 ------------
 app/services/dependencies_filter.rb           | 12 ++++
 .../communication/website/dependency_test.rb  | 61 +++++++++++++++++--
 8 files changed, 131 insertions(+), 57 deletions(-)
 delete mode 100644 app/models/concerns/with_dependencies_synchronization.rb
 create mode 100644 app/services/dependencies_filter.rb

diff --git a/app/models/communication/block.rb b/app/models/communication/block.rb
index 3f027bbd2..44c6c34e5 100644
--- a/app/models/communication/block.rb
+++ b/app/models/communication/block.rb
@@ -72,7 +72,6 @@ class Communication::Block < ApplicationRecord
 
   scope :published, -> { where(published: true) }
 
-  after_save :sync_if_about_is_direct
   before_save :attach_template_blobs
   before_validation :set_university_from_about, on: :create
 
@@ -146,10 +145,6 @@ class Communication::Block < ApplicationRecord
     "Communication::Block::Template::#{template_kind.classify}".constantize
   end
 
-  def sync_if_about_is_direct
-    about.save_and_sync if about.respond_to? :save_and_sync
-  end
-
   # FIXME @sebou
   # Could not find or build blob: expected attachable, got #<ActiveStorage::Blob id: "f4c78657-5062-416b-806f-0b80fb66f9cd", key: "gri33wtop0igur8w3a646llel3sd", filename: "logo.svg", content_type: "image/svg+xml", metadata: {"identified"=>true, "width"=>709, "height"=>137, "analyzed"=>true}, service_name: "scaleway", byte_size: 4137, checksum: "aZqqTYabP5+72ZeddcZ/2Q==", created_at: "2022-05-05 12:17:33.941505000 +0200", university_id: "ebf2d273-ffc9-4d9f-a4ee-a2146913d617">
   def attach_template_blobs
diff --git a/app/models/communication/website/with_connected_objects.rb b/app/models/communication/website/with_connected_objects.rb
index 99efa55b4..356752344 100644
--- a/app/models/communication/website/with_connected_objects.rb
+++ b/app/models/communication/website/with_connected_objects.rb
@@ -68,6 +68,14 @@ module Communication::Website::WithConnectedObjects
     University::Organization.where(id: ids)
   end
 
+  def is_direct_object?
+    true
+  end
+
+  def is_indirect_object?
+    false
+  end
+
   protected
 
   def connect_about
diff --git a/app/models/concerns/as_direct_object.rb b/app/models/concerns/as_direct_object.rb
index fdfd17af8..cfbaec991 100644
--- a/app/models/concerns/as_direct_object.rb
+++ b/app/models/concerns/as_direct_object.rb
@@ -8,7 +8,7 @@ module AsDirectObject
   extend ActiveSupport::Concern
 
   included do
-    include WithDependenciesSynchronization
+    include WithDependencies
     include WithGit
     include WithGitFiles
     include WithReferences
diff --git a/app/models/concerns/as_indirect_object.rb b/app/models/concerns/as_indirect_object.rb
index 56b45d6f7..118bbe5ce 100644
--- a/app/models/concerns/as_indirect_object.rb
+++ b/app/models/concerns/as_indirect_object.rb
@@ -7,7 +7,7 @@ module AsIndirectObject
 
   included do
     # Les blocs sont des objets indirects, mais n'ont pas de GitFiles, on n'inclut donc pas WithGitFiles ici
-    include WithDependenciesSynchronization
+    include WithDependencies
     include WithReferences
 
     has_many  :connections,
@@ -83,7 +83,7 @@ module AsIndirectObject
   def sync_connections
     direct_sources.each do |direct_source|
       direct_source.website.connect self, direct_source
-      direct_source.save_and_sync
+      direct_source.sync_with_git
     end
   end
 end
\ No newline at end of file
diff --git a/app/models/concerns/with_dependencies.rb b/app/models/concerns/with_dependencies.rb
index 3acfc7a4f..5b8526ead 100644
--- a/app/models/concerns/with_dependencies.rb
+++ b/app/models/concerns/with_dependencies.rb
@@ -5,6 +5,17 @@
 module WithDependencies
   extend ActiveSupport::Concern
 
+  included do
+    attr_accessor :previous_dependencies
+
+    if self < ActiveRecord::Base
+      before_save :snapshot_dependencies
+      after_save :clean_websites_if_necessary
+      after_destroy :clean_websites
+    end
+  end
+
+
   # Cette méthode doit être définie dans chaque objet,
   # et renvoyer un tableau de ses références directes.
   # Jamais de référence indirecte !
@@ -24,10 +35,10 @@ module WithDependencies
     end
   end
 
+  # On ne liste pas les objets en cours de suppression
+  # return array if respond_to?(:mark_for_destruction?) && mark_for_destruction
+  # On renvoie l'array tel quel, non modifié, si on demande les contenus syncable_only et que le contenu ne l'est pas
   def recursive_dependencies(array: [], syncable_only: false)
-    # On ne liste pas les objets en cours de suppression
-    # return array if respond_to?(:mark_for_destruction?) && mark_for_destruction
-    # On renvoie l'array tel quel, non modifié, si on demande les contenus syncable_only et que le contenu ne l'est pas
     return array unless dependency_should_be_synced?(self, syncable_only)
     dependencies.each do |dependency|
       # Si l'objet ne doit pas être ajouté on n'ajoute pas non plus ses dépendances récursives
@@ -46,15 +57,49 @@ module WithDependencies
 
   protected
   
+  # Si l'objet est déjà là, on ne doit pas l'ajouter
+  # Si l'objet n'est pas syncable, on ne doit pas l'ajouter non plus
   def dependency_should_be_added?(array, dependency, syncable_only)
-    # Si l'objet est déjà là, on ne doit pas l'ajouter
-    # Si l'objet n'est pas syncable, on ne doit pas l'ajouter non plus
     !dependency.in?(array) && dependency_should_be_synced?(dependency, syncable_only)
   end
   
+  # Si on n'est pas en syncable only on liste tout, sinon, il faut analyser
   def dependency_should_be_synced?(dependency, syncable_only)
-    # Si on n'est pas en syncable only on liste tout, sinon, il faut analyser
     !syncable_only || (dependency.respond_to?(:syncable?) && dependency.syncable?)
   end
 
+  # Stockage en RAM des dépendances avant enregistrement
+  def snapshot_dependencies
+    @previous_dependencies = persisted? ? reloaded_recursive_dependencies_syncable_filtered : []
+  end
+
+  def clean_websites_if_necessary
+    # Debug :)
+    # puts self
+    # puts "  previous_dependencies           #{ @previous_dependencies }"
+    # puts "  recursive_dependencies_syncable #{ reloaded_recursive_dependencies_syncable_filtered }"
+    # puts "  missing_dependencies_after_save #{ missing_dependencies_after_save }"
+    # puts
+    clean_websites if missing_dependencies_after_save.any?
+  end
+
+  def clean_websites
+    return unless respond_to?(:is_direct_object?)
+    
+    if is_direct_object?
+      website.destroy_obsolete_git_files
+    elsif is_indirect_object?
+      websites.each(&:destroy_obsolete_git_files)
+    end
+  end
+
+  def missing_dependencies_after_save
+    @previous_dependencies - reloaded_recursive_dependencies_syncable_filtered
+  end
+
+  def reloaded_recursive_dependencies_syncable_filtered
+    reloaded_object = self.class.unscoped.find(id)
+    reloaded_dependencies = reloaded_object.recursive_dependencies_syncable
+    DependenciesFilter.filtered(reloaded_dependencies)
+  end
 end
\ No newline at end of file
diff --git a/app/models/concerns/with_dependencies_synchronization.rb b/app/models/concerns/with_dependencies_synchronization.rb
deleted file mode 100644
index 31951d598..000000000
--- a/app/models/concerns/with_dependencies_synchronization.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-module WithDependenciesSynchronization
-  extend ActiveSupport::Concern
-
-  included do
-    include WithDependencies
-
-    attr_accessor :dependencies_before_save
-
-    # TODO
-    # before_save :compute_dependencies_before_save
-    # after_save :cleanup_websites, if: :lost_dependencies_after_save?
-    after_destroy :cleanup_websites
-  end
-
-  protected
-
-  def compute_dependencies_before_save
-    @dependencies_before_save = begin
-      array = []
-      array = self.class.find(id).recursive_dependencies_syncable if persisted?
-      array.select { |dependency| dependency.respond_to?(:git_files) }
-    end
-  end
-
-  def lost_dependencies_after_save?
-    lost_dependencies_after_save = @dependencies_before_save - recursive_dependencies_syncable
-    lost_dependencies_after_save.any?
-  end
-
-  def cleanup_websites
-    # byebug unless is_a?(Communication::Block)
-    if is_direct_object?
-      website.destroy_obsolete_git_files
-    else
-      websites.each(&:destroy_obsolete_git_files)
-    end
-  end
-
-end
diff --git a/app/services/dependencies_filter.rb b/app/services/dependencies_filter.rb
new file mode 100644
index 000000000..4523403c2
--- /dev/null
+++ b/app/services/dependencies_filter.rb
@@ -0,0 +1,12 @@
+class DependenciesFilter
+
+  # [
+  #   "gid://osuny/Communication::Block/4ac9f6fe-80cd-46f6-becc-fc14b3fecea0",
+  #   "gid://osuny/University::Person/36501bf0-99b9-58f9-b269-bf161b451c43"
+  # ]
+  def self.filtered(dependencies)
+    dependencies.select { |dependency| dependency.is_a?(ActiveRecord::Base) }
+                .map    { |dependency| dependency.to_global_id.to_s }
+                .uniq
+  end
+end
\ No newline at end of file
diff --git a/test/models/communication/website/dependency_test.rb b/test/models/communication/website/dependency_test.rb
index d7fca444e..379506642 100644
--- a/test/models/communication/website/dependency_test.rb
+++ b/test/models/communication/website/dependency_test.rb
@@ -2,6 +2,8 @@ require "test_helper"
 
 # rails test test/models/communication/website/dependency_test.rb
 class Communication::Website::DependencyTest < ActiveSupport::TestCase
+  include ActiveJob::TestHelper
+
   def test_page_dependencies
     # Rien : 0 dépendances
     page = communication_website_pages(:page_with_no_dependency)
@@ -19,17 +21,68 @@ class Communication::Website::DependencyTest < ActiveSupport::TestCase
     # - le block Personnes (1)
     # - 4 composants du template du block + 1 élément (5)
     # - 2 composants de l'élément du template (2)
-    # - La personne en dépendance du composant Person (1)
+    # - la personne en dépendance du composant Person (1)
     block = page.blocks.create(position: 1, published: true, template_kind: :organization_chart)
     block.data = "{ \"elements\": [ { \"id\": \"#{arnaud.id}\" } ] }"
     block.save
-
+    
     page = page.reload
-
+    
     assert_equal 9, page.recursive_dependencies.count
-
+    
     # On modifie le target du block
+    Delayed::Job.destroy_all
     block.data = "{ \"elements\": [ { \"id\": \"#{olivia.id}\" } ] }"
     block.save
+    # On vérifie qu'on appelle bien la méthode destroy_obsolete_git_files sur le site de la page
+    job = find_performable_method_job(:destroy_obsolete_git_files_without_delay, 
+                                      page.communication_website_id)
+    assert(job)
+
+    # Arnaud est remplacé par Olivia, le nombre de dépendances reste le même
+    assert_equal 9, page.recursive_dependencies.count
+  end
+  
+  def test_change_website_dependencies
+    dependencies_before_count = website_with_github.recursive_dependencies.count
+    
+    # On modifie l'about du website en ajoutant une école
+    website_with_github.update(about: default_school)
+    job = find_performable_method_job(:destroy_obsolete_git_files_without_delay, website_with_github.id)
+    refute(job)
+    delta = website_with_github.recursive_dependencies.count - dependencies_before_count
+    assert_equal 12, delta
+    
+    Delayed::Job.destroy_all
+
+    # On enlève l'about du website
+    website_with_github.update(about: nil)
+
+    # On vérifie qu'on appelle bien la méthode destroy_obsolete_git_files sur le site
+    job = find_performable_method_job(:destroy_obsolete_git_files_without_delay, website_with_github.id)
+    assert(job)
+  end
+
+  def test_change_menu_item_dependencies
+    website_with_github.save
+    menu = website_with_github.menus.first
+
+    item = menu.items.create(university: default_university, website: website_with_github, kind: :blank, title: 'Test')
+    assert_equal 2, item.recursive_dependencies.count
+    
+    item.kind = :page
+    item.about = communication_website_pages(:page_with_no_dependency)
+    item.save
+    assert_equal 2, item.recursive_dependencies.count
+  end
+
+  protected
+
+  # On ne peut pas utiliser assert_enqueued_jobs sur les méthodes asynchrones gérées avec handle_asynchronously
+  def find_performable_method_job(method, id)
+    Delayed::Job.all.detect { |job|
+      job.payload_object.method_name == method &&
+        job.payload_object.object.id == id
+    }
   end
 end
-- 
GitLab