squeel 1.0.6 → 1.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,7 @@
1
+ ## 1.0.7 (2012-07-14)
2
+
3
+ * Prevent reorder(nil) on reversed SQL from adding an order by id.
4
+
1
5
  ## 1.0.6 (2012-06-16)
2
6
 
3
7
  * Prevent cloned KeyPaths from modifying each other. Fixes #135
@@ -20,7 +20,7 @@ when 3
20
20
  require 'squeel/adapters/active_record/3.1/relation_extensions'
21
21
  require 'squeel/adapters/active_record/3.1/preloader_extensions'
22
22
  require 'squeel/adapters/active_record/3.1/context'
23
-
23
+
24
24
  ActiveRecord::Relation.send :include, Squeel::Adapters::ActiveRecord::RelationExtensions
25
25
  ActiveRecord::Associations::JoinDependency.send :include, Squeel::Adapters::ActiveRecord::JoinDependencyExtensions
26
26
  ActiveRecord::Associations::Preloader.send :include, Squeel::Adapters::ActiveRecord::PreloaderExtensions
@@ -39,14 +39,18 @@ module Squeel
39
39
  # behavior with Squeel loaded is to visit the *_values arrays in
40
40
  # the relations we're merging, and then use the default AR merge
41
41
  # code on the result.
42
- def merge(r, relations_visited = false)
43
- if relations_visited or not ::ActiveRecord::Relation === r
42
+ def merge(r, skip_visit = false)
43
+ if skip_visit or not ::ActiveRecord::Relation === r
44
44
  super(r)
45
45
  else
46
- clone.visit!.merge(r.clone.visit!, true)
46
+ visited.merge(r.visited, true)
47
47
  end
48
48
  end
49
49
 
50
+ def visited
51
+ clone.visit!
52
+ end
53
+
50
54
  def visit!
51
55
  predicate_viz = predicate_visitor
52
56
  attribute_viz = attribute_visitor
@@ -39,22 +39,16 @@ module Squeel
39
39
  # behavior with Squeel loaded is to visit the *_values arrays in
40
40
  # the relations we're merging, and then use the default AR merge
41
41
  # code on the result.
42
- def merge(r, relations_visited = false)
43
- if relations_visited or not ::ActiveRecord::Relation === r
42
+ def merge(r, skip_visit = false)
43
+ if skip_visit or not ::ActiveRecord::Relation === r
44
44
  super(r)
45
45
  else
46
- clone.visit!.merge(r.clone.visit!, true)
46
+ visited.merge(r.visited, true)
47
47
  end
48
48
  end
49
49
 
50
- def prepare_relation_for_association_merge!(r, association_name)
51
- r.where_values.map! {|w| Squeel::Visitors::PredicateVisitor.can_visit?(w) ? {association_name => w} : w}
52
- r.having_values.map! {|h| Squeel::Visitors::PredicateVisitor.can_visit?(h) ? {association_name => h} : h}
53
- r.group_values.map! {|g| Squeel::Visitors::AttributeVisitor.can_visit?(g) ? {association_name => g} : g}
54
- r.order_values.map! {|o| Squeel::Visitors::AttributeVisitor.can_visit?(o) ? {association_name => o} : o}
55
- r.select_values.map! {|s| Squeel::Visitors::AttributeVisitor.can_visit?(s) ? {association_name => s} : s}
56
- r.joins_values.map! {|j| [Symbol, Hash, Nodes::Stub, Nodes::Join, Nodes::KeyPath].include?(j.class) ? {association_name => j} : j}
57
- r.includes_values.map! {|i| [Symbol, Hash, Nodes::Stub, Nodes::Join, Nodes::KeyPath].include?(i.class) ? {association_name => i} : i}
50
+ def visited
51
+ clone.visit!
58
52
  end
59
53
 
60
54
  def visit!
@@ -89,9 +83,9 @@ module Squeel
89
83
  arel.group(*attribute_viz.accept(@group_values.uniq.reject{|g| g.blank?})) unless @group_values.empty?
90
84
 
91
85
  order = @reorder_value ? @reorder_value : @order_values
92
- order = attribute_viz.accept(order.uniq.reject{|o| o.blank?})
86
+ order = attribute_viz.accept(order)
93
87
  order = reverse_sql_order(attrs_to_orderings(order)) if @reverse_order_value
94
- arel.order(*order) unless order.empty?
88
+ arel.order(*order.uniq.reject{|o| o.blank?}) unless order.empty?
95
89
 
96
90
  build_select(arel, attribute_viz.accept(@select_values.uniq))
97
91
 
@@ -318,6 +312,33 @@ module Squeel
318
312
  }.compact.flatten
319
313
  end
320
314
 
315
+ def flatten_nodes(nodes)
316
+ nodes.map { |node|
317
+ case node
318
+ when Array
319
+ flatten_nodes(node)
320
+ when Nodes::And
321
+ flatten_nodes(node.children)
322
+ when Nodes::Grouping
323
+ flatten_nodes(node.expr)
324
+ else
325
+ node
326
+ end
327
+ }.flatten
328
+ end
329
+
330
+ def overwrite_squeel_equalities(wheres)
331
+ seen = {}
332
+ flatten_nodes(wheres).reverse.reject { |n|
333
+ nuke = false
334
+ if Nodes::Predicate === n && n.method_name == :eq
335
+ nuke = seen[n.expr]
336
+ seen[n.expr] = true
337
+ end
338
+ nuke
339
+ }.reverse
340
+ end
341
+
321
342
  # Simulate the logic that occurs in #to_a
322
343
  #
323
344
  # This will let us get a dump of the SQL that will be run against the
@@ -338,9 +359,11 @@ module Squeel
338
359
  # ...
339
360
  # Since you're still looking, let me explain this horrible
340
361
  # transgression you see before you.
341
- # You see, Relation#where_values_hash is defined on the
342
- # ActiveRecord::Relation class. Since it's defined there, but
343
- # I would very much like to modify its behavior, I have three
362
+ #
363
+ # You see, Relation#where_values_hash and with_default_scope
364
+ # are defined on the ActiveRecord::Relation class, itself.
365
+ # ActiveRecord::Relation class. Since they're defined there, but
366
+ # I would very much like to modify their behavior, I have three
344
367
  # choices.
345
368
  #
346
369
  # 1. Inherit from ActiveRecord::Relation in a Squeel::Relation
@@ -349,7 +372,7 @@ module Squeel
349
372
  # evil stuff with constant reassignment, all for the sake of
350
373
  # being able to use super().
351
374
  #
352
- # 2. Submit a patch to Rails core, breaking this method off into
375
+ # 2. Submit a patch to Rails core, breaking these methods off into
353
376
  # another module, all for my own selfish desire to use super()
354
377
  # while mucking about in Rails internals.
355
378
  #
@@ -357,19 +380,61 @@ module Squeel
357
380
  #
358
381
  # I opted to go with #3. Except for the hail Hansson thing.
359
382
  # Unless you're DHH, in which case, I totally said them.
383
+ #
384
+ # If you'd like to read more about alias_method_chain, see
385
+ # http://erniemiller.org/2011/02/03/when-to-use-alias_method_chain/
360
386
 
361
387
  def self.included(base)
362
388
  base.class_eval do
363
389
  alias_method_chain :where_values_hash, :squeel
390
+ alias_method_chain :with_default_scope, :squeel
364
391
  end
365
392
  end
366
393
 
394
+ # where_values_hash is used in scope_for_create. It's what allows
395
+ # new records to be created with any equality values that exist in
396
+ # your model's default scope. We hijack it in order to dig down into
397
+ # And and Grouping nodes, which are equivalent to seeing top-level
398
+ # Equality nodes in stock AR terms.
367
399
  def where_values_hash_with_squeel
368
400
  equalities = find_equality_predicates(predicate_visitor.accept(with_default_scope.where_values))
369
401
 
370
402
  Hash[equalities.map { |where| [where.left.name, where.right] }]
371
403
  end
372
404
 
405
+ # with_default_scope was added to ActiveRecord ~> 3.1 in order to
406
+ # address https://github.com/rails/rails/issues/1233. Unfortunately,
407
+ # it plays havoc with Squeel's approach of visiting both sides of
408
+ # a relation merge. Thankfully, when merging with a relation from
409
+ # the same AR::Base, it's unnecessary to visit...
410
+ #
411
+ # Except, of course, this means we have to handle the edge case
412
+ # where equalities are duplicated on both sides of the merge, in
413
+ # which case, unlike all other chained relation calls, the latter
414
+ # equality overwrites the former.
415
+ #
416
+ # The workaround using overwrite_squeel_equalities works as long
417
+ # as you stick to the Squeel DSL, but breaks down if you throw hash
418
+ # conditions into the mix. If anyone's got any suggestions, I'm all
419
+ # ears. Otherwise, just stick to the Squeel DSL.
420
+ #
421
+ # Or, don't use default scopes. They're the devil, anyway. I can't
422
+ # remember the last time I used one and didn't find myself
423
+ # regretting the decision later.
424
+ def with_default_scope_with_squeel
425
+ if default_scoped? &&
426
+ default_scope = klass.build_default_scope_with_squeel
427
+ default_scope = default_scope.merge(self, true)
428
+ default_scope.default_scoped = false
429
+ default_scope.where_values = overwrite_squeel_equalities(
430
+ default_scope.where_values + self.where_values
431
+ )
432
+ default_scope
433
+ else
434
+ self
435
+ end
436
+ end
437
+
373
438
  end
374
439
  end
375
440
  end
@@ -16,7 +16,26 @@ module Squeel
16
16
  end
17
17
  end
18
18
 
19
+ def build_default_scope_with_squeel #:nodoc:
20
+ if defined?(::ActiveRecord::Scoping) &&
21
+ method(:default_scope).owner != ::ActiveRecord::Scoping::Default::ClassMethods
22
+ evaluate_default_scope { default_scope }
23
+ elsif default_scopes.any?
24
+ evaluate_default_scope do
25
+ default_scopes.inject(relation) do |default_scope, scope|
26
+ if scope.is_a?(Hash)
27
+ default_scope.apply_finder_options(scope)
28
+ elsif !scope.is_a?(::ActiveRecord::Relation) && scope.respond_to?(:call)
29
+ default_scope.merge(scope.call, true)
30
+ else
31
+ default_scope.merge(scope, true)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+
19
38
  end
20
39
  end
21
40
  end
22
- end
41
+ end
@@ -22,10 +22,9 @@ module Squeel
22
22
 
23
23
  arel.group(*attribute_viz.accept(@group_values.uniq.reject{|g| g.blank?})) unless @group_values.empty?
24
24
 
25
- order = @reorder_value ? @reorder_value : @order_values
26
- order = attribute_viz.accept(order.uniq.reject{|o| o.blank?})
25
+ order = attribute_viz.accept(@order_values)
27
26
  order = reverse_sql_order(attrs_to_orderings(order)) if @reverse_order_value
28
- arel.order(*order) unless order.empty?
27
+ arel.order(*order.uniq.reject{|o| o.blank?}) unless order.empty?
29
28
 
30
29
  build_select(arel, attribute_viz.accept(@select_values.uniq))
31
30
 
@@ -7,6 +7,7 @@ module Squeel
7
7
  include PredicateOperators
8
8
  include Operators
9
9
  include Ordering
10
+ include Aliasing
10
11
 
11
12
  alias :== :eq
12
13
  alias :'^' :not_eq
@@ -26,10 +27,6 @@ module Squeel
26
27
  # @return [Array] The arguments to be passed to the SQL function
27
28
  attr_reader :args
28
29
 
29
- # @return [String] The SQL function's alias
30
- # @return [NilClass] If no alias
31
- attr_reader :alias
32
-
33
30
  # Create a node representing an SQL Function with the given name and arguments
34
31
  # @param [Symbol] name The function name
35
32
  # @param [Array] args The array of arguments to pass to the function.
@@ -37,14 +34,6 @@ module Squeel
37
34
  @name, @args = name, args
38
35
  end
39
36
 
40
- # Set an alias for the function
41
- # @param [String, Symbol] The alias name
42
- # @return [Function] This function with the new alias value.
43
- def as(alias_name)
44
- @alias = alias_name.to_s
45
- self
46
- end
47
-
48
37
  # expand_hash_conditions_for_aggregates assumes our hash keys can be
49
38
  # converted to symbols, so this has to be implemented, but it doesn't
50
39
  # really have to do anything useful.
@@ -29,4 +29,4 @@ module Squeel
29
29
 
30
30
  end
31
31
  end
32
- end
32
+ end
@@ -1,3 +1,3 @@
1
1
  module Squeel
2
- VERSION = "1.0.6"
2
+ VERSION = "1.0.7"
3
3
  end
@@ -99,9 +99,8 @@ module Squeel
99
99
  quote arg
100
100
  end
101
101
  end
102
- func = Arel::Nodes::NamedFunction.new(o.name, args)
103
102
 
104
- o.alias ? func.as(o.alias) : func
103
+ Arel::Nodes::NamedFunction.new(o.name, args)
105
104
  end
106
105
 
107
106
  # Visit an Operation node. Each operand will be accepted or
@@ -134,7 +133,8 @@ module Squeel
134
133
  else
135
134
  Arel.sql("#{arel_visitor.accept(args[0])} #{o.operator} #{arel_visitor.accept(args[1])}")
136
135
  end
137
- o.alias ? op.as(o.alias) : op
136
+
137
+ op
138
138
  end
139
139
 
140
140
  # Visit a Squeel Grouping node, resulting in am ARel Grouping node.
@@ -152,9 +152,8 @@ module Squeel
152
152
  quote arg
153
153
  end
154
154
  end
155
- func = Arel::Nodes::NamedFunction.new(o.name, args)
156
155
 
157
- o.alias ? func.as(o.alias) : func
156
+ Arel::Nodes::NamedFunction.new(o.name, args)
158
157
  end
159
158
 
160
159
  # Visit an ActiveRecord Relation, returning an Arel::SelectManager
@@ -199,7 +198,8 @@ module Squeel
199
198
  else
200
199
  Arel::Nodes::InfixOperation.new(o.operator, args[0], args[1])
201
200
  end
202
- o.alias ? op.as(o.alias) : op
201
+
202
+ op
203
203
  end
204
204
 
205
205
  # Visit a Squeel And node, returning an ARel Grouping containing an
@@ -258,12 +258,25 @@ module Squeel
258
258
  block.debug_sql.should eq standard.debug_sql
259
259
  end
260
260
 
261
- it 'eager loads belongs_to_associations' do
262
- relation = Article.includes(:person).
263
- where{person.name == 'Ernie'}
264
- sql = relation.debug_sql
265
- sql.should match /LEFT OUTER JOIN "people"/
266
- sql.should match /"people"."name" = 'Ernie'/
261
+ it 'eager loads belongs_to associations' do
262
+ queries = queries_for do
263
+ Article.includes(:person).
264
+ where{person.name == 'Ernie'}.all
265
+ end
266
+ queries.should have(1).item
267
+ queries.first.should match /LEFT OUTER JOIN "people"/
268
+ queries.first.should match /"people"."name" = 'Ernie'/
269
+ end
270
+
271
+ it 'eager loads belongs_to associations on models with default_scopes' do
272
+ queries = queries_for do
273
+ PersonNamedBill.includes(:parent).
274
+ where{parent.name == 'Ernie'}.all
275
+ end
276
+ queries.should have(1).item
277
+ queries.first.should match /LEFT OUTER JOIN "people"/
278
+ queries.first.should match /"people"."name" = 'Bill'/
279
+ queries.first.should match /"parents_people"."name" = 'Ernie'/
267
280
  end
268
281
 
269
282
  it 'eager loads polymorphic belongs_to associations' do
@@ -409,10 +422,25 @@ module Squeel
409
422
  aric.last_article_id.should eq Article.where(:person_id => 1).last.id
410
423
  end
411
424
 
412
- it "doesn't break #count if non-strings are used" do
425
+ end
426
+
427
+ describe '#count' do
428
+
429
+ it 'works with non-strings in select' do
413
430
  Article.select{distinct(title)}.count.should eq 51
414
431
  end
415
432
 
433
+ it 'works with non-strings in wheres' do
434
+ first_name = Person.first.name
435
+ Person.where{name.op('=', first_name)}.count.should eq 1
436
+ end
437
+
438
+ it 'works with non-strings in group' do
439
+ pending "Requires some big hacks to execute_grouped_calculation"
440
+ counts = Person.group{name.op('||', '-diddly')}.count
441
+ counts.should eq Person.group{name.op('||', '-diddly')}.count
442
+ end
443
+
416
444
  end
417
445
 
418
446
  describe '#group' do
@@ -476,11 +504,6 @@ module Squeel
476
504
  old_and_busted.to_a.should eq new_hotness.to_a
477
505
  end
478
506
 
479
- it "doesn't break #count if wheres contain InfixOperations" do
480
- first_name = Person.first.name
481
- Person.where{name.op('=', first_name)}.count.should eq 1
482
- end
483
-
484
507
  end
485
508
 
486
509
  describe '#joins' do
@@ -570,11 +593,23 @@ module Squeel
570
593
  end
571
594
 
572
595
  it 'builds options with a block' do
573
- block = Person.reorder{id}
596
+ block = @standard.reorder{id}
574
597
  block.to_sql.should_not eq @standard.to_sql
575
598
  block.to_sql.should match /ORDER BY "people"."id"/
576
599
  end
577
600
 
601
+ it 'drops order by clause when passed nil' do
602
+ block = @standard.reorder(nil)
603
+ sql = block.to_sql
604
+ sql.should_not match /ORDER BY/
605
+ end
606
+
607
+ it 'drops order by clause when passed nil if reversed' do
608
+ block = @standard.reverse_order.reorder(nil)
609
+ sql = block.to_sql
610
+ sql.should_not match /ORDER BY/
611
+ end
612
+
578
613
  end
579
614
 
580
615
  describe '#build_where' do
@@ -689,6 +724,13 @@ module Squeel
689
724
  sql.should match /Bert/
690
725
  end
691
726
 
727
+ it 'uses the given equality condition in the case of a conflicting where from a default scope in AR >= 3.1' do
728
+ relation = PersonNamedBill.where{name == 'Ernie'}
729
+ sql = relation.to_sql
730
+ sql.should_not match /Bill/
731
+ sql.should match /Ernie/
732
+ end unless ::ActiveRecord::VERSION::MINOR == 0
733
+
692
734
  it "doesn't ruin everything when a scope returns nil" do
693
735
  relation = Person.nil_scope
694
736
  relation.should eq Person.scoped
@@ -153,20 +153,6 @@ module Squeel
153
153
  negated.expr.should eq @f
154
154
  end
155
155
 
156
- describe '#as' do
157
-
158
- it 'aliases the function' do
159
- @f.as('the_alias')
160
- @f.alias.should eq 'the_alias'
161
- end
162
-
163
- it 'casts the alias to a string' do
164
- @f.as(:the_alias)
165
- @f.alias.should eq 'the_alias'
166
- end
167
-
168
- end
169
-
170
156
  end
171
157
  end
172
158
  end
@@ -153,20 +153,6 @@ module Squeel
153
153
  negated.expr.should eq @o
154
154
  end
155
155
 
156
- describe '#as' do
157
-
158
- it 'aliases the function' do
159
- @o.as('the_alias')
160
- @o.alias.should eq 'the_alias'
161
- end
162
-
163
- it 'casts the alias to a string' do
164
- @o.as(:the_alias)
165
- @o.alias.should eq 'the_alias'
166
- end
167
-
168
- end
169
-
170
156
  end
171
157
  end
172
158
  end
@@ -37,7 +37,8 @@ end
37
37
 
38
38
  class PersonNamedBill < ActiveRecord::Base
39
39
  self.table_name = 'people'
40
- default_scope where(:name => 'Bill')
40
+ belongs_to :parent, :class_name => 'Person', :foreign_key => :parent_id
41
+ default_scope where{name == 'Bill'}
41
42
  end
42
43
 
43
44
  class Message < ActiveRecord::Base
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: 1.0.6
4
+ version: 1.0.7
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-06-16 00:00:00.000000000 Z
12
+ date: 2012-07-14 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
@@ -240,7 +240,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
240
240
  version: '0'
241
241
  segments:
242
242
  - 0
243
- hash: -4410929732049188226
243
+ hash: -1051407331683440462
244
244
  required_rubygems_version: !ruby/object:Gem::Requirement
245
245
  none: false
246
246
  requirements:
@@ -249,7 +249,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
249
249
  version: '0'
250
250
  segments:
251
251
  - 0
252
- hash: -4410929732049188226
252
+ hash: -1051407331683440462
253
253
  requirements: []
254
254
  rubyforge_project: squeel
255
255
  rubygems_version: 1.8.24