before_filter :require_auth_scope_all, :except => :render_not_found
before_filter :catch_redirect_hint
- before_filter :load_where_param, :only => :index
- before_filter :load_filters_param, :only => :index
- before_filter :find_objects_for_index, :only => :index
before_filter :find_object_by_uuid, :except => [:index, :create,
:render_error,
:render_not_found]
+ before_filter :load_limit_offset_order_params, only: [:index, :owned_items]
+ before_filter :load_where_param, only: [:index, :owned_items]
+ before_filter :load_filters_param, only: [:index, :owned_items]
+ before_filter :find_objects_for_index, :only => :index
before_filter :reload_object_before_update, :only => :update
before_filter :render_404_if_no_object, except: [:index, :create,
:render_error,
attr_accessor :resource_attrs
+ DEFAULT_LIMIT = 100
+
def index
@objects.uniq!(&:id)
if params[:eager] and params[:eager] != '0' and params[:eager] != 0 and params[:eager] != ''
show
end
+ def self._owned_items_requires_parameters
+ _index_requires_parameters.
+ merge({
+ include_linked: {
+ type: 'boolean', required: false, default: false
+ },
+ })
+ end
+
+ def owned_items
+ all_objects = []
+ all_available = 0
+
+ # Trick apply_where_limit_order_params into applying suitable
+ # per-table values. *_all are the real ones we'll apply to the
+ # aggregate set.
+ limit_all = @limit
+ offset_all = @offset
+ @orders = []
+
+ ArvadosModel.descendants.
+ reject(&:abstract_class?).
+ sort_by(&:to_s).
+ each do |klass|
+ case klass.to_s
+ # We might expect klass==Link etc. here, but we would be
+ # disappointed: when Rails reloads model classes, we get two
+ # distinct classes called Link which do not equal each
+ # other. But we can still rely on klass.to_s to be "Link".
+ when 'ApiClientAuthorization'
+ # Do not want.
+ else
+ @objects = klass.readable_by(current_user)
+ cond_sql = "#{klass.table_name}.owner_uuid = ?"
+ cond_params = [@object.uuid]
+ if params[:include_linked]
+ @objects = @objects.
+ joins("LEFT JOIN links mng_links"\
+ " ON mng_links.link_class=#{klass.sanitize 'permission'}"\
+ " AND mng_links.name=#{klass.sanitize 'can_manage'}"\
+ " AND mng_links.tail_uuid=#{klass.sanitize @object.uuid}"\
+ " AND mng_links.head_uuid=#{klass.table_name}.uuid")
+ cond_sql += " OR mng_links.uuid IS NOT NULL"
+ end
+ @objects = @objects.where(cond_sql, *cond_params)
+ @limit = limit_all - all_objects.count
+ apply_where_limit_order_params
+ items_available = @objects.
+ except(:limit).except(:offset).
+ count(:id, distinct: true)
+ all_available += items_available
+ @offset = [@offset - items_available, 0].max
+
+ all_objects += @objects.to_a
+ end
+ end
+ @objects = all_objects || []
+ @object_list = {
+ :kind => "arvados#objectList",
+ :etag => "",
+ :self_link => "",
+ :offset => offset_all,
+ :limit => limit_all,
+ :items_available => all_available,
+ :items => @objects.as_api_response(nil)
+ }
+ render json: @object_list
+ end
+
def catch_redirect_hint
if !current_user
if params.has_key?('redirect_to') then
end
end
+ def load_limit_offset_order_params
+ if params[:limit]
+ unless params[:limit].to_s.match(/^\d+$/)
+ raise ArgumentError.new("Invalid value for limit parameter")
+ end
+ @limit = params[:limit].to_i
+ else
+ @limit = DEFAULT_LIMIT
+ end
+
+ if params[:offset]
+ unless params[:offset].to_s.match(/^\d+$/)
+ raise ArgumentError.new("Invalid value for offset parameter")
+ end
+ @offset = params[:offset].to_i
+ else
+ @offset = 0
+ end
+
+ @orders = []
+ if params[:order]
+ params[:order].split(',').each do |order|
+ attr, direction = order.strip.split " "
+ direction ||= 'asc'
+ if attr.match /^[a-z][_a-z0-9]+$/ and
+ model_class.columns.collect(&:name).index(attr) and
+ ['asc','desc'].index direction.downcase
+ @orders << "#{table_name}.#{attr} #{direction.downcase}"
+ end
+ end
+ end
+ if @orders.empty?
+ @orders << "#{table_name}.modified_at desc"
+ end
+ end
+
def find_objects_for_index
@objects ||= model_class.readable_by(current_user)
apply_where_limit_order_params
end
def apply_where_limit_order_params
+ ar_table_name = @objects.table_name
if @filters.is_a? Array and @filters.any?
cond_out = []
param_out = []
case operator.downcase
when '=', '<', '<=', '>', '>=', 'like'
if operand.is_a? String
- cond_out << "#{table_name}.#{attr} #{operator} ?"
+ cond_out << "#{ar_table_name}.#{attr} #{operator} ?"
if (# any operator that operates on value rather than
# representation:
operator.match(/[<=>]/) and
operand = Time.parse operand
end
param_out << operand
+ elsif operand.nil? and operator == '='
+ cond_out << "#{ar_table_name}.#{attr} is null"
else
- raise ArgumentError.new("Invalid operand type '#{operand.class}' for '#{operator}' operator in filter")
+ raise ArgumentError.new("Invalid operand type '#{operand.class}' "\
+ "for '#{operator}' operator in filters")
end
when 'in'
if operand.is_a? Array
- cond_out << "#{table_name}.#{attr} IN (?)"
+ cond_out << "#{ar_table_name}.#{attr} IN (?)"
param_out << operand
else
- raise ArgumentError.new("Invalid argument '#{operand}' for 'in' operator in filter")
+ raise ArgumentError.new("Invalid operand type '#{operand.class}' "\
+ "for '#{operator}' operator in filters")
end
when 'is_a'
operand = [operand] unless operand.is_a? Array
operand.each do |op|
cl = ArvadosModel::kind_class op
if cl
- cond << "#{table_name}.#{attr} like ?"
+ cond << "#{ar_table_name}.#{attr} like ?"
param_out << cl.uuid_like_pattern
else
cond << "1=0"
# Including owner_uuid in an "any column" search will
# probably just return a lot of false positives.
next if column == 'owner_uuid'
- ilikes << "#{table_name}.#{column} ilike ?"
+ ilikes << "#{ar_table_name}.#{column} ilike ?"
conditions << "%#{value[1]}%"
end
if ilikes.any?
elsif attr.to_s.match(/^[a-z][_a-z0-9]+$/) and
model_class.columns.collect(&:name).index(attr.to_s)
if value.nil?
- conditions[0] << " and #{table_name}.#{attr} is ?"
+ conditions[0] << " and #{ar_table_name}.#{attr} is ?"
conditions << nil
elsif value.is_a? Array
if value[0] == 'contains' and value.length == 2
- conditions[0] << " and #{table_name}.#{attr} like ?"
+ conditions[0] << " and #{ar_table_name}.#{attr} like ?"
conditions << "%#{value[1]}%"
else
- conditions[0] << " and #{table_name}.#{attr} in (?)"
+ conditions[0] << " and #{ar_table_name}.#{attr} in (?)"
conditions << value
end
elsif value.is_a? String or value.is_a? Fixnum or value == true or value == false
- conditions[0] << " and #{table_name}.#{attr}=?"
+ conditions[0] << " and #{ar_table_name}.#{attr}=?"
conditions << value
elsif value.is_a? Hash
# Not quite the same thing as "equal?" but better than nothing?
value.each do |k,v|
if v.is_a? String
- conditions[0] << " and #{table_name}.#{attr} ilike ?"
+ conditions[0] << " and #{ar_table_name}.#{attr} ilike ?"
conditions << "%#{k}%#{v}%"
end
end
end
end
- if params[:limit]
- begin
- @limit = params[:limit].to_i
- rescue
- raise ArgumentError.new("Invalid value for limit parameter")
- end
- else
- @limit = 100
- end
+ @objects = @objects.order(@orders.join ", ") if @orders.any?
@objects = @objects.limit(@limit)
-
- orders = []
-
- if params[:offset]
- begin
- @objects = @objects.offset(params[:offset].to_i)
- @offset = params[:offset].to_i
- rescue
- raise ArgumentError.new("Invalid value for limit parameter")
- end
- else
- @offset = 0
- end
-
- orders = []
- if params[:order]
- params[:order].split(',').each do |order|
- attr, direction = order.strip.split " "
- direction ||= 'asc'
- if attr.match /^[a-z][_a-z0-9]+$/ and
- model_class.columns.collect(&:name).index(attr) and
- ['asc','desc'].index direction.downcase
- orders << "#{table_name}.#{attr} #{direction.downcase}"
- end
- end
- end
- if orders.empty?
- orders << "#{table_name}.modified_at desc"
- end
- @objects = @objects.order(orders.join ", ")
+ @objects = @objects.offset(@offset)
end
def resource_attrs
params[:uuid] = params.delete :id
end
@where = { uuid: params[:uuid] }
+ @offset = 0
+ @limit = 1
+ @orders = []
+ @filters = []
+ @objects = nil
find_objects_for_index
@object = @objects.first
end
{
filters: { type: 'array', required: false },
where: { type: 'object', required: false },
- order: { type: 'string', required: false }
+ order: { type: 'string', required: false },
+ limit: { type: 'integer', required: false, default: DEFAULT_LIMIT },
+ offset: { type: 'integer', required: false, default: 0 },
}
end