squeel 0.9.3 → 0.9.4

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