arel 0.3.3 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/lib/arel/algebra.rb +1 -0
  2. data/lib/arel/algebra/attributes.rb +1 -1
  3. data/lib/arel/algebra/attributes/attribute.rb +95 -7
  4. data/lib/arel/algebra/attributes/integer.rb +1 -1
  5. data/lib/arel/algebra/core_extensions/object.rb +0 -13
  6. data/lib/arel/algebra/header.rb +67 -0
  7. data/lib/arel/algebra/predicates.rb +153 -7
  8. data/lib/arel/algebra/relations/operations/having.rb +11 -7
  9. data/lib/arel/algebra/relations/operations/join.rb +1 -2
  10. data/lib/arel/algebra/relations/operations/project.rb +1 -1
  11. data/lib/arel/algebra/relations/relation.rb +13 -22
  12. data/lib/arel/algebra/relations/utilities/compound.rb +5 -1
  13. data/lib/arel/algebra/relations/utilities/externalization.rb +1 -1
  14. data/lib/arel/engines/memory/predicates.rb +58 -2
  15. data/lib/arel/engines/memory/relations/array.rb +6 -3
  16. data/lib/arel/engines/memory/relations/operations.rb +1 -1
  17. data/lib/arel/engines/sql/attributes.rb +1 -1
  18. data/lib/arel/engines/sql/core_extensions/array.rb +4 -0
  19. data/lib/arel/engines/sql/core_extensions/nil_class.rb +4 -0
  20. data/lib/arel/engines/sql/core_extensions/object.rb +4 -0
  21. data/lib/arel/engines/sql/core_extensions/range.rb +4 -0
  22. data/lib/arel/engines/sql/predicates.rb +48 -2
  23. data/lib/arel/engines/sql/primitives.rb +8 -0
  24. data/lib/arel/engines/sql/relations/compiler.rb +2 -2
  25. data/lib/arel/engines/sql/relations/operations/join.rb +1 -1
  26. data/lib/arel/engines/sql/relations/relation.rb +4 -0
  27. data/lib/arel/engines/sql/relations/table.rb +8 -4
  28. data/lib/arel/version.rb +1 -1
  29. data/spec/algebra/unit/relations/join_spec.rb +1 -2
  30. data/spec/algebra/unit/relations/table_spec.rb +1 -1
  31. data/spec/attributes/boolean_spec.rb +1 -1
  32. data/spec/attributes/float_spec.rb +1 -1
  33. data/spec/attributes/header_spec.rb +42 -0
  34. data/spec/attributes/integer_spec.rb +1 -1
  35. data/spec/attributes/string_spec.rb +1 -1
  36. data/spec/attributes/time_spec.rb +4 -2
  37. data/spec/engines/memory/integration/joins/cross_engine_spec.rb +3 -4
  38. data/spec/engines/sql/unit/predicates/in_spec.rb +23 -5
  39. data/spec/engines/sql/unit/predicates/noteq_spec.rb +75 -0
  40. data/spec/engines/sql/unit/primitives/attribute_spec.rb +0 -19
  41. data/spec/engines/sql/unit/relations/having_spec.rb +33 -0
  42. data/spec/relations/join_spec.rb +5 -3
  43. data/spec/relations/relation_spec.rb +2 -2
  44. data/spec/shared/relation_spec.rb +126 -13
  45. data/spec/support/check.rb +1 -1
  46. data/spec/support/connections/mysql_connection.rb +1 -1
  47. data/spec/support/connections/oracle_connection.rb +1 -1
  48. data/spec/support/connections/postgresql_connection.rb +1 -1
  49. data/spec/support/guards.rb +1 -1
  50. data/spec/support/matchers.rb +1 -1
  51. data/spec/support/matchers/be_like.rb +3 -3
  52. data/spec/support/matchers/have_rows.rb +1 -1
  53. data/spec/support/model.rb +6 -2
  54. metadata +7 -4
@@ -54,4 +54,4 @@ module Arel
54
54
  end
55
55
  end
56
56
  end
57
- end
57
+ end
@@ -116,4 +116,4 @@ module Arel
116
116
  end
117
117
  end
118
118
  end
119
- end
119
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ module Arel
4
+ describe "Header" do
5
+ before :all do
6
+ @relation = Model.build do |r|
7
+ r.attribute :id, Attributes::Integer
8
+ r.attribute :name, Attributes::String
9
+ r.attribute :age, Attributes::Integer
10
+ end
11
+
12
+ @other = Model.build do |r|
13
+ r.attribute :foo, Attributes::String
14
+ end
15
+
16
+ @subset = Model.build do |r|
17
+ r.attribute :id, Attributes::Integer
18
+ end
19
+ end
20
+
21
+ describe "attribute lookup" do
22
+ it "finds attributes by name" do
23
+ @relation.attributes[:name].should == Attributes::String.new(@relation, :name)
24
+ end
25
+
26
+ it "returns nil if no attribute is found" do
27
+ @relation.attributes[:does_not_exist].should be_nil
28
+ @relation[:does_not_exist].should be_nil
29
+ end
30
+ end
31
+
32
+ describe "#union" do
33
+ it "keeps all attributes from disjoint headers" do
34
+ (@relation.attributes.union @other.attributes).to_ary.should have(4).items
35
+ end
36
+
37
+ it "keeps all attributes from both relations even if they seem like subsets" do
38
+ (@relation.attributes.union @subset.attributes).to_ary.should have(4).items
39
+ end
40
+ end
41
+ end
42
+ end
@@ -116,4 +116,4 @@ module Arel
116
116
  end
117
117
  end
118
118
  end
119
- end
119
+ end
@@ -40,4 +40,4 @@ module Arel
40
40
  end
41
41
  end
42
42
  end
43
- end
43
+ end
@@ -16,7 +16,9 @@ module Arel
16
16
  end
17
17
 
18
18
  describe "#type_cast" do
19
- it "works"
19
+ it "works" do
20
+ pending
21
+ end
20
22
  end
21
23
  end
22
- end
24
+ end
@@ -13,10 +13,9 @@ module Arel
13
13
  @photos.insert(@photos[:id] => 1, @photos[:user_id] => 1, @photos[:camera_id] => 6)
14
14
  @photos.insert(@photos[:id] => 2, @photos[:user_id] => 2, @photos[:camera_id] => 42)
15
15
  # Oracle adapter returns database integers as Ruby integers and not strings
16
- @adapter_returns_integer = false
17
- adapter_is :oracle do
18
- @adapter_returns_integer = true
19
- end
16
+ # So does the FFI sqlite library
17
+ db_int_return = @photos.project(@photos[:camera_id]).first.tuple.first
18
+ @adapter_returns_integer = db_int_return.is_a?(String) ? false : true
20
19
  end
21
20
 
22
21
  describe 'when the in memory relation is on the left' do
@@ -100,6 +100,28 @@ module Arel
100
100
  end
101
101
  end
102
102
 
103
+ describe 'when relating to a range with an excluded end' do
104
+ before do
105
+ @range = 1...3
106
+ end
107
+
108
+ it 'manufactures sql with a >= and <' do
109
+ sql = In.new(@attribute, @range).to_sql
110
+
111
+ adapter_is :mysql do
112
+ sql.should be_like(%Q{(`users`.`id` >= 1 AND `users`.`id` < 3)})
113
+ end
114
+
115
+ adapter_is :oracle do
116
+ sql.should be_like(%Q{("USERS"."ID" >= 1 AND "USERS"."ID" < 3)})
117
+ end
118
+
119
+ adapter_is_not :mysql, :oracle do
120
+ sql.should be_like(%Q{("users"."id" >= 1 AND "users"."id" < 3)})
121
+ end
122
+ end
123
+ end
124
+
103
125
  describe 'when relating to a time range' do
104
126
  before do
105
127
  @relation = Arel::Table.new(:developers)
@@ -115,11 +137,7 @@ module Arel
115
137
  end
116
138
 
117
139
  adapter_is :sqlite3 do
118
- if RUBY_VERSION < '1.9'
119
- sql.should be_like(%Q{"developers"."created_at" BETWEEN '2010-01-01 00:00:00.000000' AND '2010-02-01 00:00:00.000000'})
120
- else
121
- sql.should be_like(%Q{"developers"."created_at" BETWEEN '2010-01-01 00:00:00' AND '2010-02-01 00:00:00'})
122
- end
140
+ sql.should match(/"developers"."created_at" BETWEEN '2010-01-01 00:00:00(?:\.\d+)' AND '2010-02-01 00:00:00(?:\.\d+)'/)
123
141
  end
124
142
 
125
143
  adapter_is :postgresql do
@@ -0,0 +1,75 @@
1
+ require 'spec_helper'
2
+
3
+ module Arel
4
+ module Predicates
5
+ describe Equality do
6
+ before do
7
+ @relation1 = Arel::Table.new(:users)
8
+ @relation2 = Arel::Table.new(:photos)
9
+ @attribute1 = @relation1[:id]
10
+ @attribute2 = @relation2[:user_id]
11
+ end
12
+
13
+ describe '#to_sql' do
14
+ describe 'when relating to a non-nil value' do
15
+ it "manufactures a not predicate" do
16
+ sql = Inequality.new(@attribute1, @attribute2).to_sql
17
+
18
+ adapter_is :mysql do
19
+ sql.should be_like(%Q{`users`.`id` != `photos`.`user_id`})
20
+ end
21
+
22
+ adapter_is :oracle do
23
+ sql.should be_like(%Q{"USERS"."ID" != "PHOTOS"."USER_ID"})
24
+ end
25
+
26
+ adapter_is_not :mysql, :oracle do
27
+ sql.should be_like(%Q{"users"."id" != "photos"."user_id"})
28
+ end
29
+ end
30
+ end
31
+
32
+ describe 'when relation to a nil value' do
33
+ before do
34
+ @nil = nil
35
+ end
36
+
37
+ it "manufactures an is null predicate" do
38
+ sql = Inequality.new(@attribute1, @nil).to_sql
39
+
40
+ adapter_is :mysql do
41
+ sql.should be_like(%Q{`users`.`id` IS NOT NULL})
42
+ end
43
+
44
+ adapter_is :oracle do
45
+ sql.should be_like(%Q{"USERS"."ID" IS NOT NULL})
46
+ end
47
+
48
+ adapter_is_not :mysql, :oracle do
49
+ sql.should be_like(%Q{"users"."id" IS NOT NULL})
50
+ end
51
+ end
52
+ end
53
+
54
+ describe "when relating to a nil Value" do
55
+ it "manufactures an IS NULL predicate" do
56
+ value = nil.bind(@relation1)
57
+ sql = Inequality.new(@attribute1, value).to_sql
58
+
59
+ adapter_is :mysql do
60
+ sql.should be_like(%Q{`users`.`id` IS NOT NULL})
61
+ end
62
+
63
+ adapter_is :oracle do
64
+ sql.should be_like(%Q{"USERS"."ID" IS NOT NULL})
65
+ end
66
+
67
+ adapter_is_not :mysql, :oracle do
68
+ sql.should be_like(%Q{"users"."id" IS NOT NULL})
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -31,25 +31,6 @@ module Arel
31
31
  end
32
32
  end
33
33
  end
34
-
35
- describe 'for an inexistent attribute' do
36
- it "manufactures sql" do
37
- sql = @relation[:does_not_exist].to_sql
38
-
39
- adapter_is :mysql do
40
- sql.should be_like(%Q{`users`.`does_not_exist`})
41
- end
42
-
43
- adapter_is :oracle do
44
- sql.should be_like(%Q{"USERS"."DOEST_NOT_EXIST"})
45
- end
46
-
47
- adapter_is_not :mysql, :oracle do
48
- sql.should be_like(%Q{"users"."does_not_exist"})
49
- end
50
- end
51
- end
52
-
53
34
  end
54
35
  end
55
36
  end
@@ -39,6 +39,39 @@ module Arel
39
39
  end
40
40
  end
41
41
  end
42
+
43
+ describe 'when given two predicates' do
44
+ it "manufactures sql with where clause conditions joined by AND" do
45
+ sql = @relation.group(@relation[:department]).having("MIN(salary) > 1000", "MAX(salary) < 10000").to_sql
46
+
47
+ adapter_is :mysql do
48
+ sql.should be_like(%Q{
49
+ SELECT `developers`.`id`, `developers`.`name`, `developers`.`salary`, `developers`.`department`, `developers`.`created_at`
50
+ FROM `developers`
51
+ GROUP BY `developers`.`department`
52
+ HAVING MIN(salary) > 1000 AND MAX(salary) < 10000
53
+ })
54
+ end
55
+
56
+ adapter_is :oracle do
57
+ sql.should be_like(%Q{
58
+ SELECT "DEVELOPERS"."ID", "DEVELOPERS"."NAME", "DEVELOPERS"."SALARY", "DEVELOPERS"."DEPARTMENT", "DEVELOPERS"."CREATED_AT"
59
+ FROM "DEVELOPERS"
60
+ GROUP BY "DEVELOPERS"."DEPARTMENT"
61
+ HAVING MIN(salary) > 1000 AND MAX(salary) < 10000
62
+ })
63
+ end
64
+
65
+ adapter_is_not :mysql, :oracle do
66
+ sql.should be_like(%Q{
67
+ SELECT "developers"."id", "developers"."name", "developers"."salary", "developers"."department", "developers"."created_at"
68
+ FROM "developers"
69
+ GROUP BY "developers"."department"
70
+ HAVING MIN(salary) > 1000 AND MAX(salary) < 10000
71
+ })
72
+ end
73
+ end
74
+ end
42
75
  end
43
76
  end
44
77
  end
@@ -13,6 +13,7 @@ describe "Arel" do
13
13
 
14
14
  r.attribute :id, Arel::Attributes::Integer
15
15
  r.attribute :owner_id, Arel::Attributes::Integer
16
+ r.attribute :name, Arel::Attributes::String
16
17
  r.attribute :age, Arel::Attributes::Integer
17
18
  end
18
19
  end
@@ -28,13 +29,14 @@ describe "Arel" do
28
29
  8.times do |i|
29
30
  thing_id = owner_id * 8 + i
30
31
  age = 2 * thing_id
32
+ name = "Name #{thing_id % 6}"
31
33
 
32
- @thing.insert([thing_id, owner_id, age])
33
- @expected << Arel::Row.new(@relation, [thing_id, owner_id, age, owner_id])
34
+ @thing.insert([thing_id, owner_id, name, age])
35
+ @expected << Arel::Row.new(@relation, [thing_id, owner_id, name, age, owner_id])
34
36
  end
35
37
  end
36
38
  end
37
39
 
38
40
  it_should_behave_like 'A Relation'
39
41
  end
40
- end
42
+ end
@@ -14,7 +14,7 @@ describe "Arel" do
14
14
 
15
15
  describe "Relation" do
16
16
  before :all do
17
- @expected = (1..20).map { |i| @relation.insert([i, nil, 2 * i]) }
17
+ @expected = (1..20).map { |i| @relation.insert([i, "Name #{i % 6}", 2 * i]) }
18
18
  end
19
19
 
20
20
  it_should_behave_like 'A Relation'
@@ -28,4 +28,4 @@ describe "Arel" do
28
28
  end
29
29
  end
30
30
  end
31
- end
31
+ end
@@ -35,9 +35,19 @@ share_examples_for 'A Relation' do
35
35
  @relation.where(@relation[:age].eq(@pivot[@relation[:age]])).should have_rows(expected)
36
36
  end
37
37
 
38
- it "finds rows with a not predicate" do
38
+ it "finds rows with an equal to complement predicate" do
39
39
  expected = @expected.select { |r| r[@relation[:age]] != @pivot[@relation[:age]] }
40
- @relation.where(@relation[:age].not(@pivot[@relation[:age]])).should have_rows(expected)
40
+ @relation.where(@relation[:age].eq(@pivot[@relation[:age]]).complement).should have_rows(expected)
41
+ end
42
+
43
+ it "finds rows with a not eq predicate" do
44
+ expected = @expected.select { |r| r[@relation[:age]] != @pivot[@relation[:age]] }
45
+ @relation.where(@relation[:age].not_eq(@pivot[@relation[:age]])).should have_rows(expected)
46
+ end
47
+
48
+ it "finds rows with an not eq complement predicate" do
49
+ expected = @expected.select { |r| r[@relation[:age]] == @pivot[@relation[:age]] }
50
+ @relation.where(@relation[:age].not_eq(@pivot[@relation[:age]]).complement).should have_rows(expected)
41
51
  end
42
52
 
43
53
  it "finds rows with a less than predicate" do
@@ -45,52 +55,155 @@ share_examples_for 'A Relation' do
45
55
  @relation.where(@relation[:age].lt(@pivot[@relation[:age]])).should have_rows(expected)
46
56
  end
47
57
 
58
+ it "finds rows with a less than complement predicate" do
59
+ expected = @expected.select { |r| r[@relation[:age]] >= @pivot[@relation[:age]] }
60
+ @relation.where(@relation[:age].lt(@pivot[@relation[:age]]).complement).should have_rows(expected)
61
+ end
62
+
48
63
  it "finds rows with a less than or equal to predicate" do
49
64
  expected = @expected.select { |r| r[@relation[:age]] <= @pivot[@relation[:age]] }
50
65
  @relation.where(@relation[:age].lteq(@pivot[@relation[:age]])).should have_rows(expected)
51
66
  end
52
67
 
68
+ it "finds rows with a less than or equal to complement predicate" do
69
+ expected = @expected.select { |r| r[@relation[:age]] > @pivot[@relation[:age]] }
70
+ @relation.where(@relation[:age].lteq(@pivot[@relation[:age]]).complement).should have_rows(expected)
71
+ end
72
+
53
73
  it "finds rows with a greater than predicate" do
54
74
  expected = @expected.select { |r| r[@relation[:age]] > @pivot[@relation[:age]] }
55
75
  @relation.where(@relation[:age].gt(@pivot[@relation[:age]])).should have_rows(expected)
56
76
  end
57
77
 
78
+ it "finds rows with a greater than complement predicate" do
79
+ expected = @expected.select { |r| r[@relation[:age]] <= @pivot[@relation[:age]] }
80
+ @relation.where(@relation[:age].gt(@pivot[@relation[:age]]).complement).should have_rows(expected)
81
+ end
82
+
58
83
  it "finds rows with a greater than or equal to predicate" do
59
84
  expected = @expected.select { |r| r[@relation[:age]] >= @pivot[@relation[:age]] }
60
85
  @relation.where(@relation[:age].gteq(@pivot[@relation[:age]])).should have_rows(expected)
61
86
  end
62
87
 
63
- it "finds rows with a matches predicate"
88
+ it "finds rows with a greater than or equal to complement predicate" do
89
+ expected = @expected.select { |r| r[@relation[:age]] < @pivot[@relation[:age]] }
90
+ @relation.where(@relation[:age].gteq(@pivot[@relation[:age]]).complement).should have_rows(expected)
91
+ end
92
+
93
+ it "finds rows with a matches predicate" do
94
+ expected = @expected.select { |r| r[@relation[:name]] =~ /#{@pivot[@relation[:name]]}/ }
95
+ @relation.where(@relation[:name].matches(/#{@pivot[@relation[:name]]}/)).should have_rows(expected)
96
+ end
97
+
98
+ it "finds rows with a matches complement predicate" do
99
+ expected = @expected.select { |r| r[@relation[:name]] !~ /#{@pivot[@relation[:name]]}/ }
100
+ @relation.where(@relation[:name].matches(/#{@pivot[@relation[:name]]}/).complement).should have_rows(expected)
101
+ end
102
+
103
+ it "finds rows with a not matches predicate" do
104
+ expected = @expected.select { |r| r[@relation[:name]] !~ /#{@pivot[@relation[:name]]}/ }
105
+ @relation.where(@relation[:name].not_matches(/#{@pivot[@relation[:name]]}/)).should have_rows(expected)
106
+ end
107
+
108
+ it "finds rows with a not matches complement predicate" do
109
+ expected = @expected.select { |r| r[@relation[:name]] =~ /#{@pivot[@relation[:name]]}/ }
110
+ @relation.where(@relation[:name].not_matches(/#{@pivot[@relation[:name]]}/).complement).should have_rows(expected)
111
+ end
64
112
 
65
113
  it "finds rows with an in predicate" do
66
- pending
67
- set = @expected[1..(@expected.length/2+1)]
68
- @relation.all(:id.in => set.map { |r| r.id }).should have_resources(set)
114
+ expected = @expected.select {|r| r[@relation[:age]] >=3 && r[@relation[:age]] <= 20}
115
+ @relation.where(@relation[:age].in(3..20)).should have_rows(expected)
116
+ end
117
+
118
+ it "finds rows with an in complement predicate" do
119
+ expected = @expected.select {|r| !(r[@relation[:age]] >=3 && r[@relation[:age]] <= 20)}
120
+ @relation.where(@relation[:age].in(3..20).complement).should have_rows(expected)
121
+ end
122
+
123
+ it "finds rows with a not in predicate" do
124
+ expected = @expected.select {|r| !(r[@relation[:age]] >=3 && r[@relation[:age]] <= 20)}
125
+ @relation.where(@relation[:age].not_in(3..20)).should have_rows(expected)
126
+ end
127
+
128
+ it "finds rows with a not in complement predicate" do
129
+ expected = @expected.select {|r| r[@relation[:age]] >=3 && r[@relation[:age]] <= 20}
130
+ @relation.where(@relation[:age].not_in(3..20).complement).should have_rows(expected)
131
+ end
132
+
133
+ it "finds rows with a polyadic predicate of class Any" do
134
+ expected = @expected.select {|r| [2,4,8,16].include?(r[@relation[:age]])}
135
+ @relation.where(@relation[:age].in_any([2,4], [8, 16])).should have_rows(expected)
136
+ end
137
+
138
+ it "finds rows with a polyadic predicate of class Any complement" do
139
+ expected = @expected.select {|r| ![2,4,8,16].include?(r[@relation[:age]])}
140
+ @relation.where(@relation[:age].in_any([2,4], [8, 16]).complement).should have_rows(expected)
141
+ end
142
+
143
+ it "finds rows with a polyadic predicate of class All" do
144
+ expected = @expected.select {|r| r[@relation[:name]] =~ /Name/ && r[@relation[:name]] =~ /1/}
145
+ @relation.where(@relation[:name].matches_all(/Name/, /1/)).should have_rows(expected)
146
+ end
147
+
148
+ it "finds rows with a polyadic predicate of class All complement" do
149
+ expected = @expected.select {|r| !(r[@relation[:name]] =~ /Name/ && r[@relation[:name]] =~ /1/)}
150
+ @relation.where(@relation[:name].matches_all(/Name/, /1/).complement).should have_rows(expected)
69
151
  end
70
152
  end
71
153
 
72
154
  describe "#order" do
73
155
  describe "by one attribute" do
74
156
  before :all do
75
- @expected.map! { |r| r[@relation[:age]] }
76
- @expected.sort!
157
+ @expected.sort! { |a, b| a[@relation[:age]] <=> b[@relation[:age]]}.map! {|e| e[@relation[:id]]}
77
158
  end
78
159
 
79
160
  it "can be specified as ascending order" do
80
161
  actual = []
81
- @relation.order(@relation[:age].asc).each { |r| actual << r[@relation[:age]] }
162
+ @relation.order(@relation[:age].asc).each { |r| actual << r[@relation[:id]] }
82
163
  actual.should == @expected
83
164
  end
84
165
 
85
166
  it "can be specified as descending order" do
86
167
  actual = []
87
- @relation.order(@relation[:age].desc).each { |r| actual << r[@relation[:age]] }
168
+ @relation.order(@relation[:age].desc).each { |r| actual << r[@relation[:id]] }
88
169
  actual.should == @expected.reverse
89
170
  end
90
171
  end
91
172
 
92
- describe "by two attributes" do
93
- it "works"
173
+ describe "by two attributes in two separate calls to #order" do
174
+ before :all do
175
+ @expected = @expected.sort_by { |e| [e[@relation[:name]], e[@relation[:age]]]}.map {|e| e[@relation[:id]]}
176
+ end
177
+
178
+ it "can be specified as ascending order" do
179
+ actual = []
180
+ @relation.order(@relation[:age].asc).order(@relation[:name].asc).each { |r| actual << r[@relation[:id]] }
181
+ actual.should == @expected
182
+ end
183
+
184
+ it "can be specified as descending order" do
185
+ actual = []
186
+ @relation.order(@relation[:age].desc).order(@relation[:name].desc).each { |r| actual << r[@relation[:id]] }
187
+ actual.should == @expected.reverse
188
+ end
189
+ end
190
+
191
+ describe "by two attributes in one call to #order" do
192
+ before :all do
193
+ @expected = @expected.sort_by { |e| [e[@relation[:name]], e[@relation[:age]]]}.map {|e| e[@relation[:id]]}
194
+ end
195
+
196
+ it "can be specified as ascending order in one call to #order" do
197
+ actual = []
198
+ @relation.order(@relation[:name].asc, @relation[:age].asc).each { |r| actual << r[@relation[:id]] }
199
+ actual.should == @expected
200
+ end
201
+
202
+ it "can be specified as descending order in one call to #order" do
203
+ actual = []
204
+ @relation.order(@relation[:name].desc, @relation[:age].desc).each { |r| actual << r[@relation[:id]] }
205
+ actual.should == @expected.reverse
206
+ end
94
207
  end
95
208
  end
96
209
 
@@ -139,4 +252,4 @@ share_examples_for 'A Relation' do
139
252
  actual.should == expected[3..-1]
140
253
  end
141
254
  end
142
- end
255
+ end