Merge branch '8784-dir-listings'
[arvados.git] / services / api / lib / can_be_an_owner.rb
index 16a878347aac2088174a4b80a07ae6a565ab2daf..f5b2fe98f8f7552270c7794b391d96c9f6178e50 100644 (file)
@@ -1,9 +1,15 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
 # Protect referential integrity of owner_uuid columns in other tables
 # that can refer to the uuid column in this table.
 
 module CanBeAnOwner
 
   def self.included(base)
+    base.extend(ClassMethods)
+
     # Rails' "has_many" can prevent us from destroying the owner
     # record when other objects refer to it.
     ActiveRecord::Base.connection.tables.each do |t|
@@ -14,7 +20,7 @@ module CanBeAnOwner
       base.has_many(t.to_sym,
                     foreign_key: :owner_uuid,
                     primary_key: :uuid,
-                    dependent: :restrict)
+                    dependent: :restrict_with_exception)
     end
     # We need custom protection for changing an owner's primary
     # key. (Apart from this restriction, admins are allowed to change
@@ -22,6 +28,42 @@ module CanBeAnOwner
     base.validate :restrict_uuid_change_breaking_associations
   end
 
+  module ClassMethods
+    def install_view(type)
+      conn = ActiveRecord::Base.connection
+      transaction do
+        # Check whether the temporary view has already been created
+        # during this connection. If not, create it.
+        conn.exec_query "SAVEPOINT check_#{type}_view"
+        begin
+          conn.exec_query("SELECT 1 FROM #{type}_view LIMIT 0")
+        rescue
+          conn.exec_query "ROLLBACK TO SAVEPOINT check_#{type}_view"
+          sql = File.read(Rails.root.join("lib", "create_#{type}_view.sql"))
+          conn.exec_query(sql)
+        ensure
+          conn.exec_query "RELEASE SAVEPOINT check_#{type}_view"
+        end
+      end
+    end
+  end
+
+  def descendant_project_uuids
+    self.class.install_view('ancestor')
+    ActiveRecord::Base.connection.
+      exec_query('SELECT ancestor_view.uuid
+                  FROM ancestor_view
+                  LEFT JOIN groups ON groups.uuid=ancestor_view.uuid
+                  WHERE ancestor_uuid = $1 AND groups.group_class = $2',
+                  # "name" arg is a query label that appears in logs:
+                  "descendant_project_uuids for #{self.uuid}",
+                  # "binds" arg is an array of [col_id, value] for '$1' vars:
+                  [[nil, self.uuid], [nil, 'project']],
+                  ).rows.map do |project_uuid,|
+      project_uuid
+    end
+  end
+
   protected
 
   def restrict_uuid_change_breaking_associations
@@ -43,5 +85,4 @@ module CanBeAnOwner
       self.owner_uuid = uuid
     end
   end
-
 end