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