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.
- data/CHANGELOG.md +6 -1
- data/lib/squeel/adapters/active_record.rb +3 -29
- data/lib/squeel/adapters/active_record/3.0/association_preload_extensions.rb +2 -0
- data/lib/squeel/adapters/active_record/3.0/context.rb +3 -62
- data/lib/squeel/adapters/active_record/3.0/relation_extensions.rb +5 -303
- data/lib/squeel/adapters/active_record/3.1/compat.rb +1 -22
- data/lib/squeel/adapters/active_record/3.1/context.rb +1 -76
- data/lib/squeel/adapters/active_record/3.1/preloader_extensions.rb +1 -21
- data/lib/squeel/adapters/active_record/3.1/relation_extensions.rb +4 -391
- data/lib/squeel/adapters/active_record/3.2/compat.rb +1 -0
- data/lib/squeel/adapters/active_record/3.2/context.rb +1 -0
- data/lib/squeel/adapters/active_record/3.2/preloader_extensions.rb +1 -0
- data/lib/squeel/adapters/active_record/3.2/relation_extensions.rb +40 -0
- data/lib/squeel/adapters/active_record/base_extensions.rb +2 -0
- data/lib/squeel/adapters/active_record/compat.rb +22 -1
- data/lib/squeel/adapters/active_record/context.rb +84 -1
- data/lib/squeel/adapters/active_record/join_dependency_extensions.rb +11 -0
- data/lib/squeel/adapters/active_record/preloader_extensions.rb +23 -1
- data/lib/squeel/adapters/active_record/relation_extensions.rb +376 -17
- data/lib/squeel/version.rb +1 -1
- data/lib/squeel/visitors/visitor.rb +0 -1
- data/spec/squeel/adapters/active_record/relation_extensions_spec.rb +5 -0
- metadata +8 -4
data/CHANGELOG.md
CHANGED
@@ -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
|
-
|
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
|
-
|
9
|
-
|
10
|
-
require
|
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}"
|
@@ -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
|
52
|
-
|
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 =
|
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(
|
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
|