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.
- data/CHANGELOG.md +8 -0
- data/README.md +15 -3
- data/lib/squeel/adapters/active_record/3.0/compat.rb +11 -0
- data/lib/squeel/adapters/active_record/4.0/relation_extensions.rb +15 -13
- data/lib/squeel/adapters/active_record/relation_extensions.rb +2 -0
- data/lib/squeel/version.rb +1 -1
- data/lib/squeel/visitors/predicate_visitation.rb +11 -0
- data/lib/squeel/visitors/predicate_visitor.rb +32 -0
- data/lib/squeel/visitors/visitor.rb +9 -0
- data/spec/squeel/adapters/active_record/relation_extensions_spec.rb +59 -5
- data/spec/support/models.rb +1 -4
- metadata +5 -5
data/CHANGELOG.md
CHANGED
@@ -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(
|
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
|
data/lib/squeel/version.rb
CHANGED
@@ -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
|
-
|
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
|
data/spec/support/models.rb
CHANGED
@@ -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
|
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.
|
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-
|
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:
|
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:
|
274
|
+
hash: 2299455513016806786
|
275
275
|
requirements: []
|
276
276
|
rubyforge_project: squeel
|
277
|
-
rubygems_version: 1.8.
|
277
|
+
rubygems_version: 1.8.23
|
278
278
|
signing_key:
|
279
279
|
specification_version: 3
|
280
280
|
summary: Active Record 3, improved.
|