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,3 +1,8 @@
1
+ ## 1.0.12 (2012-10-07)
2
+
3
+ * Properly uniq order_values before visiting, to fix #163
4
+ * Remove an unnecessary passthrough on String in visitor.rb. Fixes #162
5
+
1
6
  ## 1.0.11 (2012-09-03)
2
7
 
3
8
  * Fixed issue #157, resolving problems when joining the same table twice.
@@ -6,7 +11,7 @@
6
11
 
7
12
  ## 1.0.10 (2012-09-01)
8
13
 
9
- * Yanked from RubyGems.org due to semantic versioning oversight
14
+ * Yanked from RubyGems.org due to semantic versioning oversight
10
15
 
11
16
  ## 1.0.9 (2012-08-06)
12
17
 
@@ -3,36 +3,10 @@ when 3
3
3
  ActiveRecord::Relation.send :include, Squeel::Nodes::Aliasing
4
4
  require 'squeel/adapters/active_record/join_dependency_extensions'
5
5
  require 'squeel/adapters/active_record/base_extensions'
6
- ActiveRecord::Base.extend Squeel::Adapters::ActiveRecord::BaseExtensions
7
6
 
8
- case ActiveRecord::VERSION::MINOR
9
- when 0
10
- require 'squeel/adapters/active_record/3.0/compat'
11
- require 'squeel/adapters/active_record/3.0/relation_extensions'
12
- require 'squeel/adapters/active_record/3.0/association_preload_extensions'
13
- require 'squeel/adapters/active_record/3.0/context'
14
-
15
- ActiveRecord::Relation.send :include, Squeel::Adapters::ActiveRecord::RelationExtensions
16
- ActiveRecord::Associations::ClassMethods::JoinDependency.send :include, Squeel::Adapters::ActiveRecord::JoinDependencyExtensions
17
- ActiveRecord::Base.extend Squeel::Adapters::ActiveRecord::AssociationPreloadExtensions
18
- when 1
19
- require 'squeel/adapters/active_record/3.1/compat'
20
- require 'squeel/adapters/active_record/3.1/relation_extensions'
21
- require 'squeel/adapters/active_record/3.1/preloader_extensions'
22
- require 'squeel/adapters/active_record/3.1/context'
23
-
24
- ActiveRecord::Relation.send :include, Squeel::Adapters::ActiveRecord::RelationExtensions
25
- ActiveRecord::Associations::JoinDependency.send :include, Squeel::Adapters::ActiveRecord::JoinDependencyExtensions
26
- ActiveRecord::Associations::Preloader.send :include, Squeel::Adapters::ActiveRecord::PreloaderExtensions
27
- else
28
- require 'squeel/adapters/active_record/compat'
29
- require 'squeel/adapters/active_record/relation_extensions'
30
- require 'squeel/adapters/active_record/preloader_extensions'
31
- require 'squeel/adapters/active_record/context'
32
-
33
- ActiveRecord::Relation.send :include, Squeel::Adapters::ActiveRecord::RelationExtensions
34
- ActiveRecord::Associations::JoinDependency.send :include, Squeel::Adapters::ActiveRecord::JoinDependencyExtensions
35
- ActiveRecord::Associations::Preloader.send :include, Squeel::Adapters::ActiveRecord::PreloaderExtensions
7
+ adapter_directory = "#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}"
8
+ Dir[File.expand_path("../active_record/#{adapter_directory}/*.rb", __FILE__)].each do |f|
9
+ require f
36
10
  end
37
11
  else
38
12
  raise NotImplementedError, "Squeel does not support ActiveRecord version #{ActiveRecord::VERSION::STRING}"
@@ -11,3 +11,5 @@ module Squeel
11
11
  end
12
12
  end
13
13
  end
14
+
15
+ ActiveRecord::Base.extend Squeel::Adapters::ActiveRecord::AssociationPreloadExtensions
@@ -1,73 +1,14 @@
1
- require 'squeel/context'
1
+ require 'squeel/adapters/active_record/context'
2
2
 
3
3
  module Squeel
4
4
  module Adapters
5
5
  module ActiveRecord
6
6
  class Context < ::Squeel::Context
7
- # Because the AR::Associations namespace is insane
8
- JoinBase = ::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinBase
9
-
10
- def initialize(object)
11
- super
12
- @base = object.join_base
13
- @engine = @base.arel_engine
14
- @arel_visitor = Arel::Visitors.visitor_for @engine
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 JoinBase === 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
7
 
49
8
  private
50
9
 
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
10
+ def get_arel_visitor
11
+ Arel::Visitors.visitor_for @engine
71
12
  end
72
13
 
73
14
  end
@@ -1,16 +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::ClassMethods::JoinDependency::JoinAssociation
9
- JoinDependency = ::ActiveRecord::Associations::ClassMethods::JoinDependency
10
-
11
- attr_writer :join_dependency
12
- private :join_dependency=
13
-
14
8
  # Returns a JoinDependency for the current relation.
15
9
  #
16
10
  # We don't need to clear out @join_dependency by overriding #reset,
@@ -20,48 +14,6 @@ module Squeel
20
14
  @join_dependency ||= (build_join_dependency(table, @joins_values) && @join_dependency)
21
15
  end
22
16
 
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
17
  def build_arel
66
18
  arel = table
67
19
 
@@ -86,35 +38,6 @@ module Squeel
86
38
  arel
87
39
  end
88
40
 
89
- # So, building a select for a count query in ActiveRecord is
90
- # pretty heavily dependent on select_values containing strings.
91
- # I'd initially expected that I could just hack together a fix
92
- # to select_for_count and everything would fall in line, but
93
- # unfortunately, pretty much everything from that point on
94
- # in ActiveRecord::Calculations#perform_calculation expects
95
- # the column to be a string, or at worst, a symbol.
96
- #
97
- # In the long term, I would like to refactor the code in
98
- # Rails core, but for now, I'm going to settle for this hack
99
- # that tries really hard to coerce things to a string.
100
- def select_for_count
101
- visited_values = select_visit(select_values.uniq)
102
- if visited_values.size == 1
103
- select = visited_values.first
104
-
105
- str_select = case select
106
- when String
107
- select
108
- when Symbol
109
- select.to_s
110
- else
111
- select.to_sql if select.respond_to?(:to_sql)
112
- end
113
-
114
- str_select if str_select && str_select !~ /[,*]/
115
- end
116
- end
117
-
118
41
  def build_join_dependency(relation, joins)
119
42
  association_joins = []
120
43
 
@@ -153,126 +76,14 @@ module Squeel
153
76
  relation = relation.join(custom_joins)
154
77
  end
155
78
 
156
- def includes(*args)
157
- if block_given? && args.empty?
158
- super(DSL.eval &Proc.new)
159
- else
160
- super
161
- end
162
- end
163
-
164
- def preload(*args)
165
- if block_given? && args.empty?
166
- super(DSL.eval &Proc.new)
167
- else
168
- super
169
- end
170
- end
171
-
172
- def eager_load(*args)
173
- if block_given? && args.empty?
174
- super(DSL.eval &Proc.new)
175
- else
176
- super
177
- end
178
- end
179
-
180
- def select(value = Proc.new)
181
- if block_given? && Proc === value
182
- if value.arity > 0
183
- to_a.select {|*block_args| value.call(*block_args)}
184
- else
185
- relation = clone
186
- relation.select_values += Array.wrap(DSL.eval &value)
187
- relation
188
- end
189
- else
190
- super
191
- end
192
- end
193
-
194
- def group(*args)
195
- if block_given? && args.empty?
196
- super(DSL.eval &Proc.new)
197
- else
198
- super
199
- end
200
- end
201
-
202
- def order(*args)
203
- if block_given? && args.empty?
204
- super(DSL.eval &Proc.new)
205
- else
206
- super
207
- end
208
- end
209
-
210
- def reorder(*args)
211
- if block_given? && args.empty?
212
- super(DSL.eval &Proc.new)
213
- else
214
- super
215
- end
216
- end
217
-
218
- def joins(*args)
219
- if block_given? && args.empty?
220
- super(DSL.eval &Proc.new)
221
- else
222
- super
223
- end
224
- end
225
-
226
- def where(opts = Proc.new, *rest)
227
- if block_given? && Proc === opts
228
- super(DSL.eval &opts)
229
- else
230
- super
231
- end
232
- end
233
-
234
- def having(*args)
235
- if block_given? && args.empty?
236
- super(DSL.eval &Proc.new)
237
- else
238
- super
239
- end
240
- end
241
-
242
- def from(*args)
243
- if block_given? && args.empty?
244
- super(DSL.eval &Proc.new)
245
- else
246
- super
247
- end
248
- end
249
-
250
- def build_where(opts, other = [])
251
- case opts
252
- when String, Array
253
- super
254
- else # Let's prevent PredicateBuilder from doing its thing
255
- [opts, *other].map do |arg|
256
- case arg
257
- when Array # Just in case there's an array in there somewhere
258
- @klass.send(:sanitize_sql, arg)
259
- when Hash
260
- @klass.send(:expand_hash_conditions_for_aggregates, arg)
261
- else
262
- arg
263
- end
264
- end
265
- end
266
- end
267
-
268
79
  def collapse_wheres(arel, wheres)
269
- wheres = [wheres] unless Array === wheres
80
+ wheres = Array(wheres)
270
81
  binaries = wheres.grep(Arel::Nodes::Binary)
271
82
 
272
83
  groups = binaries.group_by {|b| [b.class, b.left]}
273
84
 
274
85
  groups.each do |_, bins|
275
- arel = arel.where(bins.inject(&:and))
86
+ arel = arel.where(Arel::Nodes::And.new(bins))
276
87
  end
277
88
 
278
89
  (wheres - binaries).each do |where|
@@ -283,117 +94,6 @@ module Squeel
283
94
  arel
284
95
  end
285
96
 
286
- def find_equality_predicates(nodes)
287
- nodes.map { |node|
288
- case node
289
- when Arel::Nodes::Equality
290
- if node.left.respond_to?(:relation) &&
291
- node.left.relation.name == table_name
292
- node
293
- end
294
- when Arel::Nodes::Grouping
295
- find_equality_predicates([node.expr])
296
- when Arel::Nodes::And
297
- find_equality_predicates(node.children)
298
- else
299
- nil
300
- end
301
- }.compact.flatten
302
- end
303
-
304
- def flatten_nodes(nodes)
305
- nodes.map { |node|
306
- case node
307
- when Array
308
- flatten_nodes(node)
309
- when Nodes::And
310
- flatten_nodes(node.children)
311
- when Nodes::Grouping
312
- flatten_nodes(node.expr)
313
- else
314
- node
315
- end
316
- }.flatten
317
- end
318
-
319
- def merge_resolving_duplicate_squeel_equalities(r)
320
- left = clone
321
- right = r.clone
322
- left.where_values = flatten_nodes(left.where_values)
323
- right.where_values = flatten_nodes(right.where_values)
324
- right_equalities = right.where_values.select do |obj|
325
- Nodes::Predicate === obj && obj.method_name == :eq
326
- end
327
- right.where_values -= right_equalities
328
- left.where_values = resolve_duplicate_squeel_equalities(
329
- left.where_values + right_equalities
330
- )
331
- left.merge(right, true)
332
- end
333
-
334
- def resolve_duplicate_squeel_equalities(wheres)
335
- seen = {}
336
- wheres.reverse.reject { |n|
337
- nuke = false
338
- if Nodes::Predicate === n && n.method_name == :eq
339
- nuke = seen[n.expr]
340
- seen[n.expr] = true
341
- end
342
- nuke
343
- }.reverse
344
- end
345
-
346
- # Simulate the logic that occurs in #to_a
347
- #
348
- # This will let us get a dump of the SQL that will be run against the
349
- # DB for debug purposes without actually running the query.
350
- def debug_sql
351
- if eager_loading?
352
- including = (@eager_load_values + @includes_values).uniq
353
- join_dependency = JoinDependency.new(@klass, including, nil)
354
- construct_relation_for_association_find(join_dependency).to_sql
355
- else
356
- arel.to_sql
357
- end
358
- end
359
-
360
- ### ZOMG ALIAS_METHOD_CHAIN IS BELOW. HIDE YOUR EYES!
361
- # ...
362
- # ...
363
- # ...
364
- # Since you're still looking, let me explain this horrible
365
- # transgression you see before you.
366
- #
367
- # You see, Relation#where_values_hash is defined on the
368
- # ActiveRecord::Relation class, itself.
369
- #
370
- # Since it's defined there, but I would very much like to modify its
371
- # behavior, I have three choices:
372
- #
373
- # 1. Inherit from ActiveRecord::Relation in a Squeel::Relation
374
- # class, and make an attempt to usurp all of the various calls
375
- # to methods on ActiveRecord::Relation by doing some really
376
- # evil stuff with constant reassignment, all for the sake of
377
- # being able to use super().
378
- #
379
- # 2. Submit a patch to Rails core, breaking this method off into
380
- # another module, all for my own selfish desire to use super()
381
- # while mucking about in Rails internals.
382
- #
383
- # 3. Use alias_method_chain, and say 10 hail Hanssons as penance.
384
- #
385
- # I opted to go with #3. Except for the hail Hansson thing.
386
- # Unless you're DHH, in which case, I totally said them.
387
- #
388
- # If you'd like to read more about alias_method_chain, see
389
- # http://erniemiller.org/2011/02/03/when-to-use-alias_method_chain/
390
-
391
- def self.included(base)
392
- base.class_eval do
393
- alias_method_chain :where_values_hash, :squeel
394
- end
395
- end
396
-
397
97
  def where_values_hash_with_squeel
398
98
  equalities = find_equality_predicates(where_visit(@where_values))
399
99
 
@@ -404,3 +104,5 @@ module Squeel
404
104
  end
405
105
  end
406
106
  end
107
+
108
+ ActiveRecord::Relation.send :include, Squeel::Adapters::ActiveRecord::RelationExtensions