-
Notifications
You must be signed in to change notification settings - Fork 7
/
query.rb
140 lines (124 loc) · 4.81 KB
/
query.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
module Services
class Query
include ObjectClass
COMMA_REGEX = /\s*,\s*/
TABLE_NAME_REGEX = /\A([A-Za-z0-9_]+)\./
CREATED_BEFORE_AFTER_REGEX = /\Acreated_(before|after)\z/
class << self
delegate :call, to: :new
def convert_condition_objects_to_ids(*class_names)
@object_to_id_class_names = class_names
end
def object_to_id_class_names
@object_to_id_class_names || []
end
end
def call(ids_or_conditions = {}, _conditions = {})
ids, conditions = if ids_or_conditions.is_a?(Hash)
if _conditions.any?
fail ArgumentError, 'If conditions are passed as first argument, there must not be a second argument.'
end
[[], ids_or_conditions.symbolize_keys]
else
if ids_or_conditions.nil?
fail ArgumentError, 'IDs must not be nil.'
end
[Array(ids_or_conditions), _conditions.symbolize_keys]
end
object_table_id = "#{object_class.table_name}.id"
unless conditions.key?(:order)
conditions[:order] = object_table_id
end
scope = conditions.delete(:scope).try(:dup) || object_class.all
if ids.any?
scope = scope.where(object_table_id => ids)
end
if conditions.any?
self.class.object_to_id_class_names.each do |class_name|
if object_or_objects = conditions.delete(class_name)
ids = case object_or_objects
when Array
object_or_objects.map(&:id)
when ActiveRecord::Relation
object_or_objects.select(:id)
else
[object_or_objects.id]
end
conditions[:"#{class_name}_id"] = ids.size == 1 ? ids.first : ids
end
end
conditions.each do |k, v|
if new_scope = process(scope, k, v)
conditions.delete k
scope = new_scope
end
end
# If a JOIN is involved, use a subquery to make sure we get DISTINCT records.
if scope.to_sql =~ / join /i
scope = object_class.where(id: scope.select("DISTINCT #{object_table_id}"))
end
end
conditions.each do |k, v|
allowed_class_methods = Services.configuration.allowed_class_methods_in_queries[object_class.to_s]
if allowed_class_methods&.key?(k)
arity = allowed_class_methods[k] || object_class.method(k).arity
case arity
when 0
unless v == true
raise ArgumentError, "Method #{k} of class #{self} takes no arguments, so `true` must be passed as the value for this param, not #{v} (#{v.class})."
end
scope = scope.public_send(k)
when 1, -1
scope = scope.public_send(k, v)
else
unless v.is_a?(Array)
raise ArgumentError, "Method #{k} of class #{self} takes more than one argument, so an array must be passed as the value for this param, not #{v} (#{v.class})."
end
scope = scope.public_send(k, *v)
end
else
case k
when :id_not
scope = scope.where.not(id: v)
when CREATED_BEFORE_AFTER_REGEX
operator = $1 == 'before' ? '<' : '>'
scope = scope.where("#{object_class.table_name}.created_at #{operator} ?", v)
when :order
next unless v
order = v.split(COMMA_REGEX).map do |order_part|
table_name = order_part[TABLE_NAME_REGEX, 1]
case
when table_name && table_name != object_class.table_name
unless reflection = object_class.reflections.values.detect { |reflection| reflection.table_name == table_name }
fail "Reflection on class #{object_class} with table name #{table_name} not found."
end
if ActiveRecord::VERSION::MAJOR >= 5
scope = scope.left_outer_joins(reflection.name)
else
join_conditions = "LEFT OUTER JOIN #{table_name} ON #{table_name}.#{reflection.foreign_key} = #{object_class.table_name}.id"
if reflection.type
join_conditions << " AND #{table_name}.#{reflection.type} = '#{object_class}'"
end
scope = scope.joins(join_conditions)
end
when !table_name
order_part.prepend "#{object_class.table_name}."
end
order_part
end.join(', ')
scope = scope.order(order)
when :limit
scope = scope.limit(v)
when :page
scope = scope.page(v)
when :per_page
scope = scope.per(v)
else
raise ArgumentError, "Unexpected condition: #{k}"
end
end
end
scope
end
end
end