activerecord-filter 5.0.0.6 → 5.0.0.7

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 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
  - - ">="