activerecord-filter 5.0.0.6 → 5.0.0.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7ebd77f99aa6b3919c780144570afccb9020e970
4
- data.tar.gz: 9bb379f8e4148ca004c7b5823efcb56b95b2c952
3
+ metadata.gz: a17e5665d5d8476c382e7dca7ab1bcbb600da917
4
+ data.tar.gz: ac54d3aba9f451bd468a4e55943423307fc1e5cc
5
5
  SHA512:
6
- metadata.gz: cafc7b16939a790e9a134e0a7266f14424ab1e9f5d18543c7edf1f7437254592b48c68bd0f85ccb87aee55eb03ad27ea4b24f252223b6ee617747beed216430d
7
- data.tar.gz: f128d2aee5a42cdbb6c4f6dea4f772aaf30f7bfae9f407b0d60a950bf56fdb347389049e2adc7858734d919b0fd366bc00b2ee2e2d354c9c92f080867564940a
6
+ metadata.gz: 4c5a83ec75a1bd656fcdf8c4a4505f3013cf155e03b0b3d638c0807ecc520a656b362ba9c8f75fd135ece248e030b984a95c24784b7947fab46b8f78b68688df
7
+ data.tar.gz: 7ec0169479bfb8edf3b9767f4cd5daed9ce607ed0b3472a15cfceb2f6a59654654cb1a822a7502c0ecbcbac04c65854c8473b8437f27eaebe0be79d39dbc19f6
@@ -7,321 +7,405 @@ end
7
7
 
8
8
  module ActiveRecord::Filter
9
9
 
10
+ delegate :filter, :filter_for, to: :all
11
+
10
12
  def inherited(subclass)
11
13
  super
12
14
  subclass.instance_variable_set('@filters', HashWithIndifferentAccess.new)
13
15
  end
14
16
 
15
- def filter_on(name, lambda)
16
- @filters[name] = lambda
17
+ def filters
18
+ @filters
17
19
  end
18
20
 
19
- def filter(filters, options={})
20
- resource = all
21
- return resource unless filters
22
-
23
- case filters
24
- when Hash, ActionController::Parameters
25
- filters.each do |key, value|
26
- if @filters[key]
27
- #TODO add test for this... not sure how rails does this lambda call,
28
- # do they run it in a context for merge?
29
- resource = resource.merge( @filters[key].call(value) )
30
- else
31
- resource = resource.filter_for(key, value, options)
32
- end
33
- end
34
- when Integer, Array
35
- resource = resource.filter_for(:id, filters, options)
36
- end
37
-
38
- resource
21
+ def filter_on(name, dependent_joins=nil, &block)
22
+ @filters[name.to_s] = {
23
+ joins: dependent_joins,
24
+ block: block
25
+ }
39
26
  end
27
+
28
+ end
40
29
 
41
- def filter_for(key, value, options={})
42
- column = columns_hash[key.to_s]
30
+ module ActiveRecord
31
+ class PredicateBuilder # :nodoc:
43
32
 
44
- if column && column.array
45
- all.filter_for_array(key, value, options)
46
- elsif column
47
- all.send("filter_for_#{column.type}", key, value, options)
48
- elsif relation = reflect_on_association(key)
49
- self.send("filter_for_#{relation.macro}", relation, value)
50
- else
51
- raise ActiveRecord::UnkownFilterError.new("Unkown filter \"#{key}\" for #{self}.")
33
+ def self.filter_joins(klass, filters)
34
+ build_filter_joins(klass, filters).inject(&:+)
52
35
  end
53
- end
54
-
55
- {
56
- filter_for_geometry: :itself,
57
- filter_for_datetime: :to_datetime,
58
- filter_for_integer: :to_i,
59
- filter_for_fixnum: :to_i,
60
- filter_for_text: :itself,
61
- filter_for_boolean: :itself,
62
- filter_for_string: :itself,
63
- filter_for_uuid: :itself,
64
- filter_for_decimal: :to_f,
65
- filter_for_float: :to_f
66
- }.each_pair do |method_name, send_method|
67
- define_method(method_name) do |column, value, options={}|
68
- table = options[:table_alias] ? arel_table.alias(options[:table_alias]) : arel_table
69
-
70
- if value.is_a?(Hash) || value.class.name == "ActionController::Parameters".freeze
71
- resource = all
72
- value.each_pair do |key, value|
73
- converted_value = if value.is_a?(Array)
74
- value.map { |x| x.try(:send, send_method) }
75
- else
76
- value.try(:send, send_method)
36
+
37
+ def self.build_filter_joins(klass, filters, relations=[], custom=[])
38
+ if filters.is_a?(Array)
39
+ filters.each { |f| build_filter_joins(klass, f, relations, custom) }.compact
40
+ elsif filters.is_a?(Hash)
41
+ filters.each do |key, value|
42
+ if klass.filters.has_key?(key.to_sym)
43
+ js = klass.filters.dig(key.to_sym, :joins)
44
+ if js.is_a?(Array)
45
+ js.each do |j|
46
+ if j.is_a?(Hash)
47
+ relations << j
48
+ else
49
+ custom << j
50
+ end
51
+ end
52
+ elsif js
53
+ relations << js
54
+ end
55
+ elsif reflection = klass._reflections[key.to_s]
56
+ if value.is_a?(Hash)
57
+ relations << {key => build_filter_joins(reflection.klass, value)}
58
+ elsif value != true && value != false && value != 'true' && value != 'false' && !value.nil?
59
+ relations << key
60
+ end
61
+ elsif key.to_s.ends_with?('_ids') && reflection = klass._reflections[key.to_s.gsub(/_ids$/, 's')]
62
+ relations << reflection.name
63
+ elsif reflection = klass.reflect_on_all_associations(:has_and_belongs_to_many).find {|r| r.join_table == key.to_s && value.keys.first.to_s == r.association_foreign_key.to_s }
64
+ reflection = klass._reflections[klass._reflections[reflection.name.to_s].delegate_reflection.options[:through].to_s]
65
+ relations << {reflection.name => build_filter_joins(reflection.klass, value)}
77
66
  end
67
+ end
68
+ end
69
+ [relations, custom]
70
+ end
71
+
72
+ def build_from_filter_hash(attributes, join_dependency)
73
+ if attributes.is_a?(Array)
74
+ node = build_from_filter_hash(attributes.shift, join_dependency)
78
75
 
79
- resource = case key.to_sym
80
- when :equal_to, :eq
81
- resource.where(table[column].eq(converted_value))
82
- when :greater_than, :gt
83
- resource.where(table[column].gt(converted_value))
84
- when :less_than, :lt
85
- resource.where(table[column].lt(converted_value))
86
- when :greater_than_or_equal_to, :gteq, :gte
87
- resource.where(table[column].gteq(converted_value))
88
- when :less_than_or_equal_to, :lteq, :lte
89
- resource.where(table[column].lteq(converted_value))
90
- when :in
91
- resource.where(table[column].in(converted_value))
92
- when :not
93
- resource.where(table[column].not_eq(converted_value))
94
- when :not_in
95
- resource.where(table[column].not_in(converted_value))
96
- when :like, :ilike
97
- resource.where(table[column].matches(converted_value))
98
- when :ts_match
99
- if converted_value.is_a?(Array)
100
- resource.where(table[column].ts_query(*converted_value))
101
- else
102
- resource.where(table[column].ts_query(converted_value))
103
- end
104
- when :intersects
105
- # geometry_value = if value.is_a?(Hash) # GeoJSON
106
- # Arel::Nodes::NamedFunction.new('ST_GeomFromGeoJSON', [JSON.generate(value)])
107
- # elsif # EWKB
108
- # elsif # WKB
109
- # elsif # EWKT
110
- # elsif # WKT
111
- # end
112
-
113
- # TODO us above if to determin if SRID sent
114
- geometry_value = if value.is_a?(Hash) || value.class.name == "ActionController::Parameters".freeze
115
- Arel::Nodes::NamedFunction.new('ST_SetSRID', [Arel::Nodes::NamedFunction.new('ST_GeomFromGeoJSON', [Arel::Nodes.build_quoted(JSON.generate(value))]), 4326])
116
- elsif value[0,1] == "\x00" || value[0,1] == "\x01" || value[0,4] =~ /[0-9a-fA-F]{4}/
117
- Arel::Nodes::NamedFunction.new('ST_SetSRID', [Arel::Nodes::NamedFunction.new('ST_GeomFromEWKB', [Arel::Nodes.build_quoted(value)]), 4326])
76
+ n = attributes.shift(2)
77
+ while !n.empty?
78
+ n[1] = build_from_filter_hash(n[1], join_dependency)
79
+ if n[0] == 'AND'
80
+ if node.is_a?(Arel::Nodes::And)
81
+ node.children.push(n[1])
118
82
  else
119
- Arel::Nodes::NamedFunction.new('ST_SetSRID', [Arel::Nodes::NamedFunction.new('ST_GeomFromText', [Arel::Nodes.build_quoted(value)]), 4326])
83
+ node = node.and(n[1])
120
84
  end
121
-
122
- resource.where(Arel::Nodes::NamedFunction.new('ST_Intersects', [table[column], geometry_value]))
85
+ elsif n[0] == 'OR'
86
+ node = Arel::Nodes::Grouping.new(node).or(Arel::Nodes::Grouping.new(n[1]))
123
87
  else
124
- raise "Not Supported: #{key.to_sym}"
88
+ raise 'lll'
125
89
  end
90
+ n = attributes.shift(2)
126
91
  end
127
- resource
128
- elsif value.is_a?(Array)
129
- where(table[column].in(value.map { |x| x.send(send_method) }))
130
- elsif value == true || value == 'true'
131
- case method_name # columns_hash[column.to_s].try(:type)
132
- when :filter_for_boolean then where(table[column].eq(value.try(:send, send_method)))
133
- else where(table[column].not_eq(nil))
92
+
93
+ node
94
+ elsif attributes.is_a?(Hash)
95
+ expand_from_filter_hash(attributes, join_dependency)
96
+ else
97
+ expand_from_filter_hash({id: attributes}, join_dependency)
98
+ end
99
+ end
100
+
101
+ def expand_from_filter_hash(attributes, join_dependency)
102
+ klass = table.send(:klass)
103
+
104
+ children = attributes.flat_map do |key, value|
105
+ if custom_filter = klass.filters[key]
106
+ self.instance_exec(klass, table, key, value, join_dependency, &custom_filter[:block])
107
+ elsif column = klass.columns_hash[key.to_s] || klass.columns_hash[key.to_s.split('.').first]
108
+ expand_filter_for_column(key, column, value)
109
+ elsif relation = klass.reflect_on_association(key)
110
+ expand_filter_for_relationship(relation, value, join_dependency)
111
+ elsif key.to_s.ends_with?('_ids') && relation = klass.reflect_on_association(key.to_s.gsub(/_ids$/, 's'))
112
+ expand_filter_for_relationship(relation, {id: value}, join_dependency)
113
+ elsif relation = klass.reflect_on_all_associations(:has_and_belongs_to_many).find {|r| r.join_table == key.to_s && value.keys.first.to_s == r.association_foreign_key.to_s }
114
+ expand_filter_for_join_table(relation, value, join_dependency)
115
+ else
116
+ raise ActiveRecord::UnkownFilterError.new("Unkown filter \"#{key}\" for #{klass}.")
134
117
  end
135
- elsif value == false || value == 'false'
136
- case method_name # columns_hash[column.to_s].try(:type)
137
- when :filter_for_boolean then where(table[column].eq(value.try(:send, send_method)))
138
- else where(table[column].eq(nil))
118
+ end
119
+
120
+ children.compact!
121
+ if children.size > 1
122
+ Arel::Nodes::And.new(children)
123
+ else
124
+ children.first
125
+ end
126
+ end
127
+
128
+ def convert_filter_value(column, value)
129
+ caster = table.send(:klass).attribute_types[column.name]
130
+ if value.is_a?(Array) && !column.array
131
+ value.map {|v| caster.cast(v) }
132
+ else
133
+ caster.cast(value)
134
+ end
135
+ end
136
+
137
+ def expand_filter_for_column(key, column, value)
138
+ attribute = table.arel_attribute(column.name)
139
+ if column.type == :json || column.type == :jsonb
140
+ names = key.to_s.split('.')
141
+ names.shift
142
+ attribute = attribute.dig(names)
143
+ end
144
+
145
+ if value.is_a?(Hash)
146
+ nodes = value.map do |subkey, subvalue|
147
+ expand_filter_for_arel_attribute(column, attribute, subkey, subvalue)
139
148
  end
149
+ nodes.inject { |c, n| c.nil? ? n : c.and(n) }
140
150
  elsif value == nil
141
- where(table[column].eq(nil))
142
- # when ''
143
- # # TODO support nil. Currently rails params encode nil as empty strings,
144
- # # and we can't tell which is desired, so do both
145
- # where(table[column].eq(value).or(table[column].eq(nil)))
146
- elsif value.respond_to?(send_method)
147
- where(table[column].eq(value.try(:send, send_method)))
151
+ attribute.eq(nil)
152
+ elsif value == true || value == 'true'
153
+ column.type == :boolean ? attribute.eq(true) : attribute.not_eq(nil)
154
+ elsif value == false || value == 'false'
155
+ column.type == :boolean ? attribute.eq(false) : attribute.eq(nil)
156
+ elsif value.is_a?(Array) && !column.array
157
+ attribute.in(convert_filter_value(column, value))
158
+ elsif column.type != :json && column.type != :jsonb
159
+ converted_value = convert_filter_value(column, column.array ? Array(value) : value)
160
+ attribute.eq(converted_value)
148
161
  else
149
162
  raise ActiveRecord::UnkownFilterError.new("Unkown type for #{column}. (type #{value.class})")
150
163
  end
164
+
151
165
  end
152
- end
153
166
 
154
- def filter_for_jsonb(column, value, options = {})
155
- table = options[:table_alias] ? arel_table.alias(options[:table_alias]) : arel_table
156
- column = table[column]
157
-
158
- drill_for_json(column, value, all, 'jsonb')
159
- end
160
-
161
- def filter_for_json(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, 'json')
166
- end
167
-
168
- def drill_for_json(column, drill, resource, cast)
169
- drill.each do |key, value|
170
- if value.is_a?(Hash) || value.class.name == "ActionController::Parameters".freeze
171
- resource = drill_for_json(column.key(key), value, resource, cast)
172
- else
173
- value = Arel::Attributes::Cast.new(Arel::Nodes::Quoted.new(value.to_s), cast)
174
-
175
- resource = case key.to_sym
176
- when :equal, :eq
177
- resource.where(column.eq(value))
178
- when :greater_than, :gt
179
- resource.where(column.gt(value))
180
- when :less_than, :lt
181
- resource.where(column.lt(value))
182
- when :greater_than_or_equal_to, :gteq, :gte
183
- resource.where(column.gteq(value))
184
- when :less_than_or_equal_to, :lteq, :lte
185
- resource.where(column.lteq(value))
186
- when :not
187
- resource.where(column.not_eq(value))
188
- when :has_key
189
- resource.where(column.has_key(value))
190
- when :not_in
191
- resource.where(column.not_in(value))
167
+ def expand_filter_for_arel_attribute(column, attribute, key, value)
168
+ case key.to_sym
169
+ when :contains
170
+ attribute.contains(column.array ? Array(convert_filter_value(column, value)) : convert_filter_value(column, value))
171
+ when :contained_by
172
+ attribute.contained_by(column.array ? Array(convert_filter_value(column, value)) : convert_filter_value(column, value))
173
+ when :equal_to, :eq
174
+ attribute.eq(convert_filter_value(column, value))
175
+ when :excludes
176
+ attribute.excludes(convert_filter_value(column, Array(value)))
177
+ when :greater_than, :gt
178
+ attribute.gt(convert_filter_value(column, value))
179
+ when :greater_than_or_equal_to, :gteq, :gte
180
+ attribute.gteq(convert_filter_value(column, value))
181
+ when :has_key
182
+ attribute.has_key(convert_filter_value(column, value))
183
+ when :in
184
+ attribute.in(convert_filter_value(column, value))
185
+ when :intersects
186
+ # geometry_value = if value.is_a?(Hash) # GeoJSON
187
+ # Arel::Nodes::NamedFunction.new('ST_GeomFromGeoJSON', [JSON.generate(value)])
188
+ # elsif # EWKB
189
+ # elsif # WKB
190
+ # elsif # EWKT
191
+ # elsif # WKT
192
+ # end
193
+
194
+ # TODO us above if to determin if SRID sent
195
+ geometry_value = if subvalue.is_a?(Hash)
196
+ Arel::Nodes::NamedFunction.new('ST_SetSRID', [Arel::Nodes::NamedFunction.new('ST_GeomFromGeoJSON', [Arel::Nodes.build_quoted(JSON.generate(subvalue))]), 4326])
197
+ elsif subvalue[0,1] == "\x00" || subvalue[0,1] == "\x01" || subvalue[0,4] =~ /[0-9a-fA-F]{4}/
198
+ Arel::Nodes::NamedFunction.new('ST_SetSRID', [Arel::Nodes::NamedFunction.new('ST_GeomFromEWKB', [Arel::Nodes.build_quoted(subvalue)]), 4326])
192
199
  else
193
- raise 'Not supported'
200
+ Arel::Nodes::NamedFunction.new('ST_SetSRID', [Arel::Nodes::NamedFunction.new('ST_GeomFromText', [Arel::Nodes.build_quoted(subvalue)]), 4326])
194
201
  end
202
+
203
+ Arel::Nodes::NamedFunction.new('ST_Intersects', [attribute, geometry_value])
204
+ when :less_than, :lt
205
+ attribute.lt(convert_filter_value(column, value))
206
+ when :less_than_or_equal_to, :lteq, :lte
207
+ attribute.lteq(convert_filter_value(column, value))
208
+ when :like, :ilike
209
+ attribute.matches(convert_filter_value(column, value))
210
+ when :not, :not_equal, :neq
211
+ attribute.not_eq(convert_filter_value(column, value))
212
+ when :not_in
213
+ attribute.not_in(convert_filter_value(column, value))
214
+ when :overlaps
215
+ attribute.overlaps(convert_filter_value(column, value))
216
+ when :ts_match
217
+ if value.is_a?(Array)
218
+ attribute.ts_query(*convert_filter_value(column, value))
219
+ else
220
+ attribute.ts_query(convert_filter_value(column, value))
221
+ end
222
+ when :within
223
+ attribute.within(value)
224
+ else
225
+ raise "Not Supported: #{key.to_sym}"
195
226
  end
196
227
  end
197
- resource
228
+
229
+ def expand_filter_for_relationship(relation, value, join_dependency)
230
+ case relation.macro
231
+ when :has_many
232
+ if value == true || value == 'true'
233
+ counter_cache_column_name = relation.counter_cache_column || "#{relation.plural_name}_count"
234
+ if relation.active_record.column_names.include?(counter_cache_column_name.to_s)
235
+ return table.arel_attribute(counter_cache_column_name.to_sym).gt(0)
236
+ else
237
+ raise "Not Supported: #{relation.name}"
238
+ end
239
+ elsif value == false || value == 'false'
240
+ counter_cache_column_name = relation.counter_cache_column || "#{relation.plural_name}_count"
241
+ if relation.active_record.column_names.include?(counter_cache_column_name.to_s)
242
+ return table.arel_attribute(counter_cache_column_name.to_sym).eq(0)
243
+ else
244
+ raise "Not Supported: #{relation.name}"
245
+ end
246
+ end
247
+ when :belongs_to
248
+ if value == true || value == 'true'
249
+ return table.arel_attribute(relation.foreign_key).not_eq(nil)
250
+ elsif value == false || value == 'false' || value.nil?
251
+ return table.arel_attribute(relation.foreign_key).eq(nil)
252
+ end
253
+ end
254
+
255
+
256
+
257
+ builder = associated_predicate_builder(relation.name.to_sym)
258
+
259
+ if join_dependency
260
+ join_dependency = join_dependency.children.find { |c| c.reflection.name == relation.name }
261
+ builder.table.instance_variable_set(:@arel_table, join_dependency.tables.first)
262
+ end
263
+
264
+ builder.build_from_filter_hash(value, join_dependency)
265
+ end
266
+
267
+ def expand_filter_for_join_table(relation, value, join_dependency)
268
+ relation = relation.active_record._reflections[relation.active_record._reflections[relation.name.to_s].delegate_reflection.options[:through].to_s]
269
+
270
+ builder = associated_predicate_builder(relation.name.to_sym)
271
+ if join_dependency
272
+ join_dependency = join_dependency.children.find { |c| c.reflection.name == relation.name }
273
+ builder.table.instance_variable_set(:@arel_table, join_dependency.tables.first)
274
+ end
275
+ builder.build_from_filter_hash(value, join_dependency)
276
+ end
277
+
198
278
  end
279
+ end
199
280
 
200
- def filter_for_array(column, value, options={})
201
- table = options[:table_alias] ? arel_table.alias(options[:table_alias]) : arel_table
202
281
 
203
- if value.is_a?(Hash) || value.class.name == "ActionController::Parameters".freeze
204
- resource = all
205
- value.each_pair do |key, value|
206
- resource = case key.to_sym
207
- when :contains
208
- resource.where(table[column].contains(value))
209
- when :overlaps
210
- resource.where(table[column].overlaps(value))
211
- when :excludes
212
- resource.where.not(table[column].contains(value))
213
- # when :not_overlaps
214
- # resource.where.not(Arel::Nodes::Overlaps.new(table[column], Arel::Attributes::Array.new(Array(value))))
282
+ module ActiveRecord
283
+ class Relation
284
+ class FilterClauseFactory # :nodoc:
285
+ def initialize(klass, predicate_builder)
286
+ @klass = klass
287
+ @predicate_builder = predicate_builder
288
+ end
289
+
290
+ def build(filters, join_dependency)
291
+ binds = []
292
+
293
+ if filters.is_a?(Hash) || filters.is_a?(Array)
294
+ # attributes = predicate_builder.resolve_column_aliases(filters)
295
+ # attributes = klass.send(:expand_hash_conditions_for_aggregates, attributes)
296
+ # attributes.stringify_keys!
297
+ #
298
+ # attributes, binds = predicate_builder.create_binds(attributes)
299
+ parts = [predicate_builder.build_from_filter_hash(filters, join_dependency)]
215
300
  else
216
- raise "Not Supported: #{key.to_sym}"
301
+ raise ArgumentError, "Unsupported argument type: #{filters.inspect} (#{filters.class})"
217
302
  end
303
+
304
+ WhereClause.new(parts, binds)
218
305
  end
219
- resource
220
- else
221
- where(table[column].contains(value))
306
+
307
+ protected
308
+
309
+ attr_reader :klass, :predicate_builder
222
310
  end
223
311
  end
312
+ end
224
313
 
225
- def filter_for_has_and_belongs_to_many(relation, value)
226
- resource = all
227
-
228
- options = {}
229
- if connection.class.name == 'ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter'
230
- options[:table_alias] = relation.name
231
- elsif resource.klass == relation.klass
232
- options[:table_alias] = "#{relation.name}_#{relation.klass.table_name}"
314
+ class ActiveRecord::Relation
315
+ module Filter
316
+
317
+ def initialize(klass, table, predicate_builder, values = {})
318
+ @filters = []
319
+ super
233
320
  end
234
321
 
235
- if value.is_a?(Hash) || value.class.name == "ActionController::Parameters".freeze
236
- resource = resource.joins(relation.name) #if !resource.references?(relation.name)
237
- resource = resource.merge(relation.klass.filter(value, options))
238
- elsif value.is_a?(Integer)
239
- resource = resource.joins(relation.name) #if !resource.references?(relation.name)
240
- resource = resource.merge(relation.klass.filter(value, options))
241
- elsif value.is_a?(Array)
242
- resource = resource.joins(relation.name) #if !resource.references?(relation.name)
243
- resource = resource.merge(relation.klass.filter(value, options))
244
- else
245
- raise 'Not supported'
322
+ def initialize_copy(other)
323
+ @filters = @filters.deep_dup
324
+ super
246
325
  end
247
-
248
- resource
249
- end
250
-
251
- def filter_for_has_many(relation, value)
252
- resource = all
253
-
254
- if value.is_a?(Hash) || value.class.name == "ActionController::Parameters".freeze
255
- if relation.options[:through] && !relation.options[:source_type]
256
- resource = resource.joins(relation.options[:through] => relation.source_reflection_name)
257
- else
258
- resource = resource.joins(relation.name) # if !resource.joined?(relation.name)
259
- end
260
- resource = resource.merge(relation.klass.filter(value))
261
- elsif value.is_a?(Array) || value.is_a?(Integer)
262
- resource = filter_for_has_many(relation, {:id => value})
263
- elsif value == true || value == 'true'
264
- counter_cache_column_name = relation.counter_cache_column || "#{relation.plural_name}_count"
265
- if resource.column_names.include?(counter_cache_column_name)
266
- resource = resource.where(resource.arel_table[counter_cache_column_name.to_sym].gt(0))
267
- else
268
- raise 'Not supported'
326
+
327
+ def filter(filters)
328
+ if filters.is_a?(ActionController::Parameters)
329
+ filters = filters.to_unsafe_h
330
+ elsif filters.is_a?(Array)
331
+ filters.map! do |f|
332
+ f.is_a?(ActionController::Parameters) ? f.to_unsafe_h : f
333
+ end
269
334
  end
270
- elsif value == false || value == 'false'
271
- # TODO if the has_many relationship has counter_cache true can just use counter_cache_column method
272
- counter_cache_column_name = relation.counter_cache_column || "#{relation.plural_name}_count"
273
- if resource.column_names.include?(counter_cache_column_name)
274
- resource = resource.where(resource.arel_table[counter_cache_column_name.to_sym].eq(0))
335
+
336
+ if filters.nil? || filters.empty?
337
+ self
275
338
  else
276
- raise 'Not supported'
339
+ spawn.filter!(filters)
277
340
  end
278
- else
279
- raise 'Not supported'
280
341
  end
281
-
282
- resource
283
- end
284
- alias_method :filter_for_has_one, :filter_for_has_many
285
-
286
- def filter_for_belongs_to(relation, value)
287
- resource = all
288
342
 
289
- options = {}
290
- if connection.class.name == 'ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter'
291
- options[:table_alias] = relation.name
343
+ def filter!(filters)
344
+ joins!(ActiveRecord::PredicateBuilder.filter_joins(klass, filters))
345
+ @filters << filters
346
+ self
347
+ end
348
+
349
+ def filter_clause_factory
350
+ @filter_clause_factory ||= FilterClauseFactory.new(klass, predicate_builder)
351
+ end
352
+
353
+ def build_arel
354
+ arel = super
355
+ build_filters(arel)
356
+ arel
292
357
  end
293
358
 
294
- if value.is_a?(Array) || value.is_a?(Integer) || value.is_a?(NilClass)
295
- resource = resource.where(:"#{relation.foreign_key}" => value)
296
- elsif value == true || value == 'true'
297
- resource = resource.where(resource.arel_table[:"#{relation.foreign_key}"].not_eq(nil))
298
- elsif value == false || value == 'false'
299
- resource = resource.where(resource.arel_table[:"#{relation.foreign_key}"].eq(nil))
300
- elsif value.is_a?(Hash) || value.class.name == "ActionController::Parameters".freeze
301
- if relation.polymorphic?
302
- raise 'no :as' if !value[:as]
303
- v = value.dup
304
- klass = v.delete(:as).classify.constantize
305
- t1 = resource.arel_table
306
- t2 = klass.arel_table
307
- resource = resource.joins(t1.join(t2).on(
308
- t2[:id].eq(t1["#{relation.name}_id"]).and(t1["#{relation.name}_type"].eq(klass.name))
309
- ).join_sources.first)
310
- resource = resource.merge(klass.filter(v, options))
311
- else
312
- resource = resource.joins(relation.name) # if !resource.references?(relation.name)
313
- resource = resource.merge(relation.klass.filter(value, options))
359
+ def build_join_query(manager, buckets, join_type)
360
+ buckets.default = []
361
+
362
+ association_joins = buckets[:association_join]
363
+ stashed_association_joins = buckets[:stashed_join]
364
+ join_nodes = buckets[:join_node].uniq
365
+ string_joins = buckets[:string_join].map(&:strip).uniq
366
+
367
+ join_list = join_nodes + convert_join_strings_to_ast(manager, string_joins)
368
+
369
+ join_dependency = ActiveRecord::Associations::JoinDependency.new(
370
+ @klass,
371
+ association_joins,
372
+ join_list
373
+ )
374
+
375
+ join_infos = join_dependency.join_constraints stashed_association_joins, join_type
376
+
377
+ join_infos.each do |info|
378
+ info.joins.each { |join| manager.from(join) }
379
+ manager.bind_values.concat info.binds
314
380
  end
315
- else
316
- if value.is_a?(String) && value =~ /\A\d+\Z/
317
- resource = resource.where(:"#{relation.foreign_key}" => value.to_i)
318
- else
319
- raise 'Not supported'
381
+
382
+ manager.join_sources.concat(join_list)
383
+
384
+ if @klass.connection.class.name != 'ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter'
385
+ @join_dependency = join_dependency
320
386
  end
387
+
388
+ manager
321
389
  end
322
- resource
390
+
391
+ def build_filters(manager)
392
+ @filters.each do |filters|
393
+ manager.where(filter_clause_factory.build(filters, @join_dependency&.join_root).ast)
394
+ end
395
+ end
396
+
323
397
  end
398
+ end
324
399
 
400
+ module ActiveRecord::SpawnMethods
401
+ def except(*skips)
402
+ r = relation_with values.except(*skips)
403
+ if !skips.include?(:where)
404
+ r.instance_variable_set(:@filters, instance_variable_get(:@filters))
405
+ end
406
+ r
407
+ end
325
408
  end
326
409
 
327
- ActiveRecord::Base.extend(ActiveRecord::Filter)
410
+ ActiveRecord::Relation.prepend(ActiveRecord::Relation::Filter)
411
+ ActiveRecord::Base.extend(ActiveRecord::Filter)
@@ -1,5 +1,5 @@
1
1
  module ActiveRecord
2
2
  module Filter
3
- VERSION = '5.0.0.6'
3
+ VERSION = '5.0.0.7'
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-filter
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.0.6
4
+ version: 5.0.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jon Bracy
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-01-22 00:00:00.000000000 Z
11
+ date: 2017-04-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -59,7 +59,7 @@ dependencies:
59
59
  version: '1.0'
60
60
  - - ">="
61
61
  - !ruby/object:Gem::Version
62
- version: 1.0.0
62
+ version: 1.5.2
63
63
  type: :runtime
64
64
  prerelease: false
65
65
  version_requirements: !ruby/object:Gem::Requirement
@@ -69,27 +69,21 @@ dependencies:
69
69
  version: '1.0'
70
70
  - - ">="
71
71
  - !ruby/object:Gem::Version
72
- version: 1.0.0
72
+ version: 1.5.2
73
73
  - !ruby/object:Gem::Dependency
74
74
  name: pg
75
75
  requirement: !ruby/object:Gem::Requirement
76
76
  requirements:
77
- - - "~>"
78
- - !ruby/object:Gem::Version
79
- version: '0.18'
80
77
  - - ">="
81
78
  - !ruby/object:Gem::Version
82
- version: 0.18.4
79
+ version: '0'
83
80
  type: :development
84
81
  prerelease: false
85
82
  version_requirements: !ruby/object:Gem::Requirement
86
83
  requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: '0.18'
90
84
  - - ">="
91
85
  - !ruby/object:Gem::Version
92
- version: 0.18.4
86
+ version: '0'
93
87
  - !ruby/object:Gem::Dependency
94
88
  name: bundler
95
89
  requirement: !ruby/object:Gem::Requirement
@@ -181,21 +175,21 @@ dependencies:
181
175
  - !ruby/object:Gem::Version
182
176
  version: 5.0.0
183
177
  - !ruby/object:Gem::Dependency
184
- name: factory_girl_rails
178
+ name: faker
185
179
  requirement: !ruby/object:Gem::Requirement
186
180
  requirements:
187
181
  - - ">="
188
182
  - !ruby/object:Gem::Version
189
- version: 4.7.0
183
+ version: '0'
190
184
  type: :development
191
185
  prerelease: false
192
186
  version_requirements: !ruby/object:Gem::Requirement
193
187
  requirements:
194
188
  - - ">="
195
189
  - !ruby/object:Gem::Version
196
- version: 4.7.0
190
+ version: '0'
197
191
  - !ruby/object:Gem::Dependency
198
- name: faker
192
+ name: byebug
199
193
  requirement: !ruby/object:Gem::Requirement
200
194
  requirements:
201
195
  - - ">="