activerecord-filter 5.0.0.beta1 → 5.0.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/active_record/filter.rb +321 -7
- data/lib/active_record/filter/version.rb +1 -1
- metadata +41 -20
- data/ext/active_record/base.rb +0 -268
- data/ext/active_record/query_methods.rb +0 -27
- data/ext/active_record/unkown_filter_error.rb +0 -9
- data/ext/arel/attributes/array.rb +0 -5
- data/ext/arel/nodes/contains.rb +0 -6
- data/ext/arel/nodes/overlaps.rb +0 -6
- data/ext/arel/visitors/postgresql.rb +0 -37
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 70a1e7037c74baea87037f1a7e6bf7dc7a581754
|
4
|
+
data.tar.gz: 09391e5064f066076c345556c521e08016deebf2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0e84af9fa5d6fbb5ed2e16cfa473af1f117d67c9d108c7200cd0a6331773bb6e9e331fc7079f257958c148678ef0034e3353c08acebca85f8a0514cb38453703
|
7
|
+
data.tar.gz: d66507821b1dc004c496f34c5565899377869725474cba87a83d5481e995734811e77f03e672dad3501ebab7b9d2877e8d491b310f8d85d2cadf0a699722b9f5
|
data/lib/active_record/filter.rb
CHANGED
@@ -1,9 +1,323 @@
|
|
1
1
|
require 'active_record'
|
2
|
+
require 'arel/extensions'
|
2
3
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
4
|
+
class ActiveRecord::UnkownFilterError < NoMethodError
|
5
|
+
attr_reader :klass, :filter
|
6
|
+
|
7
|
+
def initialize(klass, filter)
|
8
|
+
@klass = klass
|
9
|
+
@filter = filter.to_s
|
10
|
+
super("unkown filter #{filter.inspect} for #{klass}.")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module ActiveRecord::Filter
|
15
|
+
|
16
|
+
def inherited(subclass)
|
17
|
+
super
|
18
|
+
subclass.instance_variable_set('@filters', HashWithIndifferentAccess.new)
|
19
|
+
end
|
20
|
+
|
21
|
+
def filter_on(name, lambda)
|
22
|
+
@filters[name] = lambda
|
23
|
+
end
|
24
|
+
|
25
|
+
def filter(filters, options={})
|
26
|
+
resource = all
|
27
|
+
return resource unless filters
|
28
|
+
|
29
|
+
if filters.is_a?(Hash) || filters.class.name == "ActionController::Parameters".freeze
|
30
|
+
filters.each do |key, value|
|
31
|
+
if @filters[key]
|
32
|
+
#TODO add test for this... not sure how rails does this lambda call,
|
33
|
+
# do they run it in a context for merge?
|
34
|
+
resource = resource.merge( @filters[key].call(value) )
|
35
|
+
else
|
36
|
+
resource = resource.filter_for(key, value, options)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
elsif filters.is_a?(Array) || filters.is_a?(Integer)
|
40
|
+
resource = resource.filter_for(:id, filters, options)
|
41
|
+
end
|
42
|
+
|
43
|
+
resource
|
44
|
+
end
|
45
|
+
|
46
|
+
def filter_for(key, value, options={})
|
47
|
+
column = columns_hash[key.to_s]
|
48
|
+
if column && column.array
|
49
|
+
all.filter_for_array(key, value, options)
|
50
|
+
elsif column
|
51
|
+
all.send("filter_for_#{column.type}", key, value, options)
|
52
|
+
else
|
53
|
+
if relation = reflect_on_association(key)
|
54
|
+
self.send("filter_for_#{relation.macro}", relation, value)
|
55
|
+
else
|
56
|
+
# Custome filter, try to guess based on value
|
57
|
+
# raise ActiveRecord::UnkownFilterError.new(self, key)
|
58
|
+
if value.is_a?(Hash) || value.class.name == "ActionController::Parameters".freeze
|
59
|
+
all.send("filter_for_#{value.values.first.class.to_s.downcase}", key, value, options)
|
60
|
+
else
|
61
|
+
all.send("filter_for_#{value.class.to_s.downcase}", key, value, options)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
{
|
68
|
+
filter_for_geometry: :itself,
|
69
|
+
filter_for_datetime: :to_datetime,
|
70
|
+
filter_for_integer: :to_i,
|
71
|
+
filter_for_text: :itself,
|
72
|
+
filter_for_boolean: :itself,
|
73
|
+
filter_for_string: :itself,
|
74
|
+
filter_for_uuid: :itself,
|
75
|
+
filter_for_decimal: :to_f,
|
76
|
+
filter_for_float: :to_f
|
77
|
+
}.each_pair do |method_name, send_method|
|
78
|
+
define_method(method_name) do |column, value, options={}|
|
79
|
+
table = options[:table_alias] ? arel_table.alias(options[:table_alias]) : arel_table
|
80
|
+
|
81
|
+
if value.is_a?(Hash) || value.class.name == "ActionController::Parameters".freeze
|
82
|
+
resource = all
|
83
|
+
value.each_pair do |key, value|
|
84
|
+
converted_value = if value.is_a?(Array)
|
85
|
+
value.map { |x| x.try(:send, send_method) }
|
86
|
+
else
|
87
|
+
value.try(:send, send_method)
|
88
|
+
end
|
89
|
+
|
90
|
+
resource = case key.to_sym
|
91
|
+
when :equal_to, :eq
|
92
|
+
resource.where(table[column].eq(converted_value))
|
93
|
+
when :greater_than, :gt
|
94
|
+
resource.where(table[column].gt(converted_value))
|
95
|
+
when :less_than, :lt
|
96
|
+
resource.where(table[column].lt(converted_value))
|
97
|
+
when :greater_than_or_equal_to, :gteq, :gte
|
98
|
+
resource.where(table[column].gteq(converted_value))
|
99
|
+
when :less_than_or_equal_to, :lteq, :lte
|
100
|
+
resource.where(table[column].lteq(converted_value))
|
101
|
+
when :in
|
102
|
+
resource.where(table[column].in(converted_value))
|
103
|
+
when :not
|
104
|
+
resource.where(table[column].not_eq(converted_value))
|
105
|
+
when :not_in
|
106
|
+
resource.where(table[column].not_in(converted_value).or(table[column].eq(nil)))
|
107
|
+
when :like, :ilike
|
108
|
+
resource.where(table[column].matches(converted_value))
|
109
|
+
when :ts_match
|
110
|
+
if converted_value.is_a?(Array)
|
111
|
+
resource.where(table[column].ts_query(*converted_value))
|
112
|
+
else
|
113
|
+
resource.where(table[column].ts_query(converted_value))
|
114
|
+
end
|
115
|
+
when :intersects
|
116
|
+
# geometry_value = if value.is_a?(Hash) # GeoJSON
|
117
|
+
# Arel::Nodes::NamedFunction.new('ST_GeomFromGeoJSON', [JSON.generate(value)])
|
118
|
+
# elsif # EWKB
|
119
|
+
# elsif # WKB
|
120
|
+
# elsif # EWKT
|
121
|
+
# elsif # WKT
|
122
|
+
# end
|
123
|
+
|
124
|
+
# TODO us above if to determin if SRID sent
|
125
|
+
geometry_value = if value.is_a?(Hash) || value.class.name == "ActionController::Parameters".freeze
|
126
|
+
Arel::Nodes::NamedFunction.new('ST_SetSRID', [Arel::Nodes::NamedFunction.new('ST_GeomFromGeoJSON', [Arel::Nodes.build_quoted(JSON.generate(value))]), 4326])
|
127
|
+
elsif value[0,1] == "\x00" || value[0,1] == "\x01" || value[0,4] =~ /[0-9a-fA-F]{4}/
|
128
|
+
Arel::Nodes::NamedFunction.new('ST_SetSRID', [Arel::Nodes::NamedFunction.new('ST_GeomFromEWKB', [Arel::Nodes.build_quoted(value)]), 4326])
|
129
|
+
else
|
130
|
+
Arel::Nodes::NamedFunction.new('ST_SetSRID', [Arel::Nodes::NamedFunction.new('ST_GeomFromText', [Arel::Nodes.build_quoted(value)]), 4326])
|
131
|
+
end
|
132
|
+
|
133
|
+
resource.where(Arel::Nodes::NamedFunction.new('ST_Intersects', [table[column], geometry_value]))
|
134
|
+
else
|
135
|
+
raise "Not Supported: #{key.to_sym}"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
resource
|
139
|
+
elsif value.is_a?(Array)
|
140
|
+
where(table[column].in(value.map { |x| x.send(send_method) }))
|
141
|
+
elsif value == true || value == 'true'
|
142
|
+
case method_name # columns_hash[column.to_s].try(:type)
|
143
|
+
when :filter_for_boolean then where(table[column].eq(value.try(:send, send_method)))
|
144
|
+
else where(table[column].not_eq(nil))
|
145
|
+
end
|
146
|
+
elsif value == false || value == 'false'
|
147
|
+
case method_name # columns_hash[column.to_s].try(:type)
|
148
|
+
when :filter_for_boolean then where(table[column].eq(value.try(:send, send_method)))
|
149
|
+
else where(table[column].eq(nil))
|
150
|
+
end
|
151
|
+
# when ''
|
152
|
+
# # TODO support nil. Currently rails params encode nil as empty strings,
|
153
|
+
# # and we can't tell which is desired, so do both
|
154
|
+
# where(table[column].eq(value).or(table[column].eq(nil)))
|
155
|
+
else
|
156
|
+
where(table[column].eq(value.try(:send, send_method)))
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def filter_for_jsonb(column, value, options = {})
|
162
|
+
table = options[:table_alias] ? arel_table.alias(options[:table_alias]) : arel_table
|
163
|
+
column = table[column]
|
164
|
+
|
165
|
+
drill_for_json(column, value, all)
|
166
|
+
end
|
167
|
+
alias :filter_for_json :filter_for_jsonb
|
168
|
+
|
169
|
+
def drill_for_json(column, drill, resource)
|
170
|
+
if cast = drill.delete(:cast)
|
171
|
+
column = column.cast_as(cast)
|
172
|
+
end
|
173
|
+
|
174
|
+
drill.each do |key, value|
|
175
|
+
if value.is_a?(Hash) || value.class.name == "ActionController::Parameters".freeze
|
176
|
+
resource = drill_for_json(column.key(key), value, resource)
|
177
|
+
else
|
178
|
+
resource = case key.to_sym
|
179
|
+
when :equal, :eq
|
180
|
+
resource.where(column.eq(value))
|
181
|
+
when :greater_than, :gt
|
182
|
+
resource.where(column.gt(value))
|
183
|
+
when :less_than, :lt
|
184
|
+
resource.where(column.lt(value))
|
185
|
+
when :greater_than_or_equal_to, :gteq, :gte
|
186
|
+
resource.where(column.gteq(value))
|
187
|
+
when :less_than_or_equal_to, :lteq, :lte
|
188
|
+
resource.where(column.lteq(value))
|
189
|
+
when :not
|
190
|
+
resource.where(column.not_eq(value))
|
191
|
+
when :has_key
|
192
|
+
resource.where(column.has_key(value))
|
193
|
+
when :not_in
|
194
|
+
resource.where(column.not_in(value).or(column.eq(nil)))
|
195
|
+
else
|
196
|
+
raise 'Not supported'
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
resource
|
201
|
+
end
|
202
|
+
|
203
|
+
def filter_for_array(column, value, options={})
|
204
|
+
table = options[:table_alias] ? arel_table.alias(options[:table_alias]) : arel_table
|
205
|
+
|
206
|
+
if value.is_a?(Hash) || value.class.name == "ActionController::Parameters".freeze
|
207
|
+
resource = all
|
208
|
+
value.each_pair do |key, value|
|
209
|
+
resource = case key.to_sym
|
210
|
+
when :contains
|
211
|
+
resource.where(table[column].contains(value))
|
212
|
+
when :overlaps
|
213
|
+
resource.where(table[column].overlaps(value))
|
214
|
+
when :excludes
|
215
|
+
resource.where.not(table[column].contains(value))
|
216
|
+
# when :not_overlaps
|
217
|
+
# resource.where.not(Arel::Nodes::Overlaps.new(table[column], Arel::Attributes::Array.new(Array(value))))
|
218
|
+
else
|
219
|
+
raise "Not Supported: #{key.to_sym}"
|
220
|
+
end
|
221
|
+
end
|
222
|
+
resource
|
223
|
+
else
|
224
|
+
where(table[column].contains(value))
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def filter_for_has_and_belongs_to_many(relation, value)
|
229
|
+
resource = all
|
230
|
+
|
231
|
+
options = {}
|
232
|
+
if resource.klass == relation.klass
|
233
|
+
options[:table_alias] = "#{relation.name}_#{relation.klass.table_name}"
|
234
|
+
end
|
235
|
+
|
236
|
+
if value.is_a?(Hash) || value.class.name == "ActionController::Parameters".freeze
|
237
|
+
resource = resource.joins(relation.name) #if !resource.references?(relation.name)
|
238
|
+
resource = resource.merge(relation.klass.filter(value, options))
|
239
|
+
elsif value.is_a?(Integer)
|
240
|
+
resource = resource.joins(relation.name) #if !resource.references?(relation.name)
|
241
|
+
resource = resource.merge(relation.klass.filter(value, options))
|
242
|
+
elsif value.is_a?(Array)
|
243
|
+
resource = resource.joins(relation.name) #if !resource.references?(relation.name)
|
244
|
+
resource = resource.merge(relation.klass.filter(value, options))
|
245
|
+
else
|
246
|
+
raise 'Not supported'
|
247
|
+
end
|
248
|
+
|
249
|
+
resource
|
250
|
+
end
|
251
|
+
|
252
|
+
def filter_for_has_many(relation, value)
|
253
|
+
resource = all
|
254
|
+
|
255
|
+
if value.is_a?(Hash) || value.class.name == "ActionController::Parameters".freeze
|
256
|
+
if relation.options[:through] && !relation.options[:source_type]
|
257
|
+
resource = resource.joins(relation.options[:through] => relation.source_reflection_name)
|
258
|
+
else
|
259
|
+
resource = resource.joins(relation.name) # if !resource.joined?(relation.name)
|
260
|
+
end
|
261
|
+
resource = resource.merge(relation.klass.filter(value))
|
262
|
+
elsif value.is_a?(Array) || value.is_a?(Integer)
|
263
|
+
resource = filter_for_has_many(relation, {:id => value})
|
264
|
+
elsif value == true || value == 'true'
|
265
|
+
counter_cache_column_name = relation.counter_cache_column || "#{relation.plural_name}_count"
|
266
|
+
if resource.column_names.include?(counter_cache_column_name)
|
267
|
+
resource = resource.where(resource.arel_table[counter_cache_column_name.to_sym].gt(0))
|
268
|
+
else
|
269
|
+
raise 'Not supported'
|
270
|
+
end
|
271
|
+
elsif value == false || value == 'false'
|
272
|
+
# TODO if the has_many relationship has counter_cache true can just use counter_cache_column method
|
273
|
+
counter_cache_column_name = relation.counter_cache_column || "#{relation.plural_name}_count"
|
274
|
+
if resource.column_names.include?(counter_cache_column_name)
|
275
|
+
resource = resource.where(resource.arel_table[counter_cache_column_name.to_sym].eq(0))
|
276
|
+
else
|
277
|
+
raise 'Not supported'
|
278
|
+
end
|
279
|
+
else
|
280
|
+
raise 'Not supported'
|
281
|
+
end
|
282
|
+
|
283
|
+
resource
|
284
|
+
end
|
285
|
+
alias_method :filter_for_has_one, :filter_for_has_many
|
286
|
+
|
287
|
+
def filter_for_belongs_to(relation, value)
|
288
|
+
resource = all
|
289
|
+
|
290
|
+
if value.is_a?(Array) || value.is_a?(Integer) || value.is_a?(NilClass)
|
291
|
+
resource = resource.where(:"#{relation.foreign_key}" => value)
|
292
|
+
elsif value == true || value == 'true'
|
293
|
+
resource = resource.where(resource.arel_table[:"#{relation.foreign_key}"].not_eq(nil))
|
294
|
+
elsif value == false || value == 'false'
|
295
|
+
resource = resource.where(resource.arel_table[:"#{relation.foreign_key}"].eq(nil))
|
296
|
+
elsif value.is_a?(Hash) || value.class.name == "ActionController::Parameters".freeze
|
297
|
+
if relation.polymorphic?
|
298
|
+
raise 'no :as' if !value[:as]
|
299
|
+
v = value.dup
|
300
|
+
klass = v.delete(:as).classify.constantize
|
301
|
+
t1 = resource.arel_table
|
302
|
+
t2 = klass.arel_table
|
303
|
+
resource = resource.joins(t1.join(t2).on(
|
304
|
+
t2[:id].eq(t1["#{relation.name}_id"]).and(t1["#{relation.name}_type"].eq(klass.name))
|
305
|
+
).join_sources.first)
|
306
|
+
resource = resource.merge(klass.filter(v))
|
307
|
+
else
|
308
|
+
resource = resource.joins(relation.name) # if !resource.references?(relation.name)
|
309
|
+
resource = resource.merge(relation.klass.filter(value))
|
310
|
+
end
|
311
|
+
else
|
312
|
+
if value.is_a?(String) && value =~ /\A\d+\Z/
|
313
|
+
resource = resource.where(:"#{relation.foreign_key}" => value.to_i)
|
314
|
+
else
|
315
|
+
raise 'Not supported'
|
316
|
+
end
|
317
|
+
end
|
318
|
+
resource
|
319
|
+
end
|
320
|
+
|
321
|
+
end
|
322
|
+
|
323
|
+
ActiveRecord::Base.extend(ActiveRecord::Filter)
|
metadata
CHANGED
@@ -1,17 +1,31 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord-filter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.0.0.
|
4
|
+
version: 5.0.0.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jon Bracy
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-05-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 5.0.0.rc1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 5.0.0.rc1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: arel-extensions
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
16
30
|
requirements:
|
17
31
|
- - ">="
|
@@ -25,19 +39,19 @@ dependencies:
|
|
25
39
|
- !ruby/object:Gem::Version
|
26
40
|
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
42
|
+
name: pg
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
30
44
|
requirements:
|
31
|
-
- - "
|
45
|
+
- - ">="
|
32
46
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
34
|
-
type: :
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
35
49
|
prerelease: false
|
36
50
|
version_requirements: !ruby/object:Gem::Requirement
|
37
51
|
requirements:
|
38
|
-
- - "
|
52
|
+
- - ">="
|
39
53
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
54
|
+
version: '0'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: bundler
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,20 +122,34 @@ dependencies:
|
|
108
122
|
- - ">="
|
109
123
|
- !ruby/object:Gem::Version
|
110
124
|
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: railties
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 5.0.0.rc1
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 5.0.0.rc1
|
111
139
|
- !ruby/object:Gem::Dependency
|
112
140
|
name: factory_girl_rails
|
113
141
|
requirement: !ruby/object:Gem::Requirement
|
114
142
|
requirements:
|
115
|
-
- - "
|
143
|
+
- - "~>"
|
116
144
|
- !ruby/object:Gem::Version
|
117
|
-
version:
|
145
|
+
version: 4.6.0
|
118
146
|
type: :development
|
119
147
|
prerelease: false
|
120
148
|
version_requirements: !ruby/object:Gem::Requirement
|
121
149
|
requirements:
|
122
|
-
- - "
|
150
|
+
- - "~>"
|
123
151
|
- !ruby/object:Gem::Version
|
124
|
-
version:
|
152
|
+
version: 4.6.0
|
125
153
|
- !ruby/object:Gem::Dependency
|
126
154
|
name: faker
|
127
155
|
requirement: !ruby/object:Gem::Requirement
|
@@ -146,13 +174,6 @@ extra_rdoc_files:
|
|
146
174
|
- README.md
|
147
175
|
files:
|
148
176
|
- README.md
|
149
|
-
- ext/active_record/base.rb
|
150
|
-
- ext/active_record/query_methods.rb
|
151
|
-
- ext/active_record/unkown_filter_error.rb
|
152
|
-
- ext/arel/attributes/array.rb
|
153
|
-
- ext/arel/nodes/contains.rb
|
154
|
-
- ext/arel/nodes/overlaps.rb
|
155
|
-
- ext/arel/visitors/postgresql.rb
|
156
177
|
- lib/active_record/filter.rb
|
157
178
|
- lib/active_record/filter/version.rb
|
158
179
|
homepage: https://github.com/malomalo/activerecord-filter
|
data/ext/active_record/base.rb
DELETED
@@ -1,268 +0,0 @@
|
|
1
|
-
class ActiveRecord::Base
|
2
|
-
class << self
|
3
|
-
|
4
|
-
def inherited_with_filter(subclass)
|
5
|
-
inherited_without_filter(subclass)
|
6
|
-
subclass.instance_variable_set('@filters', HashWithIndifferentAccess.new)
|
7
|
-
end
|
8
|
-
alias_method_chain :inherited, :filter
|
9
|
-
|
10
|
-
def filter_on(name, lambda)
|
11
|
-
@filters[name] = lambda
|
12
|
-
end
|
13
|
-
|
14
|
-
def filter(filters, options={})
|
15
|
-
resource = all
|
16
|
-
return resource unless filters
|
17
|
-
|
18
|
-
if filters.is_a?(Hash) || filters.is_a?(ActionController::Parameters)
|
19
|
-
filters.each do |key, value|
|
20
|
-
if @filters[key]
|
21
|
-
#TODO add test for this... not sure how rails does this lambda call,
|
22
|
-
# do they run it in a context for merge?
|
23
|
-
resource = resource.merge( @filters[key].call(value) )
|
24
|
-
else
|
25
|
-
resource = resource.filter_for(key, value, options)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
elsif filters.is_a?(Array) || filters.is_a?(Integer)
|
29
|
-
resource = resource.filter_for(:id, filters, options)
|
30
|
-
end
|
31
|
-
|
32
|
-
resource
|
33
|
-
end
|
34
|
-
|
35
|
-
def filter_for(key, value, options={})
|
36
|
-
column = columns_hash[key.to_s]
|
37
|
-
if column && column.array
|
38
|
-
all.filter_for_array(key, value, options)
|
39
|
-
elsif column
|
40
|
-
all.send("filter_for_#{column.type}", key, value, options)
|
41
|
-
else
|
42
|
-
if relation = reflect_on_association(key)
|
43
|
-
self.send("filter_for_#{relation.macro}", relation, value)
|
44
|
-
else
|
45
|
-
raise ActiveRecord::UnkownFilterError.new(self, key)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
{
|
51
|
-
filter_for_geometry: :itself,
|
52
|
-
filter_for_datetime: :to_datetime,
|
53
|
-
filter_for_integer: :to_i,
|
54
|
-
filter_for_text: :itself,
|
55
|
-
filter_for_boolean: :itself,
|
56
|
-
filter_for_string: :itself,
|
57
|
-
filter_for_uuid: :itself,
|
58
|
-
filter_for_decimal: :to_f,
|
59
|
-
filter_for_float: :to_f
|
60
|
-
}.each_pair do |method_name, send_method|
|
61
|
-
define_method(method_name) do |column, value, options={}|
|
62
|
-
table = options[:table_alias] ? arel_table.alias(options[:table_alias]) : arel_table
|
63
|
-
|
64
|
-
case value
|
65
|
-
when Hash, ActionController::Parameters
|
66
|
-
resource = all
|
67
|
-
value.each_pair do |key, value|
|
68
|
-
converted_value = value.try(:send, send_method)
|
69
|
-
resource = case key.to_sym
|
70
|
-
when :greater_than, :gt
|
71
|
-
resource.where(table[column].gt(converted_value))
|
72
|
-
when :less_than, :lt
|
73
|
-
resource.where(table[column].lt(converted_value))
|
74
|
-
when :greater_than_or_equal_to, :gteq, :gte
|
75
|
-
resource.where(table[column].gteq(converted_value))
|
76
|
-
when :less_than_or_equal_to, :lteq, :lte
|
77
|
-
resource.where(table[column].lteq(converted_value))
|
78
|
-
when :not
|
79
|
-
resource.where(table[column].not_eq(converted_value))
|
80
|
-
when :not_in
|
81
|
-
resource.where(table[column].not_in(value).or(table[column].eq(nil)))
|
82
|
-
when :intersects
|
83
|
-
# geometry_value = if value.is_a?(Hash) # GeoJSON
|
84
|
-
# Arel::Nodes::NamedFunction.new('ST_GeomFromGeoJSON', [JSON.generate(value)])
|
85
|
-
# elsif # EWKB
|
86
|
-
# elsif # WKB
|
87
|
-
# elsif # EWKT
|
88
|
-
# elsif # WKT
|
89
|
-
# end
|
90
|
-
|
91
|
-
# TODO us above if to determin if SRID sent
|
92
|
-
geometry_value = if value.is_a?(Hash) || value.is_a?(ActionController::Parameters)
|
93
|
-
Arel::Nodes::NamedFunction.new('ST_SetSRID', [Arel::Nodes::NamedFunction.new('ST_GeomFromGeoJSON', [Arel::Nodes.build_quoted(JSON.generate(value))]), 4326])
|
94
|
-
elsif value[0,1] == "\x00" || value[0,1] == "\x01" || value[0,4] =~ /[0-9a-fA-F]{4}/
|
95
|
-
Arel::Nodes::NamedFunction.new('ST_SetSRID', [Arel::Nodes::NamedFunction.new('ST_GeomFromEWKB', [Arel::Nodes.build_quoted(value)]), 4326])
|
96
|
-
else
|
97
|
-
Arel::Nodes::NamedFunction.new('ST_SetSRID', [Arel::Nodes::NamedFunction.new('ST_GeomFromText', [Arel::Nodes.build_quoted(value)]), 4326])
|
98
|
-
end
|
99
|
-
|
100
|
-
resource.where(Arel::Nodes::NamedFunction.new('ST_Intersects', [table[column], geometry_value]))
|
101
|
-
else
|
102
|
-
raise "Not Supported: #{key.to_sym}"
|
103
|
-
end
|
104
|
-
end
|
105
|
-
resource
|
106
|
-
when Array
|
107
|
-
where(table[column].in(value.map { |x| x.send(send_method) }))
|
108
|
-
when true, 'true'
|
109
|
-
case method_name # columns_hash[column.to_s].try(:type)
|
110
|
-
when :filter_for_boolean then where(table[column].eq(value.try(:send, send_method)))
|
111
|
-
else where(table[column].not_eq(nil))
|
112
|
-
end
|
113
|
-
when false, 'false'
|
114
|
-
case method_name # columns_hash[column.to_s].try(:type)
|
115
|
-
when :filter_for_boolean then where(table[column].eq(value.try(:send, send_method)))
|
116
|
-
else where(table[column].eq(nil))
|
117
|
-
end
|
118
|
-
# when ''
|
119
|
-
# # TODO support nil. Currently rails params encode nil as empty strings,
|
120
|
-
# # and we can't tell which is desired, so do both
|
121
|
-
# where(table[column].eq(value).or(table[column].eq(nil)))
|
122
|
-
else
|
123
|
-
where(table[column].eq(value.try(:send, send_method)))
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
def filter_for_jsonb(column, value, options = {})
|
129
|
-
table = options[:table_alias] ? arel_table.alias(options[:table_alias]) : arel_table
|
130
|
-
|
131
|
-
case value
|
132
|
-
when true, 'true'
|
133
|
-
where(table[column].not_eq(nil))
|
134
|
-
when false, 'false'
|
135
|
-
where(table[column].eq(nil))
|
136
|
-
when nil
|
137
|
-
where(table[column].eq(nil))
|
138
|
-
else
|
139
|
-
raise 'Not supported'
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
def filter_for_array(column, value, options={})
|
144
|
-
table = options[:table_alias] ? arel_table.alias(options[:table_alias]) : arel_table
|
145
|
-
|
146
|
-
case value
|
147
|
-
when Hash, ActionController::Parameters
|
148
|
-
resource = all
|
149
|
-
value.each_pair do |key, value|
|
150
|
-
resource = case key.to_sym
|
151
|
-
when :contains
|
152
|
-
resource.where(Arel::Nodes::Contains.new(table[column], Arel::Attributes::Array.new(Array(value))))
|
153
|
-
when :overlaps
|
154
|
-
resource.where(Arel::Nodes::Overlaps.new(table[column], Arel::Attributes::Array.new(Array(value))))
|
155
|
-
when :not_overlaps
|
156
|
-
resource.where.not(Arel::Nodes::Overlaps.new(table[column], Arel::Attributes::Array.new(Array(value))))
|
157
|
-
when :not_contains
|
158
|
-
resource.where.not(Arel::Nodes::Contains.new(table[column], Arel::Attributes::Array.new(Array(value))))
|
159
|
-
else
|
160
|
-
raise "Not Supported: #{key.to_sym}"
|
161
|
-
end
|
162
|
-
end
|
163
|
-
resource
|
164
|
-
when Array
|
165
|
-
where(Arel::Nodes::Contains.new(table[column], Arel::Attributes::Array.new(value)))
|
166
|
-
else
|
167
|
-
where(Arel::Nodes::Contains.new(table[column], Arel::Attributes::Array.new([value])))
|
168
|
-
end
|
169
|
-
end
|
170
|
-
|
171
|
-
def filter_for_has_and_belongs_to_many(relation, value)
|
172
|
-
resource = all
|
173
|
-
|
174
|
-
options = {}
|
175
|
-
if resource.klass == relation.klass
|
176
|
-
options[:table_alias] = "#{relation.name}_#{relation.klass.table_name}"
|
177
|
-
end
|
178
|
-
|
179
|
-
case value
|
180
|
-
when Hash, ActionController::Parameters
|
181
|
-
resource = resource.joins(relation.name) if !resource.references?(relation.name)
|
182
|
-
resource = resource.merge(relation.klass.filter(value, options))
|
183
|
-
when Integer
|
184
|
-
resource = resource.joins(relation.name) if !resource.references?(relation.name)
|
185
|
-
resource = resource.merge(relation.klass.filter(value, options))
|
186
|
-
when Array
|
187
|
-
resource = resource.joins(relation.name) if !resource.references?(relation.name)
|
188
|
-
resource = resource.merge(relation.klass.filter(value, options))
|
189
|
-
else
|
190
|
-
raise 'Not supported'
|
191
|
-
end
|
192
|
-
|
193
|
-
resource
|
194
|
-
end
|
195
|
-
|
196
|
-
def filter_for_has_many(relation, value)
|
197
|
-
resource = all
|
198
|
-
|
199
|
-
case value
|
200
|
-
when Hash, ActionController::Parameters
|
201
|
-
if relation.options[:through]
|
202
|
-
resource = resource.joins(relation.options[:through] => relation.source_reflection_name)
|
203
|
-
else
|
204
|
-
resource = resource.joins(relation.name) # if !resource.joined?(relation.name)
|
205
|
-
end
|
206
|
-
resource = resource.merge(relation.klass.filter(value))
|
207
|
-
when Array, Integer
|
208
|
-
resource = filter_for_has_many(relation, {:id => value})
|
209
|
-
when true, 'true'
|
210
|
-
counter_cache_column_name = relation.counter_cache_column || "#{relation.plural_name}_count"
|
211
|
-
if resource.column_names.include?(counter_cache_column_name)
|
212
|
-
resource = resource.where(resource.arel_table[counter_cache_column_name.to_sym].gt(0))
|
213
|
-
else
|
214
|
-
raise 'Not supported'
|
215
|
-
end
|
216
|
-
when false, 'false'
|
217
|
-
# TODO if the has_many relationship has counter_cache true can just use counter_cache_column method
|
218
|
-
counter_cache_column_name = relation.counter_cache_column || "#{relation.plural_name}_count"
|
219
|
-
if resource.column_names.include?(counter_cache_column_name)
|
220
|
-
resource = resource.where(resource.arel_table[counter_cache_column_name.to_sym].eq(0))
|
221
|
-
else
|
222
|
-
raise 'Not supported'
|
223
|
-
end
|
224
|
-
else
|
225
|
-
raise 'Not supported'
|
226
|
-
end
|
227
|
-
|
228
|
-
resource
|
229
|
-
end
|
230
|
-
alias_method :filter_for_has_one, :filter_for_has_many
|
231
|
-
|
232
|
-
def filter_for_belongs_to(relation, value)
|
233
|
-
resource = all
|
234
|
-
|
235
|
-
case value
|
236
|
-
when Array, Integer, NilClass
|
237
|
-
resource = resource.where(:"#{relation.foreign_key}" => value)
|
238
|
-
when true, 'true'
|
239
|
-
resource = resource.where(resource.arel_table[:"#{relation.foreign_key}"].not_eq(nil))
|
240
|
-
when false, 'false'
|
241
|
-
resource = resource.where(resource.arel_table[:"#{relation.foreign_key}"].eq(nil))
|
242
|
-
when Hash, ActionController::Parameters
|
243
|
-
if relation.polymorphic?
|
244
|
-
raise 'no :as' if !value[:as]
|
245
|
-
v = value.dup
|
246
|
-
klass = v.delete(:as).classify.constantize
|
247
|
-
t1 = resource.arel_table
|
248
|
-
t2 = klass.arel_table
|
249
|
-
resource = resource.joins(t1.join(t2).on(
|
250
|
-
t2[:id].eq(t1["#{relation.name}_id"]).and(t1["#{relation.name}_type"].eq(klass.name))
|
251
|
-
).join_sources.first)
|
252
|
-
resource = resource.merge(klass.filter(v))
|
253
|
-
else
|
254
|
-
resource = resource.joins(relation.name) # if !resource.references?(relation.name)
|
255
|
-
resource = resource.merge(relation.klass.filter(value))
|
256
|
-
end
|
257
|
-
else
|
258
|
-
if value.is_a?(String) && value =~ /\A\d+\Z/
|
259
|
-
resource = resource.where(:"#{relation.foreign_key}" => value.to_i)
|
260
|
-
else
|
261
|
-
raise 'Not supported'
|
262
|
-
end
|
263
|
-
end
|
264
|
-
resource
|
265
|
-
end
|
266
|
-
|
267
|
-
end
|
268
|
-
end
|
@@ -1,27 +0,0 @@
|
|
1
|
-
# module ActiveRecord::QueryMethods
|
2
|
-
#
|
3
|
-
# # # TODO: testme and rename to joins?
|
4
|
-
# # def joined?(assoc)
|
5
|
-
# # joined_assocs = joins_values.map{ |i| i.is_a?(Hash) ? i.keys : i.to_sym }.flatten
|
6
|
-
# # joined_assocs.include?(assoc)
|
7
|
-
# # end
|
8
|
-
# #
|
9
|
-
# # # TODO: testme and rename to includes?
|
10
|
-
# # def included?(assoc)
|
11
|
-
# # included_assocs = includes_values.map{ |i| i.is_a?(Hash) ? i.keys : i.to_sym }.flatten
|
12
|
-
# # included_assocs.include?(assoc)
|
13
|
-
# # end
|
14
|
-
#
|
15
|
-
# # TODO: testme and rename to
|
16
|
-
# def references?(assoc)
|
17
|
-
# references_assocs = references_values.map{ |i| i.is_a?(Hash) ? i.keys : i.to_sym }.flatten
|
18
|
-
# references_assocs.include?(assoc)
|
19
|
-
# end
|
20
|
-
#
|
21
|
-
# end
|
22
|
-
|
23
|
-
module ActiveRecord::Querying
|
24
|
-
# delegate :joined?, :to => :all
|
25
|
-
# delegate :included?, :to => :all
|
26
|
-
delegate :references?, :to => :all
|
27
|
-
end
|
data/ext/arel/nodes/contains.rb
DELETED
data/ext/arel/nodes/overlaps.rb
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
module Arel
|
2
|
-
module Visitors
|
3
|
-
class PostgreSQL
|
4
|
-
private
|
5
|
-
|
6
|
-
def visit_Arel_Nodes_Contains o, collector
|
7
|
-
visit o.left, collector
|
8
|
-
collector << ' @> '
|
9
|
-
visit o.right, collector
|
10
|
-
end
|
11
|
-
|
12
|
-
def visit_Arel_Nodes_Overlaps o, collector
|
13
|
-
visit o.left, collector
|
14
|
-
collector << ' && '
|
15
|
-
visit o.right, collector
|
16
|
-
end
|
17
|
-
|
18
|
-
def visit_Arel_Attributes_Array o, collector
|
19
|
-
type = if !o.relation[0]
|
20
|
-
ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.new(nil)
|
21
|
-
else
|
22
|
-
# TODO: temp fix, need to figure out how to look up ruby type ->
|
23
|
-
# postgres type or use the column we're quering on...
|
24
|
-
dt = if o.relation[0].class == Fixnum
|
25
|
-
dt = "Integer"
|
26
|
-
else
|
27
|
-
o.relation[0].class
|
28
|
-
end
|
29
|
-
ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.new("ActiveRecord::Type::#{dt}".constantize.new)
|
30
|
-
end
|
31
|
-
|
32
|
-
collector << quote(type.serialize(o.relation))
|
33
|
-
end
|
34
|
-
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|