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,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