1 # Mixin module providing a method to convert filters into a list of SQL
2 # fragments suitable to be fed to ActiveRecord #where.
11 # +filters+ array of conditions, each being [column, operator, operand]
12 # +ar_table_name+ name of SQL table
16 # :cond_out array of SQL fragments for each filter expression
17 # :param_out array of values for parameter substitution in cond_out
18 def record_filters filters, ar_table_name
22 filters.each do |filter|
23 attr, operator, operand = filter
24 if !filter.is_a? Array
25 raise ArgumentError.new("Invalid element in filters array: #{filter.inspect} is not an array")
26 elsif !operator.is_a? String
27 raise ArgumentError.new("Invalid operator '#{operator}' (#{operator.class}) in filter")
28 elsif !model_class.searchable_columns(operator).index attr.to_s
29 raise ArgumentError.new("Invalid attribute '#{attr}' in filter")
31 case operator.downcase
32 when '=', '<', '<=', '>', '>=', '!=', 'like'
33 attr_type = model_class.attribute_column(attr).type
34 operator = '<>' if operator == '!='
35 if operand.is_a? String
36 if attr_type == :boolean
37 if not ['=', '<>'].include?(operator)
38 raise ArgumentError.new("Invalid operator '#{operator}' for " \
39 "boolean attribute '#{attr}'")
42 when '1', 't', 'true', 'y', 'yes'
44 when '0', 'f', 'false', 'n', 'no'
47 raise ArgumentError("Invalid operand '#{operand}' for " \
48 "boolean attribute '#{attr}'")
51 cond_out << "#{ar_table_name}.#{attr} #{operator} ?"
52 if (# any operator that operates on value rather than
54 operator.match(/[<=>]/) and (attr_type == :datetime))
55 operand = Time.parse operand
58 elsif operand.nil? and operator == '='
59 cond_out << "#{ar_table_name}.#{attr} is null"
60 elsif operand.nil? and operator == '<>'
61 cond_out << "#{ar_table_name}.#{attr} is not null"
62 elsif (attr_type == :boolean) and ['=', '<>'].include?(operator) and
63 [true, false].include?(operand)
64 cond_out << "#{ar_table_name}.#{attr} #{operator} ?"
67 raise ArgumentError.new("Invalid operand type '#{operand.class}' "\
68 "for '#{operator}' operator in filters")
71 if operand.is_a? Array
72 cond_out << "#{ar_table_name}.#{attr} #{operator} (?)"
74 if operator == 'not in' and not operand.include?(nil)
75 # explicitly allow NULL
76 cond_out[-1] = "(#{cond_out[-1]} OR #{ar_table_name}.#{attr} IS NULL)"
79 raise ArgumentError.new("Invalid operand type '#{operand.class}' "\
80 "for '#{operator}' operator in filters")
83 operand = [operand] unless operand.is_a? Array
86 cl = ArvadosModel::kind_class op
88 cond << "#{ar_table_name}.#{attr} like ?"
89 param_out << cl.uuid_like_pattern
94 cond_out << cond.join(' OR ')
98 {:cond_out => cond_out, :param_out => param_out}