From: Peter Amstutz Date: Tue, 22 Apr 2014 21:12:12 +0000 (-0400) Subject: Refactored load_filters_param and apply_where_limit_order_params into lib/ X-Git-Tag: 1.1.0~2596^2~27^2~21 X-Git-Url: https://git.arvados.org/arvados.git/commitdiff_plain/04cc77648cc62c73433801475c27ede4ceb76c8b?ds=sidebyside Refactored load_filters_param and apply_where_limit_order_params into lib/ directory so that EventBus can use them. Started adding filtering to EventBus, this is incomplete. --- diff --git a/services/api/app/controllers/application_controller.rb b/services/api/app/controllers/application_controller.rb index 033eccda92..9c5497bed6 100644 --- a/services/api/app/controllers/application_controller.rb +++ b/services/api/app/controllers/application_controller.rb @@ -1,6 +1,11 @@ +require 'load_param' +require 'record_filters' + class ApplicationController < ActionController::Base include CurrentApiClient include ThemesForRails::ActionController + include LoadParam + include RecordFilters respond_to :json protect_from_forgery @@ -103,178 +108,12 @@ class ApplicationController < ActionController::Base protected - def load_where_param - if params[:where].nil? or params[:where] == "" - @where = {} - elsif params[:where].is_a? Hash - @where = params[:where] - elsif params[:where].is_a? String - begin - @where = Oj.load(params[:where]) - raise unless @where.is_a? Hash - rescue - raise ArgumentError.new("Could not parse \"where\" param as an object") - end - end - @where = @where.with_indifferent_access - end - - def load_filters_param - @filters ||= [] - if params[:filters].is_a? Array - @filters += params[:filters] - elsif params[:filters].is_a? String and !params[:filters].empty? - begin - f = Oj.load params[:filters] - raise unless f.is_a? Array - @filters += f - rescue - raise ArgumentError.new("Could not parse \"filters\" param as an array") - end - 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 - if @filters.is_a? Array and @filters.any? - cond_out = [] - param_out = [] - @filters.each do |attr, operator, operand| - if !model_class.searchable_columns(operator).index attr.to_s - raise ArgumentError.new("Invalid attribute '#{attr}' in condition") - end - case operator.downcase - when '=', '<', '<=', '>', '>=', 'like' - if operand.is_a? String - cond_out << "#{table_name}.#{attr} #{operator} ?" - if (# any operator that operates on value rather than - # representation: - operator.match(/[<=>]/) and - model_class.attribute_column(attr).type == :datetime) - operand = Time.parse operand - end - param_out << operand - end - when 'in' - if operand.is_a? Array - cond_out << "#{table_name}.#{attr} IN (?)" - param_out << operand - end - when 'is_a' - operand = [operand] unless operand.is_a? Array - cond = [] - operand.each do |op| - cl = ArvadosModel::kind_class op - if cl - cond << "#{table_name}.#{attr} like ?" - param_out << cl.uuid_like_pattern - else - cond << "1=0" - end - end - cond_out << cond.join(' OR ') - end - end - if cond_out.any? - @objects = @objects.where(cond_out.join(' AND '), *param_out) - end - end - if @where.is_a? Hash and @where.any? - conditions = ['1=1'] - @where.each do |attr,value| - if attr.to_s == 'any' - if value.is_a?(Array) and - value.length == 2 and - value[0] == 'contains' then - ilikes = [] - model_class.searchable_columns('ilike').each do |column| - ilikes << "#{table_name}.#{column} ilike ?" - conditions << "%#{value[1]}%" - end - if ilikes.any? - conditions[0] << ' and (' + ilikes.join(' or ') + ')' - end - end - 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 << nil - elsif value.is_a? Array - if value[0] == 'contains' and value.length == 2 - conditions[0] << " and #{table_name}.#{attr} like ?" - conditions << "%#{value[1]}%" - else - conditions[0] << " and #{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 << 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 << "%#{k}%#{v}%" - end - end - end - end - end - if conditions.length > 1 - conditions[0].sub!(/^1=1 and /, '') - @objects = @objects. - where(*conditions) - 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.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 ", ") - end - def resource_attrs return @attrs if @attrs @attrs = params[resource_name] diff --git a/services/api/lib/eventbus.rb b/services/api/lib/eventbus.rb index 26674b7088..646ee83a89 100644 --- a/services/api/lib/eventbus.rb +++ b/services/api/lib/eventbus.rb @@ -1,14 +1,52 @@ require 'eventmachine' require 'oj' require 'faye/websocket' +require 'record_filters' module Faye class WebSocket attr_accessor :user attr_accessor :last_log_id + attr_accessor :filters end end +class Filter + include LoadParam + + def initialize p + @p = p + load_filters_param + end + + def params + @p + end + + def filters + @filters + end +end + +class FilterController + include RecordFilters + + def initialize(f, o) + @filters = f + @objects = o + apply_where_limit_order_params + end + + def each &b + @objects.each &b + end + + def params + {} + end + +end + class EventBus include CurrentApiClient @@ -26,20 +64,24 @@ class EventBus end ws.user = current_user + ws.filters = [] sub = @channel.subscribe do |msg| Log.where(id: msg.to_i).each do |l| + ws.last_log_id = msg.to_i if rsc = ArvadosModel::resource_class_for_uuid(l.object_uuid) - rsc.readable_by(ws.user).where(uuid: l.object_uuid).each do - ws.send(l.as_api_response.to_json) + permitted = rsc.readable_by(ws.user).where(uuid: l.object_uuid) + ws.filters.each do |filter| + FilterController.new(filter, permitted).each do + ws.send(l.as_api_response.to_json) + end end end - ws.last_log_id = msg.to_i end end ws.on :message do |event| - #puts "got #{event.data}" + ws.filters = Filter.new oj.parse(event.data) end ws.on :close do |event| diff --git a/services/api/lib/load_param.rb b/services/api/lib/load_param.rb new file mode 100644 index 0000000000..f1c3198b0e --- /dev/null +++ b/services/api/lib/load_param.rb @@ -0,0 +1,39 @@ +# Expects: +# +params+ Hash +# Sets: +# @where, @filters + +module LoadParam + + def load_where_param + if params[:where].nil? or params[:where] == "" + @where = {} + elsif params[:where].is_a? Hash + @where = params[:where] + elsif params[:where].is_a? String + begin + @where = Oj.load(params[:where]) + raise unless @where.is_a? Hash + rescue + raise ArgumentError.new("Could not parse \"where\" param as an object") + end + end + @where = @where.with_indifferent_access + end + + def load_filters_param + @filters ||= [] + if params[:filters].is_a? Array + @filters += params[:filters] + elsif params[:filters].is_a? String and !params[:filters].empty? + begin + f = Oj.load params[:filters] + raise unless f.is_a? Array + @filters += f + rescue + raise ArgumentError.new("Could not parse \"filters\" param as an array") + end + end + end + +end diff --git a/services/api/lib/record_filters.rb b/services/api/lib/record_filters.rb new file mode 100644 index 0000000000..fab16bbc1d --- /dev/null +++ b/services/api/lib/record_filters.rb @@ -0,0 +1,145 @@ +# Expects: +# @where +# @filters +# +model_class+ +# Operates on: +# @objects +module RecordFilters + + def apply_where_limit_order_params + if @filters.is_a? Array and @filters.any? + cond_out = [] + param_out = [] + @filters.each do |attr, operator, operand| + if !model_class.searchable_columns(operator).index attr.to_s + raise ArgumentError.new("Invalid attribute '#{attr}' in condition") + end + case operator.downcase + when '=', '<', '<=', '>', '>=', 'like' + if operand.is_a? String + cond_out << "#{table_name}.#{attr} #{operator} ?" + if (# any operator that operates on value rather than + # representation: + operator.match(/[<=>]/) and + model_class.attribute_column(attr).type == :datetime) + operand = Time.parse operand + end + param_out << operand + end + when 'in' + if operand.is_a? Array + cond_out << "#{table_name}.#{attr} IN (?)" + param_out << operand + end + when 'is_a' + operand = [operand] unless operand.is_a? Array + cond = [] + operand.each do |op| + cl = ArvadosModel::kind_class op + if cl + cond << "#{table_name}.#{attr} like ?" + param_out << cl.uuid_like_pattern + else + cond << "1=0" + end + end + cond_out << cond.join(' OR ') + end + end + if cond_out.any? + @objects = @objects.where(cond_out.join(' AND '), *param_out) + end + end + if @where.is_a? Hash and @where.any? + conditions = ['1=1'] + @where.each do |attr,value| + if attr.to_s == 'any' + if value.is_a?(Array) and + value.length == 2 and + value[0] == 'contains' then + ilikes = [] + model_class.searchable_columns('ilike').each do |column| + ilikes << "#{table_name}.#{column} ilike ?" + conditions << "%#{value[1]}%" + end + if ilikes.any? + conditions[0] << ' and (' + ilikes.join(' or ') + ')' + end + end + 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 << nil + elsif value.is_a? Array + if value[0] == 'contains' and value.length == 2 + conditions[0] << " and #{table_name}.#{attr} like ?" + conditions << "%#{value[1]}%" + else + conditions[0] << " and #{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 << 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 << "%#{k}%#{v}%" + end + end + end + end + end + if conditions.length > 1 + conditions[0].sub!(/^1=1 and /, '') + @objects = @objects. + where(*conditions) + 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.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 ", ") + end + +end