squeel 1.0.11 → 1.0.12

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,22 +1 @@
1
- module Arel
2
-
3
- module Nodes
4
-
5
- class Grouping < Unary
6
- include Arel::Predications
7
- end unless Grouping.include?(Arel::Predications)
8
-
9
- end
10
-
11
- module Visitors
12
-
13
- class DepthFirst < Visitor
14
-
15
- unless method_defined?(:visit_Arel_Nodes_InfixOperation)
16
- alias :visit_Arel_Nodes_InfixOperation :binary
17
- end
18
-
19
- end
20
-
21
- end
22
- end
1
+ require 'squeel/adapters/active_record/compat'
@@ -1,76 +1 @@
1
- require 'squeel/context'
2
-
3
- module Squeel
4
- module Adapters
5
- module ActiveRecord
6
- class Context < ::Squeel::Context
7
- # Because the AR::Associations namespace is insane
8
- JoinPart = ::ActiveRecord::Associations::JoinDependency::JoinPart
9
-
10
- def initialize(object)
11
- super
12
- @base = object.join_base
13
- @engine = @base.arel_engine
14
- @arel_visitor = @engine.connection.visitor
15
- @default_table = Arel::Table.new(@base.table_name, :as => @base.aliased_table_name, :engine => @engine)
16
- end
17
-
18
- def find(object, parent = @base)
19
- if JoinPart === parent
20
- case object
21
- when String, Symbol, Nodes::Stub
22
- assoc_name = object.to_s
23
- @object.join_associations.detect { |j|
24
- j.reflection.name.to_s == assoc_name && j.parent == parent
25
- }
26
- when Nodes::Join
27
- @object.join_associations.detect { |j|
28
- j.reflection.name == object.name && j.parent == parent &&
29
- (object.polymorphic? ? j.reflection.klass == object._klass : true)
30
- }
31
- else
32
- @object.join_associations.detect { |j|
33
- j.reflection == object && j.parent == parent
34
- }
35
- end
36
- end
37
- end
38
-
39
- def traverse(keypath, parent = @base, include_endpoint = false)
40
- parent = @base if keypath.absolute?
41
- keypath.path_without_endpoint.each do |key|
42
- parent = find(key, parent) || key
43
- end
44
- parent = find(keypath.endpoint, parent) if include_endpoint
45
-
46
- parent
47
- end
48
-
49
- private
50
-
51
- def get_table(object)
52
- if [Symbol, String, Nodes::Stub].include?(object.class)
53
- Arel::Table.new(object.to_s, :engine => @engine)
54
- elsif Nodes::Join === object
55
- object._klass ? object._klass.arel_table : Arel::Table.new(object._name, :engine => @engine)
56
- elsif object.respond_to?(:aliased_table_name)
57
- Arel::Table.new(object.table_name, :as => object.aliased_table_name, :engine => @engine)
58
- else
59
- raise ArgumentError, "Unable to get table for #{object}"
60
- end
61
- end
62
-
63
- def classify(object)
64
- if Class === object
65
- object
66
- elsif object.respond_to? :active_record
67
- object.active_record
68
- else
69
- raise ArgumentError, "#{object} can't be converted to a class"
70
- end
71
- end
72
-
73
- end
74
- end
75
- end
76
- end
1
+ require 'squeel/adapters/active_record/context'
@@ -1,21 +1 @@
1
- module Squeel
2
- module Adapters
3
- module ActiveRecord
4
- module PreloaderExtensions
5
-
6
- def self.included(base)
7
- base.class_eval do
8
- alias_method_chain :run, :squeel
9
- end
10
- end
11
-
12
- def run_with_squeel
13
- unless records.empty?
14
- Visitors::PreloadVisitor.new.accept(associations).each { |association| preload(association) }
15
- end
16
- end
17
-
18
- end
19
- end
20
- end
21
- end
1
+ require 'squeel/adapters/active_record/preloader_extensions'
@@ -1,67 +1,10 @@
1
- require 'active_record'
1
+ require 'squeel/adapters/active_record/relation_extensions'
2
2
 
3
3
  module Squeel
4
4
  module Adapters
5
5
  module ActiveRecord
6
6
  module RelationExtensions
7
7
 
8
- JoinAssociation = ::ActiveRecord::Associations::JoinDependency::JoinAssociation
9
- JoinDependency = ::ActiveRecord::Associations::JoinDependency
10
-
11
- attr_writer :join_dependency
12
- private :join_dependency=
13
-
14
- # Returns a JoinDependency for the current relation.
15
- #
16
- # We don't need to clear out @join_dependency by overriding #reset,
17
- # because the default #reset already does this, despite never setting
18
- # it anywhere that I can find. Serendipity, I say!
19
- def join_dependency
20
- @join_dependency ||= (build_join_dependency(table.from(table), @joins_values) && @join_dependency)
21
- end
22
-
23
- %w(where having group order select from).each do |visitor|
24
- define_method "#{visitor}_visit" do |values|
25
- Visitors.const_get("#{visitor.capitalize}Visitor").new(
26
- Context.new(join_dependency)
27
- ).accept(values)
28
- end
29
- end
30
-
31
- # We need to be able to support merging two relations without having
32
- # to get our hooks too deeply into ActiveRecord. That proves to be
33
- # easier said than done. I hate Relation#merge. If Squeel has a
34
- # nemesis, Relation#merge would be it.
35
- #
36
- # Whatever code you see here currently is my current best attempt at
37
- # coexisting peacefully with said nemesis.
38
- def merge(r, equalities_resolved = false)
39
- if ::ActiveRecord::Relation === r && !equalities_resolved
40
- if self.table_name != r.table_name
41
- super(r.visited)
42
- else
43
- merge_resolving_duplicate_squeel_equalities(r)
44
- end
45
- else
46
- super(r)
47
- end
48
- end
49
-
50
- def visited
51
- clone.visit!
52
- end
53
-
54
- def visit!
55
- @where_values = where_visit((@where_values - ['']).uniq)
56
- @having_values = having_visit(@having_values.uniq.reject{|h| h.blank?})
57
- # FIXME: AR barfs on ARel attributes in group_values. Workaround?
58
- # @group_values = group_visit(@group_values.uniq.reject{|g| g.blank?})
59
- @order_values = order_visit(@order_values.uniq.reject{|o| o.blank?})
60
- @select_values = select_visit(@select_values.uniq)
61
-
62
- self
63
- end
64
-
65
8
  def build_arel
66
9
  arel = table.from table
67
10
 
@@ -77,7 +20,7 @@ module Squeel
77
20
  arel.group(*group_visit(@group_values.uniq.reject{|g| g.blank?})) unless @group_values.empty?
78
21
 
79
22
  order = @reorder_value ? @reorder_value : @order_values
80
- order = order_visit(order)
23
+ order = order_visit(order.uniq)
81
24
  order = reverse_sql_order(attrs_to_orderings(order)) if @reverse_order_value
82
25
  arel.order(*order.uniq.reject{|o| o.blank?}) unless order.empty?
83
26
 
@@ -89,339 +32,9 @@ module Squeel
89
32
  arel
90
33
  end
91
34
 
92
- # reverse_sql_order will reverse the order of strings or Orderings,
93
- # but not attributes
94
- def attrs_to_orderings(order)
95
- order.map do |o|
96
- Arel::Attribute === o ? o.asc : o
97
- end
98
- end
99
-
100
- # So, building a select for a count query in ActiveRecord is
101
- # pretty heavily dependent on select_values containing strings.
102
- # I'd initially expected that I could just hack together a fix
103
- # to select_for_count and everything would fall in line, but
104
- # unfortunately, pretty much everything from that point on
105
- # in ActiveRecord::Calculations#perform_calculation expects
106
- # the column to be a string, or at worst, a symbol.
107
- #
108
- # In the long term, I would like to refactor the code in
109
- # Rails core, but for now, I'm going to settle for this hack
110
- # that tries really hard to coerce things to a string.
111
- def select_for_count
112
- visited_values = select_visit(select_values.uniq)
113
- if visited_values.size == 1
114
- select = visited_values.first
115
-
116
- str_select = case select
117
- when String
118
- select
119
- when Symbol
120
- select.to_s
121
- else
122
- select.to_sql if select.respond_to?(:to_sql)
123
- end
124
-
125
- str_select if str_select && str_select !~ /[,*]/
126
- end
127
- end
128
-
129
- def build_join_dependency(manager, joins)
130
- buckets = joins.group_by do |join|
131
- case join
132
- when String
133
- 'string_join'
134
- when Hash, Symbol, Array, Nodes::Stub, Nodes::Join, Nodes::KeyPath
135
- 'association_join'
136
- when JoinAssociation
137
- 'stashed_join'
138
- when Arel::Nodes::Join
139
- 'join_node'
140
- else
141
- raise 'unknown class: %s' % join.class.name
142
- end
143
- end
144
-
145
- association_joins = buckets['association_join'] || []
146
- stashed_association_joins = buckets['stashed_join'] || []
147
- join_nodes = (buckets['join_node'] || []).uniq
148
- string_joins = (buckets['string_join'] || []).map { |x|
149
- x.strip
150
- }.uniq
151
-
152
- join_list = join_nodes + custom_join_ast(manager, string_joins)
153
-
154
- # All of that duplication just to do this...
155
- self.join_dependency = JoinDependency.new(
156
- @klass,
157
- association_joins,
158
- join_list
159
- )
160
-
161
- join_dependency.graft(*stashed_association_joins)
162
-
163
- @implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?
164
-
165
- join_dependency.join_associations.each do |association|
166
- association.join_to(manager)
167
- end
168
-
169
- manager.join_sources.concat join_list
170
-
171
- manager
172
- end
173
-
174
- def includes(*args)
175
- if block_given? && args.empty?
176
- super(DSL.eval &Proc.new)
177
- else
178
- super
179
- end
180
- end
181
-
182
- def preload(*args)
183
- if block_given? && args.empty?
184
- super(DSL.eval &Proc.new)
185
- else
186
- super
187
- end
188
- end
189
-
190
- def eager_load(*args)
191
- if block_given? && args.empty?
192
- super(DSL.eval &Proc.new)
193
- else
194
- super
195
- end
196
- end
197
-
198
- def select(value = Proc.new)
199
- if block_given? && Proc === value
200
- if value.arity > 0
201
- to_a.select {|*block_args| value.call(*block_args)}
202
- else
203
- relation = clone
204
- relation.select_values += Array.wrap(DSL.eval &value)
205
- relation
206
- end
207
- else
208
- super
209
- end
210
- end
211
-
212
- def group(*args)
213
- if block_given? && args.empty?
214
- super(DSL.eval &Proc.new)
215
- else
216
- super
217
- end
218
- end
219
-
220
- def order(*args)
221
- if block_given? && args.empty?
222
- super(DSL.eval &Proc.new)
223
- else
224
- super
225
- end
226
- end
227
-
228
- def reorder(*args)
229
- if block_given? && args.empty?
230
- super(DSL.eval &Proc.new)
231
- else
232
- super
233
- end
234
- end
235
-
236
- def joins(*args)
237
- if block_given? && args.empty?
238
- super(DSL.eval &Proc.new)
239
- else
240
- super
241
- end
242
- end
243
-
244
- def where(opts = Proc.new, *rest)
245
- if block_given? && Proc === opts
246
- super(DSL.eval &opts)
247
- else
248
- super
249
- end
250
- end
251
-
252
- def having(*args)
253
- if block_given? && args.empty?
254
- super(DSL.eval &Proc.new)
255
- else
256
- super
257
- end
258
- end
259
-
260
- def from(*args)
261
- if block_given? && args.empty?
262
- super(DSL.eval &Proc.new)
263
- else
264
- super
265
- end
266
- end
267
-
268
- def build_where(opts, other = [])
269
- case opts
270
- when String, Array
271
- super
272
- else # Let's prevent PredicateBuilder from doing its thing
273
- [opts, *other].map do |arg|
274
- case arg
275
- when Array # Just in case there's an array in there somewhere
276
- @klass.send(:sanitize_sql, arg)
277
- when Hash
278
- @klass.send(:expand_hash_conditions_for_aggregates, arg)
279
- else
280
- arg
281
- end
282
- end
283
- end
284
- end
285
-
286
- def collapse_wheres(arel, wheres)
287
- wheres = Array(wheres)
288
- binaries = wheres.grep(Arel::Nodes::Binary)
289
-
290
- groups = binaries.group_by {|b| [b.class, b.left]}
291
-
292
- groups.each do |_, bins|
293
- arel.where(Arel::Nodes::And.new(bins))
294
- end
295
-
296
- (wheres - binaries).each do |where|
297
- where = Arel.sql(where) if String === where
298
- arel.where(Arel::Nodes::Grouping.new(where))
299
- end
300
- end
301
-
302
- def find_equality_predicates(nodes)
303
- nodes.map { |node|
304
- case node
305
- when Arel::Nodes::Equality
306
- if node.left.respond_to?(:relation) &&
307
- node.left.relation.name == table_name
308
- node
309
- end
310
- when Arel::Nodes::Grouping
311
- find_equality_predicates([node.expr])
312
- when Arel::Nodes::And
313
- find_equality_predicates(node.children)
314
- else
315
- nil
316
- end
317
- }.compact.flatten
318
- end
319
-
320
- def flatten_nodes(nodes)
321
- nodes.map { |node|
322
- case node
323
- when Array
324
- flatten_nodes(node)
325
- when Nodes::And
326
- flatten_nodes(node.children)
327
- when Nodes::Grouping
328
- flatten_nodes(node.expr)
329
- else
330
- node
331
- end
332
- }.flatten
333
- end
334
-
335
- def merge_resolving_duplicate_squeel_equalities(r)
336
- left = clone
337
- right = r.clone
338
- left.where_values = flatten_nodes(left.where_values)
339
- right.where_values = flatten_nodes(right.where_values)
340
- right_equalities = right.where_values.select do |obj|
341
- Nodes::Predicate === obj && obj.method_name == :eq
342
- end
343
- right.where_values -= right_equalities
344
- left.where_values = resolve_duplicate_squeel_equalities(
345
- left.where_values + right_equalities
346
- )
347
- left.merge(right, true)
348
- end
349
-
350
- def resolve_duplicate_squeel_equalities(wheres)
351
- seen = {}
352
- wheres.reverse.reject { |n|
353
- nuke = false
354
- if Nodes::Predicate === n && n.method_name == :eq
355
- nuke = seen[n.expr]
356
- seen[n.expr] = true
357
- end
358
- nuke
359
- }.reverse
360
- end
361
-
362
- # Simulate the logic that occurs in #to_a
363
- #
364
- # This will let us get a dump of the SQL that will be run against the
365
- # DB for debug purposes without actually running the query.
366
- def debug_sql
367
- if eager_loading?
368
- including = (@eager_load_values + @includes_values).uniq
369
- join_dependency = JoinDependency.new(@klass, including, [])
370
- construct_relation_for_association_find(join_dependency).to_sql
371
- else
372
- arel.to_sql
373
- end
374
- end
375
-
376
- ### ZOMG ALIAS_METHOD_CHAIN IS BELOW. HIDE YOUR EYES!
377
- # ...
378
- # ...
379
- # ...
380
- # Since you're still looking, let me explain this horrible
381
- # transgression you see before you.
382
- #
383
- # You see, Relation#where_values_hash is defined on the
384
- # ActiveRecord::Relation class, itself.
385
- #
386
- # Since it's defined there, but I would very much like to modify its
387
- # behavior, I have three choices:
388
- #
389
- # 1. Inherit from ActiveRecord::Relation in a Squeel::Relation
390
- # class, and make an attempt to usurp all of the various calls
391
- # to methods on ActiveRecord::Relation by doing some really
392
- # evil stuff with constant reassignment, all for the sake of
393
- # being able to use super().
394
- #
395
- # 2. Submit a patch to Rails core, breaking this method off into
396
- # another module, all for my own selfish desire to use super()
397
- # while mucking about in Rails internals.
398
- #
399
- # 3. Use alias_method_chain, and say 10 hail Hanssons as penance.
400
- #
401
- # I opted to go with #3. Except for the hail Hansson thing.
402
- # Unless you're DHH, in which case, I totally said them.
403
- #
404
- # If you'd like to read more about alias_method_chain, see
405
- # http://erniemiller.org/2011/02/03/when-to-use-alias_method_chain/
406
-
407
- def self.included(base)
408
- base.class_eval do
409
- alias_method_chain :where_values_hash, :squeel
410
- end
411
- end
412
-
413
- # where_values_hash is used in scope_for_create. It's what allows
414
- # new records to be created with any equality values that exist in
415
- # your model's default scope. We hijack it in order to dig down into
416
- # And and Grouping nodes, which are equivalent to seeing top-level
417
- # Equality nodes in stock AR terms.
418
- def where_values_hash_with_squeel
419
- equalities = find_equality_predicates(where_visit(with_default_scope.where_values))
420
-
421
- Hash[equalities.map { |where| [where.left.name, where.right] }]
422
- end
423
-
424
35
  end
425
36
  end
426
37
  end
427
38
  end
39
+
40
+ ActiveRecord::Relation.send :include, Squeel::Adapters::ActiveRecord::RelationExtensions