arel 0.3.3 → 0.4.0

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.
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