squeel 0.9.3 → 0.9.4

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml CHANGED
@@ -1,7 +1,10 @@
1
1
  rvm:
2
2
  - 1.8.7
3
3
  - 1.9.2
4
+ - 1.9.3
4
5
  - ree
5
6
  - rbx
6
- - rbx-2.0
7
- - ruby-head
7
+ - ruby-head
8
+
9
+ env:
10
+ - RAILS=3-2-stable
@@ -15,6 +15,14 @@ when 3
15
15
  ActiveRecord::Relation.send :include, Squeel::Adapters::ActiveRecord::RelationExtensions
16
16
  ActiveRecord::Associations::ClassMethods::JoinDependency.send :include, Squeel::Adapters::ActiveRecord::JoinDependencyExtensions
17
17
  ActiveRecord::Base.extend Squeel::Adapters::ActiveRecord::AssociationPreloadExtensions
18
+ when 1
19
+ require 'squeel/adapters/active_record/3.1/relation_extensions'
20
+ require 'squeel/adapters/active_record/3.1/preloader_extensions'
21
+ require 'squeel/adapters/active_record/3.1/context'
22
+
23
+ ActiveRecord::Relation.send :include, Squeel::Adapters::ActiveRecord::RelationExtensions
24
+ ActiveRecord::Associations::JoinDependency.send :include, Squeel::Adapters::ActiveRecord::JoinDependencyExtensions
25
+ ActiveRecord::Associations::Preloader.send :include, Squeel::Adapters::ActiveRecord::PreloaderExtensions
18
26
  else
19
27
  require 'squeel/adapters/active_record/relation_extensions'
20
28
  require 'squeel/adapters/active_record/preloader_extensions'
@@ -0,0 +1,76 @@
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
+ object = object.to_sym if String === object
21
+ case object
22
+ when Symbol, Nodes::Stub
23
+ @object.join_associations.detect { |j|
24
+ j.reflection.name == object.to_sym && 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.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_sym, :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
@@ -0,0 +1,21 @@
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::SymbolVisitor.new.accept(associations).each { |association| preload(association) }
15
+ end
16
+ end
17
+
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,360 @@
1
+ require 'active_record'
2
+
3
+ module Squeel
4
+ module Adapters
5
+ module ActiveRecord
6
+ module RelationExtensions
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, because
17
+ # the default #reset already does this, despite never setting it anywhere that
18
+ # 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
+ def predicate_visitor
24
+ Visitors::PredicateVisitor.new(
25
+ Context.new(join_dependency)
26
+ )
27
+ end
28
+
29
+ def attribute_visitor
30
+ Visitors::AttributeVisitor.new(
31
+ Context.new(join_dependency)
32
+ )
33
+ end
34
+
35
+ # We need to be able to support merging two relations that have a different
36
+ # base class. Stock ActiveRecord doesn't have to do anything too special, because
37
+ # it's already created predicates out of the where_values by now, and they're
38
+ # already bound to the proper table.
39
+ #
40
+ # Squeel, on the other hand, needs to do its best to ensure the predicates are still
41
+ # winding up against the proper table. The most "correct" way I can think of to do
42
+ # this is to try to emulate the default AR behavior -- that is, de-squeelifying
43
+ # the *_values, erm... values by visiting them and converting them to ARel nodes
44
+ # before merging. Merging relations is a nifty little trick, but it's another
45
+ # little corner of ActiveRecord where the magic quickly fades. :(
46
+ def merge(r)
47
+ if relation_with_different_base?(r)
48
+ r = r.clone.visit!
49
+ end
50
+
51
+ super(r)
52
+ end
53
+
54
+ def relation_with_different_base?(r)
55
+ ::ActiveRecord::Relation === r &&
56
+ base_class.name != r.klass.base_class.name
57
+ end
58
+
59
+ def prepare_relation_for_association_merge!(r, association_name)
60
+ r.where_values.map! {|w| Squeel::Visitors::PredicateVisitor.can_visit?(w) ? {association_name => w} : w}
61
+ r.having_values.map! {|h| Squeel::Visitors::PredicateVisitor.can_visit?(h) ? {association_name => h} : h}
62
+ r.group_values.map! {|g| Squeel::Visitors::AttributeVisitor.can_visit?(g) ? {association_name => g} : g}
63
+ r.order_values.map! {|o| Squeel::Visitors::AttributeVisitor.can_visit?(o) ? {association_name => o} : o}
64
+ r.select_values.map! {|s| Squeel::Visitors::AttributeVisitor.can_visit?(s) ? {association_name => s} : s}
65
+ r.joins_values.map! {|j| [Symbol, Hash, Nodes::Stub, Nodes::Join, Nodes::KeyPath].include?(j.class) ? {association_name => j} : j}
66
+ r.includes_values.map! {|i| [Symbol, Hash, Nodes::Stub, Nodes::Join, Nodes::KeyPath].include?(i.class) ? {association_name => i} : i}
67
+ end
68
+
69
+ def visit!
70
+ predicate_viz = predicate_visitor
71
+ attribute_viz = attribute_visitor
72
+
73
+ @where_values = predicate_viz.accept((@where_values - ['']).uniq)
74
+ @having_values = predicate_viz.accept(@having_values.uniq.reject{|h| h.blank?})
75
+ @group_values = attribute_viz.accept(@group_values.uniq.reject{|g| g.blank?})
76
+ @order_values = attribute_viz.accept(@order_values.uniq.reject{|o| o.blank?})
77
+ @select_values = attribute_viz.accept(@select_values.uniq)
78
+
79
+ self
80
+ end
81
+
82
+ def build_arel
83
+ arel = table.from table
84
+
85
+ build_join_dependency(arel, @joins_values) unless @joins_values.empty?
86
+
87
+ predicate_viz = predicate_visitor
88
+ attribute_viz = attribute_visitor
89
+
90
+ collapse_wheres(arel, predicate_viz.accept((@where_values - ['']).uniq))
91
+
92
+ arel.having(*predicate_viz.accept(@having_values.uniq.reject{|h| h.blank?})) unless @having_values.empty?
93
+
94
+ arel.take(connection.sanitize_limit(@limit_value)) if @limit_value
95
+ arel.skip(@offset_value) if @offset_value
96
+
97
+ arel.group(*attribute_viz.accept(@group_values.uniq.reject{|g| g.blank?})) unless @group_values.empty?
98
+
99
+ order = @reorder_value ? @reorder_value : @order_values
100
+ order = attribute_viz.accept(order.uniq.reject{|o| o.blank?})
101
+ order = reverse_sql_order(attrs_to_orderings(order)) if @reverse_order_value
102
+ arel.order(*order) unless order.empty?
103
+
104
+ build_select(arel, attribute_viz.accept(@select_values.uniq))
105
+
106
+ arel.from(@from_value) if @from_value
107
+ arel.lock(@lock_value) if @lock_value
108
+
109
+ arel
110
+ end
111
+
112
+ # reverse_sql_order will reverse the order of strings or Orderings,
113
+ # but not attributes
114
+ def attrs_to_orderings(order)
115
+ order.map do |o|
116
+ Arel::Attribute === o ? o.asc : o
117
+ end
118
+ end
119
+
120
+ def build_join_dependency(manager, joins)
121
+ buckets = joins.group_by do |join|
122
+ case join
123
+ when String
124
+ 'string_join'
125
+ when Hash, Symbol, Array, Nodes::Stub, Nodes::Join, Nodes::KeyPath
126
+ 'association_join'
127
+ when JoinAssociation
128
+ 'stashed_join'
129
+ when Arel::Nodes::Join
130
+ 'join_node'
131
+ else
132
+ raise 'unknown class: %s' % join.class.name
133
+ end
134
+ end
135
+
136
+ association_joins = buckets['association_join'] || []
137
+ stashed_association_joins = buckets['stashed_join'] || []
138
+ join_nodes = buckets['join_node'] || []
139
+ string_joins = (buckets['string_join'] || []).map { |x|
140
+ x.strip
141
+ }.uniq
142
+
143
+ join_list = custom_join_ast(manager, string_joins)
144
+
145
+ # All of this duplication just to add
146
+ self.join_dependency = JoinDependency.new(
147
+ @klass,
148
+ association_joins,
149
+ join_list
150
+ )
151
+
152
+ join_nodes.each do |join|
153
+ join_dependency.alias_tracker.aliased_name_for(join.left.name.downcase)
154
+ end
155
+
156
+ join_dependency.graft(*stashed_association_joins)
157
+
158
+ @implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?
159
+
160
+ join_dependency.join_associations.each do |association|
161
+ association.join_to(manager)
162
+ end
163
+
164
+ manager.join_sources.concat join_nodes.uniq
165
+ manager.join_sources.concat join_list
166
+
167
+ manager
168
+ end
169
+
170
+ def includes(*args)
171
+ if block_given? && args.empty?
172
+ super(DSL.eval &Proc.new)
173
+ else
174
+ super
175
+ end
176
+ end
177
+
178
+ def preload(*args)
179
+ if block_given? && args.empty?
180
+ super(DSL.eval &Proc.new)
181
+ else
182
+ super
183
+ end
184
+ end
185
+
186
+ def eager_load(*args)
187
+ if block_given? && args.empty?
188
+ super(DSL.eval &Proc.new)
189
+ else
190
+ super
191
+ end
192
+ end
193
+
194
+ def select(value = Proc.new)
195
+ if block_given? && Proc === value
196
+ if value.arity > 0
197
+ to_a.select {|*block_args| value.call(*block_args)}
198
+ else
199
+ relation = clone
200
+ relation.select_values += Array.wrap(DSL.eval &value)
201
+ relation
202
+ end
203
+ else
204
+ super
205
+ end
206
+ end
207
+
208
+ def group(*args)
209
+ if block_given? && args.empty?
210
+ super(DSL.eval &Proc.new)
211
+ else
212
+ super
213
+ end
214
+ end
215
+
216
+ def order(*args)
217
+ if block_given? && args.empty?
218
+ super(DSL.eval &Proc.new)
219
+ else
220
+ super
221
+ end
222
+ end
223
+
224
+ def reorder(*args)
225
+ if block_given? && args.empty?
226
+ super(DSL.eval &Proc.new)
227
+ else
228
+ super
229
+ end
230
+ end
231
+
232
+ def joins(*args)
233
+ if block_given? && args.empty?
234
+ super(DSL.eval &Proc.new)
235
+ else
236
+ super
237
+ end
238
+ end
239
+
240
+ def where(opts = Proc.new, *rest)
241
+ if block_given? && Proc === opts
242
+ super(DSL.eval &opts)
243
+ else
244
+ super
245
+ end
246
+ end
247
+
248
+ def having(*args)
249
+ if block_given? && args.empty?
250
+ super(DSL.eval &Proc.new)
251
+ else
252
+ super
253
+ end
254
+ end
255
+
256
+ def build_where(opts, other = [])
257
+ case opts
258
+ when String, Array
259
+ super
260
+ else # Let's prevent PredicateBuilder from doing its thing
261
+ [opts, *other].map do |arg|
262
+ case arg
263
+ when Array # Just in case there's an array in there somewhere
264
+ @klass.send(:sanitize_sql, arg)
265
+ when Hash
266
+ @klass.send(:expand_hash_conditions_for_aggregates, arg)
267
+ else
268
+ arg
269
+ end
270
+ end
271
+ end
272
+ end
273
+
274
+ def collapse_wheres(arel, wheres)
275
+ wheres = Array(wheres)
276
+ binaries = wheres.grep(Arel::Nodes::Binary)
277
+
278
+ groups = binaries.group_by {|b| [b.class, b.left]}
279
+
280
+ groups.each do |_, bins|
281
+ arel.where(Arel::Nodes::And.new(bins))
282
+ end
283
+
284
+ (wheres - binaries).each do |where|
285
+ where = Arel.sql(where) if String === where
286
+ arel.where(Arel::Nodes::Grouping.new(where))
287
+ end
288
+ end
289
+
290
+ def find_equality_predicates(nodes)
291
+ nodes.map { |node|
292
+ case node
293
+ when Arel::Nodes::Equality
294
+ node if node.left.relation.name == table_name
295
+ when Arel::Nodes::Grouping
296
+ find_equality_predicates([node.expr])
297
+ when Arel::Nodes::And
298
+ find_equality_predicates(node.children)
299
+ else
300
+ nil
301
+ end
302
+ }.compact.flatten
303
+ end
304
+
305
+ # Simulate the logic that occurs in #to_a
306
+ #
307
+ # This will let us get a dump of the SQL that will be run against the
308
+ # DB for debug purposes without actually running the query.
309
+ def debug_sql
310
+ if eager_loading?
311
+ including = (@eager_load_values + @includes_values).uniq
312
+ join_dependency = JoinDependency.new(@klass, including, [])
313
+ construct_relation_for_association_find(join_dependency).to_sql
314
+ else
315
+ arel.to_sql
316
+ end
317
+ end
318
+
319
+ ### ZOMG ALIAS_METHOD_CHAIN IS BELOW. HIDE YOUR EYES!
320
+ # ...
321
+ # ...
322
+ # ...
323
+ # Since you're still looking, let me explain this horrible
324
+ # transgression you see before you.
325
+ # You see, Relation#where_values_hash is defined on the
326
+ # ActiveRecord::Relation class. Since it's defined there, but
327
+ # I would very much like to modify its behavior, I have three
328
+ # choices.
329
+ #
330
+ # 1. Inherit from ActiveRecord::Relation in a Squeel::Relation
331
+ # class, and make an attempt to usurp all of the various calls
332
+ # to methods on ActiveRecord::Relation by doing some really
333
+ # evil stuff with constant reassignment, all for the sake of
334
+ # being able to use super().
335
+ #
336
+ # 2. Submit a patch to Rails core, breaking this method off into
337
+ # another module, all for my own selfish desire to use super()
338
+ # while mucking about in Rails internals.
339
+ #
340
+ # 3. Use alias_method_chain, and say 10 hail Hanssons as penance.
341
+ #
342
+ # I opted to go with #3. Except for the hail Hansson thing.
343
+ # Unless you're DHH, in which case, I totally said them.
344
+
345
+ def self.included(base)
346
+ base.class_eval do
347
+ alias_method_chain :where_values_hash, :squeel
348
+ end
349
+ end
350
+
351
+ def where_values_hash_with_squeel
352
+ equalities = find_equality_predicates(predicate_visitor.accept(@where_values))
353
+
354
+ Hash[equalities.map { |where| [where.left.name, where.right] }]
355
+ end
356
+
357
+ end
358
+ end
359
+ end
360
+ end
@@ -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
- object = object.to_sym if String === object
21
- case object
22
- when Symbol, Nodes::Stub
23
- @object.join_associations.detect { |j|
24
- j.reflection.name == object.to_sym && 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.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_sym, :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/3.1/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::SymbolVisitor.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/3.1/preloader_extensions'
@@ -1,84 +1,10 @@
1
- require 'active_record'
1
+ require 'squeel/adapters/active_record/3.1/relation_extensions'
2
2
 
3
3
  module Squeel
4
4
  module Adapters
5
5
  module ActiveRecord
6
6
  module RelationExtensions
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, because
17
- # the default #reset already does this, despite never setting it anywhere that
18
- # 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
- def predicate_visitor
24
- Visitors::PredicateVisitor.new(
25
- Context.new(join_dependency)
26
- )
27
- end
28
-
29
- def attribute_visitor
30
- Visitors::AttributeVisitor.new(
31
- Context.new(join_dependency)
32
- )
33
- end
34
-
35
- # We need to be able to support merging two relations that have a different
36
- # base class. Stock ActiveRecord doesn't have to do anything too special, because
37
- # it's already created predicates out of the where_values by now, and they're
38
- # already bound to the proper table.
39
- #
40
- # Squeel, on the other hand, needs to do its best to ensure the predicates are still
41
- # winding up against the proper table. The most "correct" way I can think of to do
42
- # this is to try to emulate the default AR behavior -- that is, de-squeelifying
43
- # the *_values, erm... values by visiting them and converting them to ARel nodes
44
- # before merging. Merging relations is a nifty little trick, but it's another
45
- # little corner of ActiveRecord where the magic quickly fades. :(
46
- def merge(r)
47
- if relation_with_different_base?(r)
48
- r = r.clone.visit!
49
- end
50
-
51
- super(r)
52
- end
53
-
54
- def relation_with_different_base?(r)
55
- ::ActiveRecord::Relation === r &&
56
- base_class.name != r.klass.base_class.name
57
- end
58
-
59
- def prepare_relation_for_association_merge!(r, association_name)
60
- r.where_values.map! {|w| Squeel::Visitors::PredicateVisitor.can_visit?(w) ? {association_name => w} : w}
61
- r.having_values.map! {|h| Squeel::Visitors::PredicateVisitor.can_visit?(h) ? {association_name => h} : h}
62
- r.group_values.map! {|g| Squeel::Visitors::AttributeVisitor.can_visit?(g) ? {association_name => g} : g}
63
- r.order_values.map! {|o| Squeel::Visitors::AttributeVisitor.can_visit?(o) ? {association_name => o} : o}
64
- r.select_values.map! {|s| Squeel::Visitors::AttributeVisitor.can_visit?(s) ? {association_name => s} : s}
65
- r.joins_values.map! {|j| [Symbol, Hash, Nodes::Stub, Nodes::Join, Nodes::KeyPath].include?(j.class) ? {association_name => j} : j}
66
- r.includes_values.map! {|i| [Symbol, Hash, Nodes::Stub, Nodes::Join, Nodes::KeyPath].include?(i.class) ? {association_name => i} : i}
67
- end
68
-
69
- def visit!
70
- predicate_viz = predicate_visitor
71
- attribute_viz = attribute_visitor
72
-
73
- @where_values = predicate_viz.accept((@where_values - ['']).uniq)
74
- @having_values = predicate_viz.accept(@having_values.uniq.reject{|h| h.blank?})
75
- @group_values = attribute_viz.accept(@group_values.uniq.reject{|g| g.blank?})
76
- @order_values = attribute_viz.accept(@order_values.uniq.reject{|o| o.blank?})
77
- @select_values = attribute_viz.accept(@select_values.uniq)
78
-
79
- self
80
- end
81
-
7
+
82
8
  def build_arel
83
9
  arel = table.from table
84
10
 
@@ -102,258 +28,14 @@ module Squeel
102
28
  arel.order(*order) unless order.empty?
103
29
 
104
30
  build_select(arel, attribute_viz.accept(@select_values.uniq))
105
-
31
+
32
+ arel.distinct(@uniq_value)
106
33
  arel.from(@from_value) if @from_value
107
34
  arel.lock(@lock_value) if @lock_value
108
35
 
109
36
  arel
110
37
  end
111
-
112
- # reverse_sql_order will reverse the order of strings or Orderings,
113
- # but not attributes
114
- def attrs_to_orderings(order)
115
- order.map do |o|
116
- Arel::Attribute === o ? o.asc : o
117
- end
118
- end
119
-
120
- def build_join_dependency(manager, joins)
121
- buckets = joins.group_by do |join|
122
- case join
123
- when String
124
- 'string_join'
125
- when Hash, Symbol, Array, Nodes::Stub, Nodes::Join, Nodes::KeyPath
126
- 'association_join'
127
- when JoinAssociation
128
- 'stashed_join'
129
- when Arel::Nodes::Join
130
- 'join_node'
131
- else
132
- raise 'unknown class: %s' % join.class.name
133
- end
134
- end
135
-
136
- association_joins = buckets['association_join'] || []
137
- stashed_association_joins = buckets['stashed_join'] || []
138
- join_nodes = buckets['join_node'] || []
139
- string_joins = (buckets['string_join'] || []).map { |x|
140
- x.strip
141
- }.uniq
142
-
143
- join_list = custom_join_ast(manager, string_joins)
144
-
145
- # All of this duplication just to add
146
- self.join_dependency = JoinDependency.new(
147
- @klass,
148
- association_joins,
149
- join_list
150
- )
151
-
152
- join_nodes.each do |join|
153
- join_dependency.alias_tracker.aliased_name_for(join.left.name.downcase)
154
- end
155
-
156
- join_dependency.graft(*stashed_association_joins)
157
-
158
- @implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?
159
-
160
- join_dependency.join_associations.each do |association|
161
- association.join_to(manager)
162
- end
163
-
164
- manager.join_sources.concat join_nodes.uniq
165
- manager.join_sources.concat join_list
166
-
167
- manager
168
- end
169
-
170
- def includes(*args)
171
- if block_given? && args.empty?
172
- super(DSL.eval &Proc.new)
173
- else
174
- super
175
- end
176
- end
177
-
178
- def preload(*args)
179
- if block_given? && args.empty?
180
- super(DSL.eval &Proc.new)
181
- else
182
- super
183
- end
184
- end
185
-
186
- def eager_load(*args)
187
- if block_given? && args.empty?
188
- super(DSL.eval &Proc.new)
189
- else
190
- super
191
- end
192
- end
193
-
194
- def select(value = Proc.new)
195
- if block_given? && Proc === value
196
- if value.arity > 0
197
- to_a.select {|*block_args| value.call(*block_args)}
198
- else
199
- relation = clone
200
- relation.select_values += Array.wrap(DSL.eval &value)
201
- relation
202
- end
203
- else
204
- super
205
- end
206
- end
207
-
208
- def group(*args)
209
- if block_given? && args.empty?
210
- super(DSL.eval &Proc.new)
211
- else
212
- super
213
- end
214
- end
215
-
216
- def order(*args)
217
- if block_given? && args.empty?
218
- super(DSL.eval &Proc.new)
219
- else
220
- super
221
- end
222
- end
223
-
224
- def reorder(*args)
225
- if block_given? && args.empty?
226
- super(DSL.eval &Proc.new)
227
- else
228
- super
229
- end
230
- end
231
-
232
- def joins(*args)
233
- if block_given? && args.empty?
234
- super(DSL.eval &Proc.new)
235
- else
236
- super
237
- end
238
- end
239
-
240
- def where(opts = Proc.new, *rest)
241
- if block_given? && Proc === opts
242
- super(DSL.eval &opts)
243
- else
244
- super
245
- end
246
- end
247
-
248
- def having(*args)
249
- if block_given? && args.empty?
250
- super(DSL.eval &Proc.new)
251
- else
252
- super
253
- end
254
- end
255
-
256
- def build_where(opts, other = [])
257
- case opts
258
- when String, Array
259
- super
260
- else # Let's prevent PredicateBuilder from doing its thing
261
- [opts, *other].map do |arg|
262
- case arg
263
- when Array # Just in case there's an array in there somewhere
264
- @klass.send(:sanitize_sql, arg)
265
- when Hash
266
- @klass.send(:expand_hash_conditions_for_aggregates, arg)
267
- else
268
- arg
269
- end
270
- end
271
- end
272
- end
273
-
274
- def collapse_wheres(arel, wheres)
275
- wheres = Array(wheres)
276
- binaries = wheres.grep(Arel::Nodes::Binary)
277
-
278
- groups = binaries.group_by {|b| [b.class, b.left]}
279
-
280
- groups.each do |_, bins|
281
- arel.where(Arel::Nodes::And.new(bins))
282
- end
283
-
284
- (wheres - binaries).each do |where|
285
- where = Arel.sql(where) if String === where
286
- arel.where(Arel::Nodes::Grouping.new(where))
287
- end
288
- end
289
-
290
- def find_equality_predicates(nodes)
291
- nodes.map { |node|
292
- case node
293
- when Arel::Nodes::Equality
294
- node if node.left.relation.name == table_name
295
- when Arel::Nodes::Grouping
296
- find_equality_predicates([node.expr])
297
- when Arel::Nodes::And
298
- find_equality_predicates(node.children)
299
- else
300
- nil
301
- end
302
- }.compact.flatten
303
- end
304
-
305
- # Simulate the logic that occurs in #to_a
306
- #
307
- # This will let us get a dump of the SQL that will be run against the
308
- # DB for debug purposes without actually running the query.
309
- def debug_sql
310
- if eager_loading?
311
- including = (@eager_load_values + @includes_values).uniq
312
- join_dependency = JoinDependency.new(@klass, including, [])
313
- construct_relation_for_association_find(join_dependency).to_sql
314
- else
315
- arel.to_sql
316
- end
317
- end
318
-
319
- ### ZOMG ALIAS_METHOD_CHAIN IS BELOW. HIDE YOUR EYES!
320
- # ...
321
- # ...
322
- # ...
323
- # Since you're still looking, let me explain this horrible
324
- # transgression you see before you.
325
- # You see, Relation#where_values_hash is defined on the
326
- # ActiveRecord::Relation class. Since it's defined there, but
327
- # I would very much like to modify its behavior, I have three
328
- # choices.
329
- #
330
- # 1. Inherit from ActiveRecord::Relation in a Squeel::Relation
331
- # class, and make an attempt to usurp all of the various calls
332
- # to methods on ActiveRecord::Relation by doing some really
333
- # evil stuff with constant reassignment, all for the sake of
334
- # being able to use super().
335
- #
336
- # 2. Submit a patch to Rails core, breaking this method off into
337
- # another module, all for my own selfish desire to use super()
338
- # while mucking about in Rails internals.
339
- #
340
- # 3. Use alias_method_chain, and say 10 hail Hanssons as penance.
341
- #
342
- # I opted to go with #3. Except for the hail Hansson thing.
343
- # Unless you're DHH, in which case, I totally said them.
344
-
345
- def self.included(base)
346
- base.class_eval do
347
- alias_method_chain :where_values_hash, :squeel
348
- end
349
- end
350
-
351
- def where_values_hash_with_squeel
352
- equalities = find_equality_predicates(predicate_visitor.accept(@where_values))
353
-
354
- Hash[equalities.map { |where| [where.left.name, where.right] }]
355
- end
356
-
38
+
357
39
  end
358
40
  end
359
41
  end
@@ -34,7 +34,7 @@ module Squeel
34
34
  # @param [Symbol] sym2 :hash or :symbol
35
35
  def load_core_extensions(*exts)
36
36
  exts.each do |ext|
37
- require "core_ext/#{ext}"
37
+ require "squeel/core_ext/#{ext}"
38
38
  end
39
39
  end
40
40
 
@@ -51,4 +51,4 @@ module Squeel
51
51
  end
52
52
 
53
53
  end
54
- end
54
+ end
File without changes
File without changes
@@ -1,3 +1,3 @@
1
1
  module Squeel
2
- VERSION = "0.9.3"
2
+ VERSION = "0.9.4"
3
3
  end
@@ -97,6 +97,10 @@ module Squeel
97
97
  relation.join_dependency.join_associations.should have(6).items
98
98
  arel.to_sql.should match /INNER JOIN "people" "parents_people_3" ON "parents_people_3"."id" = "children_people_3"."parent_id"/
99
99
  end
100
+
101
+ it 'respects :uniq option on associations' do
102
+ Article.first.uniq_commenters.length.should eq Article.first.uniq_commenters.count
103
+ end
100
104
 
101
105
  it 'visits wheres with a PredicateVisitor, converting them to ARel nodes' do
102
106
  relation = Person.where(:name.matches => '%bob%')
@@ -48,6 +48,7 @@ class Article < ActiveRecord::Base
48
48
  has_and_belongs_to_many :tags
49
49
  has_many :notes, :as => :notable
50
50
  has_many :commenters, :through => :comments, :source => :person
51
+ has_many :uniq_commenters, :through => :comments, :source => :person, :uniq => true
51
52
  end
52
53
 
53
54
  class Comment < ActiveRecord::Base
@@ -137,6 +138,7 @@ module Schema
137
138
  end
138
139
 
139
140
  Comment.make(:body => 'First post!', :article => Article.make(:title => 'Hello, world!'))
141
+ Comment.make(:body => 'Last post!', :article => Article.first, :person => Article.first.commenters.first)
140
142
 
141
143
  end
142
144
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: squeel
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.3
4
+ version: 0.9.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-09-30 00:00:00.000000000Z
12
+ date: 2012-01-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
16
- requirement: &70248155556820 !ruby/object:Gem::Requirement
16
+ requirement: &70129585123660 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '3.0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70248155556820
24
+ version_requirements: *70129585123660
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: activesupport
27
- requirement: &70248155556320 !ruby/object:Gem::Requirement
27
+ requirement: &70129585122480 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '3.0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70248155556320
35
+ version_requirements: *70129585122480
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: polyamorous
38
- requirement: &70248155551000 !ruby/object:Gem::Requirement
38
+ requirement: &70129585121660 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ~>
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: 0.5.0
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *70248155551000
46
+ version_requirements: *70129585121660
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: rspec
49
- requirement: &70248155547740 !ruby/object:Gem::Requirement
49
+ requirement: &70129585121080 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ~>
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: 2.6.0
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *70248155547740
57
+ version_requirements: *70129585121080
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: machinist
60
- requirement: &70248155546940 !ruby/object:Gem::Requirement
60
+ requirement: &70129585120560 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ~>
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: 1.0.6
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *70248155546940
68
+ version_requirements: *70129585120560
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: faker
71
- requirement: &70248155545680 !ruby/object:Gem::Requirement
71
+ requirement: &70129585135820 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ~>
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: 0.9.5
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *70248155545680
79
+ version_requirements: *70129585135820
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: sqlite3
82
- requirement: &70248155527020 !ruby/object:Gem::Requirement
82
+ requirement: &70129585134740 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ~>
@@ -87,7 +87,7 @@ dependencies:
87
87
  version: 1.3.3
88
88
  type: :development
89
89
  prerelease: false
90
- version_requirements: *70248155527020
90
+ version_requirements: *70129585134740
91
91
  description: ! "\n Squeel unlocks the power of ARel in your Rails 3 application
92
92
  with\n a handy block-based syntax. You can write subqueries, access named\n
93
93
  \ functions provided by your RDBMS, and more, all without writing\n SQL
@@ -105,14 +105,15 @@ files:
105
105
  - LICENSE
106
106
  - README.md
107
107
  - Rakefile
108
- - lib/core_ext/hash.rb
109
- - lib/core_ext/symbol.rb
110
108
  - lib/squeel.rb
111
109
  - lib/squeel/adapters/active_record.rb
112
110
  - lib/squeel/adapters/active_record/3.0/association_preload_extensions.rb
113
111
  - lib/squeel/adapters/active_record/3.0/compat.rb
114
112
  - lib/squeel/adapters/active_record/3.0/context.rb
115
113
  - lib/squeel/adapters/active_record/3.0/relation_extensions.rb
114
+ - lib/squeel/adapters/active_record/3.1/context.rb
115
+ - lib/squeel/adapters/active_record/3.1/preloader_extensions.rb
116
+ - lib/squeel/adapters/active_record/3.1/relation_extensions.rb
116
117
  - lib/squeel/adapters/active_record/base_extensions.rb
117
118
  - lib/squeel/adapters/active_record/context.rb
118
119
  - lib/squeel/adapters/active_record/join_dependency_extensions.rb
@@ -121,6 +122,8 @@ files:
121
122
  - lib/squeel/configuration.rb
122
123
  - lib/squeel/constants.rb
123
124
  - lib/squeel/context.rb
125
+ - lib/squeel/core_ext/hash.rb
126
+ - lib/squeel/core_ext/symbol.rb
124
127
  - lib/squeel/dsl.rb
125
128
  - lib/squeel/nodes.rb
126
129
  - lib/squeel/nodes/aliasing.rb