squeel 1.0.6 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,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