8784: Fix test for latest firefox.
[arvados.git] / services / api / lib / can_be_an_owner.rb
1 # Protect referential integrity of owner_uuid columns in other tables
2 # that can refer to the uuid column in this table.
3
4 module CanBeAnOwner
5
6   def self.included(base)
7     base.extend(ClassMethods)
8
9     # Rails' "has_many" can prevent us from destroying the owner
10     # record when other objects refer to it.
11     ActiveRecord::Base.connection.tables.each do |t|
12       next if t == base.table_name
13       next if t == 'schema_migrations'
14       klass = t.classify.constantize
15       next unless klass and 'owner_uuid'.in?(klass.columns.collect(&:name))
16       base.has_many(t.to_sym,
17                     foreign_key: :owner_uuid,
18                     primary_key: :uuid,
19                     dependent: :restrict_with_exception)
20     end
21     # We need custom protection for changing an owner's primary
22     # key. (Apart from this restriction, admins are allowed to change
23     # UUIDs.)
24     base.validate :restrict_uuid_change_breaking_associations
25   end
26
27   module ClassMethods
28     def install_view(type)
29       conn = ActiveRecord::Base.connection
30       transaction do
31         # Check whether the temporary view has already been created
32         # during this connection. If not, create it.
33         conn.exec_query "SAVEPOINT check_#{type}_view"
34         begin
35           conn.exec_query("SELECT 1 FROM #{type}_view LIMIT 0")
36         rescue
37           conn.exec_query "ROLLBACK TO SAVEPOINT check_#{type}_view"
38           sql = File.read(Rails.root.join("lib", "create_#{type}_view.sql"))
39           conn.exec_query(sql)
40         ensure
41           conn.exec_query "RELEASE SAVEPOINT check_#{type}_view"
42         end
43       end
44     end
45   end
46
47   def descendant_project_uuids
48     self.class.install_view('ancestor')
49     ActiveRecord::Base.connection.
50       exec_query('SELECT ancestor_view.uuid
51                   FROM ancestor_view
52                   LEFT JOIN groups ON groups.uuid=ancestor_view.uuid
53                   WHERE ancestor_uuid = $1 AND groups.group_class = $2',
54                   # "name" arg is a query label that appears in logs:
55                   "descendant_project_uuids for #{self.uuid}",
56                   # "binds" arg is an array of [col_id, value] for '$1' vars:
57                   [[nil, self.uuid], [nil, 'project']],
58                   ).rows.map do |project_uuid,|
59       project_uuid
60     end
61   end
62
63   protected
64
65   def restrict_uuid_change_breaking_associations
66     return true if new_record? or not uuid_changed?
67
68     # Check for objects that have my old uuid listed as their owner.
69     self.class.reflect_on_all_associations(:has_many).each do |assoc|
70       next unless assoc.foreign_key == :owner_uuid
71       if assoc.klass.where(owner_uuid: uuid_was).any?
72         errors.add(:uuid,
73                    "cannot be changed on a #{self.class} that owns objects")
74         return false
75       end
76     end
77
78     # if I owned myself before, I'll just continue to own myself with
79     # my new uuid.
80     if owner_uuid == uuid_was
81       self.owner_uuid = uuid
82     end
83   end
84 end