squeel 1.1.0 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.