Merge branch '8784-dir-listings'
[arvados.git] / apps / workbench / app / models / arvados_base.rb
index f5be0e1edcba20ddfb1f80f4ece1912eac0d5dfd..8e1cfae8a2858fba0a0b6f8c836b6dc65168a44f 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
 class ArvadosBase < ActiveRecord::Base
   self.abstract_class = true
   attr_accessor :attribute_sortkey
@@ -53,6 +57,7 @@ class ArvadosBase < ActiveRecord::Base
       'modified_by_client_uuid' => '203',
       'uuid' => '999',
     }
+    @loaded_attributes = {}
   end
 
   def self.columns
@@ -74,6 +79,14 @@ class ArvadosBase < ActiveRecord::Base
           @columns << column(k, :text)
           serialize k, coldef[:type].constantize
         end
+        define_method k do
+          unless new_record? or @loaded_attributes.include? k.to_s
+            Rails.logger.debug "BUG: access non-loaded attribute #{k}"
+            # We should...
+            # raise ActiveModel::MissingAttributeError, "missing attribute: #{k}"
+          end
+          super()
+        end
         @attribute_info[k] = coldef
       end
     end
@@ -135,12 +148,28 @@ class ArvadosBase < ActiveRecord::Base
     ArvadosResourceList.new(self).select(*args)
   end
 
+  def self.with_count(*args)
+    ArvadosResourceList.new(self).with_count(*args)
+  end
+
+  def self.distinct(*args)
+    ArvadosResourceList.new(self).distinct(*args)
+  end
+
+  def self.include_trash(*args)
+    ArvadosResourceList.new(self).include_trash(*args)
+  end
+
+  def self.recursive(*args)
+    ArvadosResourceList.new(self).recursive(*args)
+  end
+
   def self.eager(*args)
     ArvadosResourceList.new(self).eager(*args)
   end
 
-  def self.all(*args)
-    ArvadosResourceList.new(self).all(*args)
+  def self.all
+    ArvadosResourceList.new(self)
   end
 
   def self.permit_attribute_params raw_params
@@ -167,8 +196,15 @@ class ArvadosBase < ActiveRecord::Base
   def save
     obdata = {}
     self.class.columns.each do |col|
-      unless self.send(col.name.to_sym).nil? and !self.changed.include?(col.name)
-          obdata[col.name.to_sym] = self.send(col.name.to_sym)
+      # Non-nil serialized values must be sent because we can't tell
+      # whether they've changed. Other than that, any given attribute
+      # is either unchanged (in which case there's no need to send its
+      # old value in the update/create command) or has been added to
+      # #changed by ActiveRecord's #attr= method.
+      if changed.include? col.name or
+          (self.class.serialized_attributes.include? col.name and
+           @loaded_attributes[col.name])
+        obdata[col.name.to_sym] = self.send col.name
       end
     end
     obdata.delete :id
@@ -194,6 +230,7 @@ class ArvadosBase < ActiveRecord::Base
       end
     end
 
+    changes_applied
     @new_record = false
 
     self
@@ -262,6 +299,7 @@ class ArvadosBase < ActiveRecord::Base
       hash = arvados_api_client.api(self.class, '/' + uuid_or_hash)
     end
     hash.each do |k,v|
+      @loaded_attributes[k.to_s] = true
       if self.respond_to?(k.to_s + '=')
         self.send(k.to_s + '=', v)
       else
@@ -276,6 +314,7 @@ class ArvadosBase < ActiveRecord::Base
       end
     end
     @all_links = nil
+    changes_applied
     @new_record = false
     self
   end
@@ -311,7 +350,7 @@ class ArvadosBase < ActiveRecord::Base
   end
 
   def self.creatable?
-    current_user
+    current_user.andand.is_active && api_exists?(:create)
   end
 
   def self.goes_in_projects?
@@ -334,6 +373,14 @@ class ArvadosBase < ActiveRecord::Base
        (ArvadosBase.find(owner_uuid).writable_by.include? current_user.uuid rescue false)))) or false
   end
 
+  def deletable?
+    editable?
+  end
+
+  def self.api_exists?(method)
+    arvados_api_client.discovery[:resources][self.to_s.underscore.pluralize.to_sym].andand[:methods].andand[method]
+  end
+
   # Array of strings that are the names of attributes that can be edited
   # with X-Editable.
   def editable_attributes