squeel 1.0.11 → 1.0.12

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.
@@ -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