squeel 1.1.0 → 1.1.1

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,11 @@
1
+ ## 1.1.1 (2013-09-03)
2
+
3
+ * Update relation extensions to support new count behavior in Active Record
4
+ 4.0.1 (see rails/rails@da9b5d4a)
5
+ * Support two-argument version of Relation#from in AR4
6
+ * Allow equality/inequality conditions against the name of a belongs_to
7
+ association with an AR::Base value object. Fixes issue #265.
8
+
1
9
  ## 1.1.0 (2013-07-14)
2
10
 
3
11
  * Support for Active Record 4.0.0!
data/README.md CHANGED
@@ -205,7 +205,7 @@ Person.where{name == 'Joe Blow'}
205
205
  Not a very exciting example since equality is handled just fine via the
206
206
  first example in standard Active Record. But consider the following query:
207
207
 
208
- ```
208
+ ```sql
209
209
  SELECT "people".* FROM people
210
210
  WHERE ("people"."name" LIKE 'Ernie%' AND "people"."salary" < 50000)
211
211
  OR ("people"."name" LIKE 'Joe%' AND "people"."salary" > 100000)
@@ -467,6 +467,18 @@ p.flanderized_name
467
467
 
468
468
  As you can see, just like functions, these operations can be given aliases.
469
469
 
470
+ To select more than one attribute (or calculated attribute) simply put them into an array:
471
+
472
+ ```ruby
473
+ p = Person.select{[ name.op('||', '-diddly').as(flanderized_name),
474
+ coalesce(name, '<no name given>').as(name_with_default) ]}.first
475
+ p.flanderized_name
476
+ # => "Aric Smith-diddly"
477
+ p.name_with_default
478
+ # => "Aric Smith"
479
+ ```
480
+
481
+
470
482
  ## Compatibility with Active Record
471
483
 
472
484
  Most of the new functionality provided by Squeel is accessed with the new block-style `where{}`
@@ -493,13 +505,13 @@ Person.where(:first_name => :last_name)
493
505
 
494
506
  produces this SQL query in plain Active Record:
495
507
 
496
- ```
508
+ ```sql
497
509
  SELECT people.* FROM people WHERE people.first_name = 'last_name'.
498
510
  ```
499
511
 
500
512
  but produces this SQL query if you are using Squeel:
501
513
 
502
- ```
514
+ ```sql
503
515
  SELECT people.* FROM people WHERE people.first_name = people.last_name
504
516
  ```
505
517
 
@@ -196,3 +196,14 @@ module Arel
196
196
  end
197
197
 
198
198
  end
199
+
200
+ module ActiveRecord
201
+ module Reflection
202
+ class AssociationReflection < MacroReflection
203
+ alias :foreign_key :primary_key_name
204
+ def foreign_type
205
+ options[:foreign_type]
206
+ end
207
+ end
208
+ end
209
+ end
@@ -49,12 +49,26 @@ module Squeel
49
49
  build_select(arel, select_visit(select_values.uniq))
50
50
 
51
51
  arel.distinct(distinct_value)
52
- arel.from(from_visit(from_value)) if from_value
52
+ arel.from(build_from) if from_value
53
53
  arel.lock(lock_value) if lock_value
54
54
 
55
55
  arel
56
56
  end
57
57
 
58
+ def build_from
59
+ opts, name = from_visit(from_value)
60
+ case opts
61
+ when ::ActiveRecord::Relation
62
+ name ||= 'subquery'
63
+ opts.arel.as(name.to_s)
64
+ when ::Arel::SelectManager
65
+ name ||= 'subquery'
66
+ opts.as(name.to_s)
67
+ else
68
+ opts
69
+ end
70
+ end
71
+
58
72
  def build_order(arel)
59
73
  orders = order_visit(dehashified_order_values)
60
74
  orders = reverse_sql_order(attrs_to_orderings(orders)) if reverse_order_value
@@ -73,17 +87,6 @@ module Squeel
73
87
  arel.order(*orders) unless orders.empty?
74
88
  end
75
89
 
76
- def build_from
77
- opts, name = from_value
78
- case opts
79
- when Relation
80
- name ||= 'subquery'
81
- opts.arel.as(name.to_s)
82
- else
83
- opts
84
- end
85
- end
86
-
87
90
  # This is copied directly from 4.0.0's implementation, but adds an extra
88
91
  # exclusion for Squeel::Nodes::Node to fix #248. Can be removed if/when
89
92
  # rails/rails#11439 is merged.
@@ -117,7 +120,6 @@ module Squeel
117
120
  end
118
121
  }
119
122
  end
120
-
121
123
  end
122
124
  end
123
125
  end
@@ -99,6 +99,8 @@ module Squeel
99
99
  end
100
100
 
101
101
  str_select if str_select && str_select !~ /[,*]/
102
+ else
103
+ :all
102
104
  end
103
105
  end
104
106
 
@@ -1,3 +1,3 @@
1
1
  module Squeel
2
- VERSION = '1.1.0'
2
+ VERSION = '1.1.1'
3
3
  end
@@ -1,6 +1,7 @@
1
1
  module Squeel
2
2
  module Visitors
3
3
  module PredicateVisitation
4
+ EXPAND_BELONGS_TO_METHODS = [:eq, :not_eq]
4
5
 
5
6
  private
6
7
 
@@ -28,6 +29,16 @@ module Squeel
28
29
  def visit_Squeel_Nodes_Predicate(o, parent)
29
30
  value = o.value
30
31
 
32
+ # Short-circuit for stuff like `where{ author.eq User.first }`
33
+ # This filthy hack emulates similar behavior in AR PredicateBuilder
34
+ if ActiveRecord::Base === value &&
35
+ EXPAND_BELONGS_TO_METHODS.include?(o.method_name) &&
36
+ association = classify(parent).reflect_on_association(
37
+ symbolify(o.expr)
38
+ )
39
+ return expand_belongs_to(o, parent, association)
40
+ end
41
+
31
42
  case value
32
43
  when Nodes::KeyPath
33
44
  value = can_visit?(value.endpoint) ? visit(value, parent) : contextualize(traverse(value, parent))[value.endpoint.to_s]
@@ -8,6 +8,31 @@ module Squeel
8
8
 
9
9
  private
10
10
 
11
+ # Expand a belongs_to association that has an AR::Base value. This allows
12
+ # for queries like:
13
+ #
14
+ # Post.where(:author => User.first)
15
+ # Post.where{author.eq User.first}
16
+ #
17
+ # @param [Squeel::Nodes::Predicate] o A predicate node (eq/not_eq)
18
+ # @param parent The current parent object in the context
19
+ # @return [Arel::Nodes::Node] An Arel predicate node
20
+ def expand_belongs_to(o, parent, association)
21
+ context = contextualize(parent)
22
+ ar_base = o.value
23
+ conditions = [
24
+ context[association.foreign_key.to_s].send(o.method_name, ar_base.id)
25
+ ]
26
+ if association.options[:polymorphic]
27
+ conditions << [
28
+ context[association.foreign_type].send(
29
+ o.method_name, ar_base.class.base_class.name
30
+ )
31
+ ]
32
+ end
33
+ conditions.inject(o.method_name == :not_eq ? :or : :and)
34
+ end
35
+
11
36
  # Visit a Hash. This entails iterating through each key and value and
12
37
  # visiting each value in turn.
13
38
  #
@@ -47,6 +72,13 @@ module Squeel
47
72
  # @param parent The current parent object in the context
48
73
  # @return An Arel predicate
49
74
  def visit_without_hash_context_shift(k, v, parent)
75
+ # Short-circuit for stuff like `where(:author => User.first)`
76
+ # This filthy hack emulates similar behavior in AR PredicateBuilder
77
+ if ActiveRecord::Base === v &&
78
+ association = classify(parent).reflect_on_association(k.to_sym)
79
+ return expand_belongs_to(Nodes::Predicate.new(k, :eq, v), parent, association)
80
+ end
81
+
50
82
  case v
51
83
  when Nodes::Stub, Symbol
52
84
  v = contextualize(parent)[v.to_s]
@@ -46,6 +46,15 @@ module Squeel
46
46
 
47
47
  private
48
48
 
49
+ def symbolify(o)
50
+ case o
51
+ when Symbol, String, Nodes::Stub
52
+ o.to_sym
53
+ else
54
+ nil
55
+ end
56
+ end
57
+
49
58
  # A hash that caches the method name to use for a visitor for a given class
50
59
  DISPATCH = Hash.new do |hash, klass|
51
60
  hash[klass] = "visit_#{(klass.name || '').gsub('::', '_')}"
@@ -389,6 +389,10 @@ module Squeel
389
389
  expect { people.first.name }.to raise_error ActiveModel::MissingAttributeError
390
390
  end
391
391
 
392
+ it 'works with multiple fields in select' do
393
+ Article.select("title, body").count.should eq 51
394
+ end
395
+
392
396
  it 'allows a function in the select values via Symbol#func' do
393
397
  relation = Person.select(:max.func(:id).as('max_id'))
394
398
  relation.first.max_id.should eq 332
@@ -421,11 +425,7 @@ module Squeel
421
425
  describe '#count' do
422
426
 
423
427
  it 'works with non-strings in select' do
424
- if activerecord_version_at_least '4.0.0'
425
- pending 'broken on current 4-0-stable'
426
- else
427
- Article.select{distinct(title)}.count.should eq 51
428
- end
428
+ Article.select{distinct(title)}.count.should eq 51
429
429
  end
430
430
 
431
431
  it 'works with non-strings in wheres' do
@@ -514,6 +514,42 @@ module Squeel
514
514
  end
515
515
  end
516
516
 
517
+ it 'allows equality conditions against a belongs_to with an AR::Base value' do
518
+ first_person = Person.first
519
+ relation = Article.where { person.eq first_person }
520
+ relation.to_sql.should match /"articles"."person_id" = #{first_person.id}/
521
+ end
522
+
523
+ it 'allows equality conditions against a polymorphic belongs_to with an AR::Base value' do
524
+ first_person = Person.first
525
+ relation = Note.where { notable.eq first_person }
526
+ relation.to_sql.should match /"notes"."notable_id" = #{first_person.id} AND "notes"."notable_type" = 'Person'/
527
+ end
528
+
529
+ it 'allows inequality conditions against a belongs_to with an AR::Base value' do
530
+ first_person = Person.first
531
+ relation = Article.where { person.not_eq first_person }
532
+ relation.to_sql.should match /"articles"."person_id" != #{first_person.id}/
533
+ end
534
+
535
+ it 'allows inequality conditions against a polymorphic belongs_to with an AR::Base value' do
536
+ first_person = Person.first
537
+ relation = Note.where { notable.not_eq first_person }
538
+ relation.to_sql.should match /\("notes"."notable_id" != #{first_person.id} OR "notes"."notable_type" != 'Person'\)/
539
+ end
540
+
541
+ it 'allows hash equality conditions against a belongs_to with an AR::Base value' do
542
+ first_person = Person.first
543
+ relation = Article.where(:person => first_person)
544
+ relation.to_sql.should match /"articles"."person_id" = #{first_person.id}/
545
+ end
546
+
547
+ it 'allows hash equality conditions against a polymorphic belongs_to with an AR::Base value' do
548
+ first_person = Person.first
549
+ relation = Note.where(:notable => first_person)
550
+ relation.to_sql.should match /"notes"."notable_id" = #{first_person.id} AND "notes"."notable_type" = 'Person'/
551
+ end
552
+
517
553
  end
518
554
 
519
555
  describe '#joins' do
@@ -643,6 +679,24 @@ module Squeel
643
679
  sql = block.to_sql
644
680
  sql.should match expected
645
681
  end
682
+
683
+ it 'creates froms from literals' do
684
+ expected = /SELECT "people".* FROM sub/
685
+ relation = Person.from('sub')
686
+ sql = relation.to_sql
687
+ sql.should match expected
688
+ end
689
+
690
+ it 'creates froms from relations' do
691
+ if activerecord_version_at_least '4.0.0'
692
+ expected = "SELECT \"people\".* FROM (SELECT \"people\".* FROM \"people\") alias"
693
+ relation = Person.from(Person.all, 'alias')
694
+ sql = relation.to_sql
695
+ sql.should == expected
696
+ else
697
+ pending 'Unsupported before ActiveRecord 4.0'
698
+ end
699
+ end
646
700
  end
647
701
 
648
702
  describe '#build_where' do
@@ -3,7 +3,7 @@ class Person < ActiveRecord::Base
3
3
  has_many :children, :class_name => 'Person', :foreign_key => :parent_id
4
4
  has_many :articles
5
5
  has_many :comments
6
- if ActiveRecord::VERSION::MAJOR == 4
6
+ if ActiveRecord::VERSION::MAJOR > 3
7
7
  has_many :articles_with_condition, lambda { where :title => 'Condition' },
8
8
  :class_name => 'Article'
9
9
  has_many :article_comments_with_first_post,
@@ -17,9 +17,6 @@ class Person < ActiveRecord::Base
17
17
  :through => :articles, :source => :comments
18
18
  end
19
19
  has_many :condition_article_comments, :through => :articles_with_condition, :source => :comments
20
- if ActiveRecord::VERSION::MAJOR == 4
21
- else
22
- end
23
20
  has_many :authored_article_comments, :through => :articles,
24
21
  :source => :comments
25
22
  has_many :notes, :as => :notable
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.1.0
4
+ version: 1.1.1
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: 2013-07-14 00:00:00.000000000 Z
12
+ date: 2013-09-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
@@ -262,7 +262,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
262
262
  version: '0'
263
263
  segments:
264
264
  - 0
265
- hash: 3013172818139711392
265
+ hash: 2299455513016806786
266
266
  required_rubygems_version: !ruby/object:Gem::Requirement
267
267
  none: false
268
268
  requirements:
@@ -271,10 +271,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
271
271
  version: '0'
272
272
  segments:
273
273
  - 0
274
- hash: 3013172818139711392
274
+ hash: 2299455513016806786
275
275
  requirements: []
276
276
  rubyforge_project: squeel
277
- rubygems_version: 1.8.25
277
+ rubygems_version: 1.8.23
278
278
  signing_key:
279
279
  specification_version: 3
280
280
  summary: Active Record 3, improved.