20470: Handle nil selection, selecting on writable_by
[arvados.git] / services / api / app / controllers / application_controller.rb
index 3750befb3fed3c1131120a79534a105fbe062ed0..1fbd65fd828841fa4e5806949ef9aac0c4445dee 100644 (file)
@@ -46,7 +46,8 @@ class ApplicationController < ActionController::Base
   before_action :load_limit_offset_order_params, only: [:index, :contents]
   before_action :load_select_param
   before_action(:find_object_by_uuid,
-                except: [:index, :create] + ERROR_ACTIONS)
+                except: [:index, :create, :update] + ERROR_ACTIONS)
+  before_action :find_object_for_update, only: [:update]
   before_action :load_where_param, only: [:index, :contents]
   before_action :load_filters_param, only: [:index, :contents]
   before_action :find_objects_for_index, :only => :index
@@ -100,7 +101,7 @@ class ApplicationController < ActionController::Base
   end
 
   def show
-    send_json @object.as_api_response(nil, select: @select)
+    send_json @object.as_api_response(nil, select: select_for_klass(@select, model_class))
   end
 
   def create
@@ -227,6 +228,22 @@ class ApplicationController < ActionController::Base
     @objects = model_class.apply_filters(@objects, @filters)
   end
 
+  def select_for_klass sel, model_class
+    return nil if sel.nil?
+    # Filter the select fields to only the ones that apply to the
+    # given class.
+    sel.map do |column|
+      sp = column.split(".")
+      if sp.length == 2 && sp[0] == model_class.table_name
+        sp[1]
+      elsif model_class.selectable_attributes.include? column
+        column
+      else
+        nil
+      end
+    end.compact
+  end
+
   def apply_where_limit_order_params model_class=nil
     model_class ||= self.model_class
     apply_filters model_class
@@ -290,7 +307,7 @@ class ApplicationController < ActionController::Base
         # Map attribute names in @select to real column names, resolve
         # those to fully-qualified SQL column names, and pass the
         # resulting string to the select method.
-        columns_list = model_class.columns_for_attributes(@select).
+        columns_list = model_class.columns_for_attributes(select_for_klass @select, model_class).
           map { |s| "#{ar_table_name}.#{ActiveRecord::Base.connection.quote_column_name s}" }
         @objects = @objects.select(columns_list.join(", "))
       end
@@ -316,7 +333,7 @@ class ApplicationController < ActionController::Base
     return if @limit == 0 || @limit == 1
     model_class ||= self.model_class
     limit_columns = model_class.limit_index_columns_read
-    limit_columns &= model_class.columns_for_attributes(@select) if @select
+    limit_columns &= model_class.columns_for_attributes(select_for_klass @select, model_class) if @select
     return if limit_columns.empty?
     model_class.transaction do
       limit_query = @objects.
@@ -464,7 +481,11 @@ class ApplicationController < ActionController::Base
     controller_name
   end
 
-  def find_object_by_uuid
+  def find_object_for_update
+    find_object_by_uuid(with_lock: true)
+  end
+
+  def find_object_by_uuid(with_lock: false)
     if params[:id] and params[:id].match(/\D/)
       params[:uuid] = params.delete :id
     end
@@ -475,7 +496,11 @@ class ApplicationController < ActionController::Base
     @filters = []
     @objects = nil
     find_objects_for_index
-    @object = @objects.first
+    if with_lock && Rails.configuration.API.LockBeforeUpdate
+      @object = @objects.lock.first
+    else
+      @object = @objects.first
+    end
   end
 
   def nullable_attributes
@@ -561,10 +586,10 @@ class ApplicationController < ActionController::Base
       :self_link => "",
       :offset => @offset,
       :limit => @limit,
-      :items => @objects.as_api_response(nil, {select: @select})
+      :items => @objects.as_api_response(nil, {select: select_for_klass(@select, model_class)})
     }
     if @extra_included
-      list[:included] = @extra_included.as_api_response(nil, {select: @select})
+      list[:included] = @extra_included.as_api_response(nil, {select: select_for_klass(@select, model_class)})
     end
     case params[:count]
     when nil, '', 'exact'
@@ -688,11 +713,6 @@ class ApplicationController < ActionController::Base
     }
   end
 
-  def client_accepts_plain_text_stream
-    (request.headers['Accept'].split(' ') &
-     ['text/plain', '*/*']).count > 0
-  end
-
   def render *opts
     if opts.first
       response = opts.first[:json]