order_query 0.3.3 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGES.md +34 -0
- data/Gemfile +4 -3
- data/MIT-LICENSE +3 -1
- data/README.md +25 -13
- data/Rakefile +26 -16
- data/lib/order_query.rb +12 -3
- data/lib/order_query/column.rb +74 -25
- data/lib/order_query/direction.rb +9 -7
- data/lib/order_query/errors.rb +11 -0
- data/lib/order_query/nulls_direction.rb +53 -0
- data/lib/order_query/point.rb +26 -9
- data/lib/order_query/space.rb +18 -8
- data/lib/order_query/sql/column.rb +9 -5
- data/lib/order_query/sql/order_by.rb +85 -11
- data/lib/order_query/sql/where.rb +63 -28
- data/lib/order_query/version.rb +3 -1
- data/spec/gemfiles/rails_5_0.gemfile +21 -0
- data/spec/gemfiles/rails_5_1.gemfile +21 -0
- data/spec/gemfiles/rails_5_2.gemfile +21 -0
- data/spec/gemfiles/rails_6_0.gemfile +21 -0
- data/spec/gemfiles/rails_6_1.gemfile +21 -0
- data/spec/gemfiles/rubocop.gemfile +5 -0
- data/spec/order_query_spec.rb +260 -67
- data/spec/spec_helper.rb +32 -6
- data/spec/support/order_expectation.rb +48 -0
- metadata +48 -27
- data/spec/gemfiles/rails_4.gemfile +0 -9
- data/spec/gemfiles/rails_4.gemfile.lock +0 -73
- data/spec/gemfiles/rails_5.gemfile +0 -8
- data/spec/gemfiles/rails_5.gemfile.lock +0 -76
data/lib/order_query/version.rb
CHANGED
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source 'https://rubygems.org'
|
4
|
+
|
5
|
+
gemspec path: '../../'
|
6
|
+
|
7
|
+
gem 'activerecord', '~> 5.0.6'
|
8
|
+
gem 'activesupport', '~> 5.0.6'
|
9
|
+
|
10
|
+
platforms :mri, :rbx do
|
11
|
+
# https://github.com/rails/rails/blob/v5.0.6/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb#L4
|
12
|
+
gem 'mysql2', '< 0.5'
|
13
|
+
|
14
|
+
# https://github.com/rails/rails/blob/v5.0.6/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L2
|
15
|
+
gem 'pg', '~> 0.18'
|
16
|
+
|
17
|
+
# https://github.com/rails/rails/blob/v5.0.6/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb#L7
|
18
|
+
gem 'sqlite3', '~> 1.3.6'
|
19
|
+
end
|
20
|
+
|
21
|
+
eval_gemfile '../../shared.gemfile'
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source 'https://rubygems.org'
|
4
|
+
|
5
|
+
gemspec path: '../../'
|
6
|
+
|
7
|
+
gem 'activerecord', '~> 5.1.3'
|
8
|
+
gem 'activesupport', '~> 5.1.3'
|
9
|
+
|
10
|
+
platforms :mri, :rbx do
|
11
|
+
# https://github.com/rails/rails/blob/v5.1.5/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb#L4
|
12
|
+
gem 'mysql2', '< 0.5'
|
13
|
+
|
14
|
+
# https://github.com/rails/rails/blob/v5.1.5/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L2
|
15
|
+
gem 'pg', '>= 0.18', '< 2.0'
|
16
|
+
|
17
|
+
# https://github.com/rails/rails/blob/v5.1.5/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb#L10
|
18
|
+
gem 'sqlite3', '~> 1.3.6'
|
19
|
+
end
|
20
|
+
|
21
|
+
eval_gemfile '../../shared.gemfile'
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source 'https://rubygems.org'
|
4
|
+
|
5
|
+
gemspec path: '../../'
|
6
|
+
|
7
|
+
gem 'activerecord', '~> 5.2.3'
|
8
|
+
gem 'activesupport', '~> 5.2.3'
|
9
|
+
|
10
|
+
platforms :mri, :rbx do
|
11
|
+
# https://github.com/rails/rails/blob/v5.2.3/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb#L6
|
12
|
+
gem 'mysql2', '>= 0.4.4', '< 0.6.0'
|
13
|
+
|
14
|
+
# https://github.com/rails/rails/blob/v5.2.3/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L4
|
15
|
+
gem 'pg', '>= 0.18', '< 2.0'
|
16
|
+
|
17
|
+
# https://github.com/rails/rails/blob/v5.2.3/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb#L12
|
18
|
+
gem 'sqlite3', '~> 1.3', '>= 1.3.6'
|
19
|
+
end
|
20
|
+
|
21
|
+
eval_gemfile '../../shared.gemfile'
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source 'https://rubygems.org'
|
4
|
+
|
5
|
+
gemspec path: '../../'
|
6
|
+
|
7
|
+
gem 'activerecord', '~> 6.0.3'
|
8
|
+
gem 'activesupport', '~> 6.0.3'
|
9
|
+
|
10
|
+
platforms :mri, :rbx do
|
11
|
+
# https://github.com/rails/rails/blob/v6.0.0/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb#L13
|
12
|
+
gem 'sqlite3', '~> 1.4'
|
13
|
+
|
14
|
+
# https://github.com/rails/rails/blob/v6.0.0/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L4
|
15
|
+
gem 'pg', '>= 0.18', '< 2.0'
|
16
|
+
|
17
|
+
# https://github.com/rails/rails/blob/v6.0.0/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb#L6
|
18
|
+
gem 'mysql2', '>= 0.4.4'
|
19
|
+
end
|
20
|
+
|
21
|
+
eval_gemfile '../../shared.gemfile'
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source 'https://rubygems.org'
|
4
|
+
|
5
|
+
gemspec path: '../../'
|
6
|
+
|
7
|
+
gem 'activerecord', '~> 6.1.1'
|
8
|
+
gem 'activesupport', '~> 6.1.1'
|
9
|
+
|
10
|
+
platforms :mri, :rbx do
|
11
|
+
# https://github.com/rails/rails/blob/v6.0.0/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb#L13
|
12
|
+
gem 'sqlite3', '~> 1.4'
|
13
|
+
|
14
|
+
# https://github.com/rails/rails/blob/v6.0.0/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L4
|
15
|
+
gem 'pg', '>= 0.18', '< 2.0'
|
16
|
+
|
17
|
+
# https://github.com/rails/rails/blob/v6.0.0/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb#L6
|
18
|
+
gem 'mysql2', '>= 0.4.4'
|
19
|
+
end
|
20
|
+
|
21
|
+
eval_gemfile '../../shared.gemfile'
|
data/spec/order_query_spec.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
# Bare model
|
@@ -10,23 +12,23 @@ class Post < ActiveRecord::Base
|
|
10
12
|
include OrderQuery
|
11
13
|
order_query :order_list,
|
12
14
|
[:pinned, [true, false]],
|
13
|
-
[
|
14
|
-
[
|
15
|
+
%i[published_at desc],
|
16
|
+
%i[id desc]
|
15
17
|
end
|
16
18
|
|
17
19
|
def create_post(attr = {})
|
18
|
-
Post.create!({pinned: false, published_at: Time.now}.merge(attr))
|
20
|
+
Post.create!({ pinned: false, published_at: Time.now }.merge(attr))
|
19
21
|
end
|
20
22
|
|
21
23
|
# Advanced model
|
22
24
|
class Issue < ActiveRecord::Base
|
23
25
|
DISPLAY_ORDER = [
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
]
|
26
|
+
[:pinned, [true, false]],
|
27
|
+
[:priority, %w[high medium low]],
|
28
|
+
[:valid_votes_count, :desc, sql: '(votes - suspicious_votes)'],
|
29
|
+
%i[updated_at desc],
|
30
|
+
%i[id desc]
|
31
|
+
].freeze
|
30
32
|
|
31
33
|
def valid_votes_count
|
32
34
|
votes - suspicious_votes
|
@@ -34,11 +36,14 @@ class Issue < ActiveRecord::Base
|
|
34
36
|
|
35
37
|
include OrderQuery
|
36
38
|
order_query :display_order, DISPLAY_ORDER
|
37
|
-
order_query :id_order_asc, [[
|
39
|
+
order_query :id_order_asc, [%i[id asc]]
|
38
40
|
end
|
39
41
|
|
40
42
|
def create_issue(attr = {})
|
41
|
-
Issue.create!(
|
43
|
+
Issue.create!(
|
44
|
+
{ priority: 'high', votes: 3, suspicious_votes: 0, updated_at: Time.now }
|
45
|
+
.merge(attr)
|
46
|
+
)
|
42
47
|
end
|
43
48
|
|
44
49
|
def wrap_top_level_or(value)
|
@@ -54,53 +59,84 @@ def wrap_top_level_or(value)
|
|
54
59
|
end
|
55
60
|
end
|
56
61
|
|
57
|
-
describe 'OrderQuery' do
|
62
|
+
RSpec.describe 'OrderQuery' do
|
63
|
+
context 'Column' do
|
64
|
+
it 'fails with ArgumentError if invalid vals_and_or_dir is passed' do
|
65
|
+
expect do
|
66
|
+
OrderQuery::Column.new(Post.all, :pinned, :desc, :extra)
|
67
|
+
end.to raise_error(ArgumentError)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'Point' do
|
72
|
+
context '#value' do
|
73
|
+
it 'fails if nil on non-nullable column' do
|
74
|
+
post = OpenStruct.new
|
75
|
+
post.pinned = nil
|
76
|
+
space = Post.seek([:pinned])
|
77
|
+
expect do
|
78
|
+
OrderQuery::Point.new(post, space)
|
79
|
+
.value(space.columns.find { |c| c.name == :pinned })
|
80
|
+
end.to raise_error(OrderQuery::Errors::NonNullableColumnIsNullError)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
58
84
|
|
59
85
|
[false, true].each do |wrap_top_level_or|
|
60
86
|
context "(wtlo: #{wrap_top_level_or})" do
|
61
87
|
wrap_top_level_or wrap_top_level_or
|
62
88
|
|
63
89
|
context 'Issue test model' do
|
64
|
-
|
65
|
-
|
90
|
+
datasets = lambda {
|
91
|
+
t = Time.now
|
92
|
+
[
|
66
93
|
[
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
94
|
+
['high', 5, 0, t, true],
|
95
|
+
['high', 5, 1, t, true],
|
96
|
+
['high', 5, 0, t],
|
97
|
+
['high', 5, 0, t - 1.day],
|
98
|
+
['high', 5, 1, t],
|
99
|
+
['medium', 10, 0, t],
|
100
|
+
['medium', 10, 5, t - 12.hours],
|
101
|
+
['low', 30, 0, t + 1.day]
|
75
102
|
],
|
76
103
|
[
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
104
|
+
['high', 5, 0, t],
|
105
|
+
['high', 5, 1, t],
|
106
|
+
['high', 5, 1, t - 1.day],
|
107
|
+
['low', 30, 0, t + 1.day]
|
81
108
|
],
|
82
109
|
[
|
83
|
-
|
84
|
-
|
85
|
-
]
|
86
|
-
|
110
|
+
['high', 5, 1, t - 1.day],
|
111
|
+
['low', 30, 0, t + 1.day]
|
112
|
+
]
|
113
|
+
]
|
114
|
+
}.call
|
87
115
|
|
88
116
|
datasets.each_with_index do |ds, i|
|
89
117
|
it "is ordered correctly (test data #{i})" do
|
90
118
|
issues = ds.map do |attr|
|
91
|
-
Issue.new(priority: attr[0], votes: attr[1],
|
119
|
+
Issue.new(priority: attr[0], votes: attr[1],
|
120
|
+
suspicious_votes: attr[2], updated_at: attr[3],
|
121
|
+
pinned: attr[4] || false)
|
92
122
|
end
|
93
123
|
issues.shuffle.reverse_each(&:save!)
|
94
124
|
expect(Issue.display_order.to_a).to eq(issues)
|
95
125
|
expect(Issue.display_order_reverse.to_a).to eq(issues.reverse)
|
96
|
-
issues.zip(issues.rotate).each_with_index do |(cur, nxt),
|
97
|
-
expect(cur.display_order.position).to eq(
|
126
|
+
issues.zip(issues.rotate).each_with_index do |(cur, nxt), j|
|
127
|
+
expect(cur.display_order.position).to eq(j + 1)
|
98
128
|
expect(cur.display_order.next).to eq(nxt)
|
99
129
|
expect(Issue.display_order_at(cur).next).to eq nxt
|
100
130
|
expect(cur.display_order.space.count).to eq(Issue.count)
|
101
|
-
expect(
|
131
|
+
expect(
|
132
|
+
cur.display_order.before.count + 1 +
|
133
|
+
cur.display_order.after.count
|
134
|
+
).to eq(nxt.display_order.count)
|
102
135
|
expect(nxt.display_order.previous).to eq(cur)
|
103
|
-
expect(
|
136
|
+
expect(
|
137
|
+
nxt.display_order.before.to_a.reverse + [nxt] +
|
138
|
+
nxt.display_order.after.to_a
|
139
|
+
).to eq(Issue.display_order.to_a)
|
104
140
|
end
|
105
141
|
end
|
106
142
|
end
|
@@ -117,16 +153,20 @@ describe 'OrderQuery' do
|
|
117
153
|
expect(a.id_order_asc.next).to eq b
|
118
154
|
expect(b.id_order_asc.previous).to eq a
|
119
155
|
expect([a] + a.id_order_asc.after.to_a).to eq(Issue.id_order_asc.to_a)
|
120
|
-
expect(b.id_order_asc.before.reverse.to_a + [b]).to
|
156
|
+
expect(b.id_order_asc.before.reverse.to_a + [b]).to(
|
157
|
+
eq Issue.id_order_asc.to_a
|
158
|
+
)
|
121
159
|
expect(Issue.id_order_asc.count).to eq(2)
|
122
160
|
end
|
123
161
|
|
124
162
|
it '.seek works on a list of ids' do
|
125
|
-
ids = 3
|
163
|
+
ids = Array.new(3) { create_issue.id }
|
126
164
|
expect(Issue.seek([[:id, ids]]).count).to eq ids.length
|
127
165
|
expect(Issue.seek([:id, ids]).count).to eq ids.length
|
128
166
|
expect(Issue.seek([:id, ids]).scope.pluck(:id)).to eq ids
|
129
|
-
expect(Issue.seek([:id, ids]).scope_reverse.pluck(:id)).to
|
167
|
+
expect(Issue.seek([:id, ids]).scope_reverse.pluck(:id)).to(
|
168
|
+
eq(ids.reverse)
|
169
|
+
)
|
130
170
|
end
|
131
171
|
|
132
172
|
context 'partitioned on a boolean flag' do
|
@@ -136,7 +176,7 @@ describe 'OrderQuery' do
|
|
136
176
|
create_issue(active: true)
|
137
177
|
end
|
138
178
|
|
139
|
-
let!(:order) { [[
|
179
|
+
let!(:order) { [%i[id desc]] }
|
140
180
|
let!(:active) { Issue.where(active: true).seek(order) }
|
141
181
|
let!(:inactive) { Issue.where(active: false).seek(order) }
|
142
182
|
|
@@ -172,7 +212,38 @@ describe 'OrderQuery' do
|
|
172
212
|
it '#seek falls back to scope when order column is missing self' do
|
173
213
|
a = create_issue(priority: 'medium')
|
174
214
|
b = create_issue(priority: 'high')
|
175
|
-
expect(
|
215
|
+
expect(
|
216
|
+
a.seek(
|
217
|
+
Issue.display_order,
|
218
|
+
[[:priority, %w[wontfix askbob]], %i[id desc]]
|
219
|
+
).next
|
220
|
+
).to eq(b)
|
221
|
+
end
|
222
|
+
|
223
|
+
context 'nil in string enum' do
|
224
|
+
display = ->(issue) { "##{issue.id}-#{issue.priority || 'NULL'}" }
|
225
|
+
priorities = [nil, 'low', 'medium', 'high']
|
226
|
+
let!(:issues) do
|
227
|
+
priorities.flat_map do |p|
|
228
|
+
[create_issue(priority: p), create_issue(priority: p)]
|
229
|
+
end
|
230
|
+
end
|
231
|
+
priorities.permutation do |perm|
|
232
|
+
it "works for #{perm} (desc)" do
|
233
|
+
expect_order(
|
234
|
+
Issue.seek([:priority, perm]),
|
235
|
+
issues.sort_by { |x| [perm.index(x.priority), x.id] },
|
236
|
+
&display
|
237
|
+
)
|
238
|
+
end
|
239
|
+
it "works for #{perm} (asc)" do
|
240
|
+
expect_order(
|
241
|
+
Issue.seek([:priority, perm, :asc]),
|
242
|
+
issues.sort_by { |x| [perm.index(x.priority), -x.id] }.reverse,
|
243
|
+
&display
|
244
|
+
)
|
245
|
+
end
|
246
|
+
end
|
176
247
|
end
|
177
248
|
|
178
249
|
before do
|
@@ -217,28 +288,33 @@ describe 'OrderQuery' do
|
|
217
288
|
|
218
289
|
context '#inspect' do
|
219
290
|
it 'Column' do
|
220
|
-
expect(OrderQuery::Column.new(
|
221
|
-
|
291
|
+
expect(OrderQuery::Column.new(Post, :id, :desc).inspect)
|
292
|
+
.to eq '(id unique desc)'
|
293
|
+
expect(
|
294
|
+
OrderQuery::Column.new(Post, :virtual, :desc, sql: 'SIN(id)')
|
295
|
+
.inspect
|
296
|
+
).to eq '(virtual SIN(id) desc)'
|
222
297
|
end
|
223
298
|
|
224
|
-
let(:space)
|
299
|
+
let(:space) do
|
225
300
|
OrderQuery::Space.new(Post, [[:pinned, [true, false]]])
|
226
|
-
|
301
|
+
end
|
227
302
|
|
228
303
|
it 'Point' do
|
229
|
-
post
|
304
|
+
post = create_post
|
230
305
|
point = OrderQuery::Point.new(post, space)
|
231
|
-
|
232
|
-
|
233
|
-
|
306
|
+
# rubocop:disable Metrics/LineLength
|
307
|
+
expect(point.inspect).to eq %(#<OrderQuery::Point @record=#<Post id: #{post.id}, title: nil, pinned: false, published_at: #{post.attribute_for_inspect(:published_at)}> @space=#<OrderQuery::Space @columns=[(pinned [true, false] desc), (id unique asc)] @base_scope=Post(id: integer, title: string, pinned: boolean, published_at: datetime)>>)
|
308
|
+
# rubocop:enable Metrics/LineLength
|
234
309
|
end
|
235
310
|
|
236
311
|
it 'Space' do
|
237
|
-
|
312
|
+
# rubocop:disable Metrics/LineLength
|
313
|
+
expect(space.inspect).to eq '#<OrderQuery::Space @columns=[(pinned [true, false] desc), (id unique asc)] @base_scope=Post(id: integer, title: string, pinned: boolean, published_at: datetime)>'
|
314
|
+
# rubocop:enable Metrics/LineLength
|
238
315
|
end
|
239
316
|
end
|
240
317
|
|
241
|
-
|
242
318
|
context 'boolean enum order' do
|
243
319
|
before do
|
244
320
|
create_post pinned: true
|
@@ -248,29 +324,141 @@ describe 'OrderQuery' do
|
|
248
324
|
Post.delete_all
|
249
325
|
end
|
250
326
|
it 'ORDER BY is collapsed' do
|
251
|
-
expect(Post.seek([:pinned, [true, false]]).scope.to_sql).to
|
327
|
+
expect(Post.seek([:pinned, [true, false]]).scope.to_sql).to(
|
328
|
+
match(/ORDER BY .posts.\..pinned. DESC/)
|
329
|
+
)
|
252
330
|
end
|
253
331
|
it 'enum asc' do
|
254
|
-
expect(
|
255
|
-
|
332
|
+
expect(
|
333
|
+
Post.seek([:pinned, [false, true], :asc]).scope.pluck(:pinned)
|
334
|
+
).to eq([true, false])
|
335
|
+
expect(
|
336
|
+
Post.seek([:pinned, [true, false], :asc]).scope.pluck(:pinned)
|
337
|
+
).to eq([false, true])
|
256
338
|
end
|
257
339
|
it 'enum desc' do
|
258
|
-
expect(
|
259
|
-
|
340
|
+
expect(
|
341
|
+
Post.seek([:pinned, [false, true], :desc]).scope.pluck(:pinned)
|
342
|
+
).to eq([false, true])
|
343
|
+
expect(
|
344
|
+
Post.seek([:pinned, [true, false], :desc]).scope.pluck(:pinned)
|
345
|
+
).to eq([true, false])
|
260
346
|
end
|
261
347
|
end
|
262
348
|
|
263
|
-
|
349
|
+
context 'nil in boolean enum' do
|
350
|
+
display = ->(post) { "##{post.id}-#{post.pinned || 'NULL'}" }
|
264
351
|
states = [nil, false, true]
|
265
|
-
let!(:posts)
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
it "
|
272
|
-
|
273
|
-
|
352
|
+
let!(:posts) do
|
353
|
+
states.flat_map do |state|
|
354
|
+
[create_post(pinned: state), create_post(pinned: state)]
|
355
|
+
end
|
356
|
+
end
|
357
|
+
states.permutation do |perm|
|
358
|
+
it "works for #{perm} (desc)" do
|
359
|
+
expect_order(
|
360
|
+
Post.seek([:pinned, perm]),
|
361
|
+
posts.sort_by { |x| [perm.index(x.pinned), x.id] },
|
362
|
+
&display
|
363
|
+
)
|
364
|
+
end
|
365
|
+
it "works for #{perm} (asc)" do
|
366
|
+
expect_order(
|
367
|
+
Post.seek([:pinned, perm, :asc]),
|
368
|
+
posts.sort_by { |x| [-perm.index(x.pinned), x.id] },
|
369
|
+
&display
|
370
|
+
)
|
371
|
+
end
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
context 'nil published_at' do
|
376
|
+
display = ->(post) { post.title }
|
377
|
+
|
378
|
+
let! :null_1 do
|
379
|
+
Post.create!(title: 'null_1', published_at: nil).reload
|
380
|
+
end
|
381
|
+
let! :null_2 do
|
382
|
+
Post.create!(title: 'null_2', published_at: nil).reload
|
383
|
+
end
|
384
|
+
let! :older do
|
385
|
+
Post.create!(title: 'older', published_at: Time.now + 1.hour)
|
386
|
+
end
|
387
|
+
let! :newer do
|
388
|
+
Post.create!(title: 'newer', published_at: Time.now - 1.hour)
|
389
|
+
end
|
390
|
+
|
391
|
+
it 'orders nulls first (desc)' do
|
392
|
+
space = Post.seek([:published_at, :desc, nulls: :first])
|
393
|
+
expect_order space, [null_1, null_2, older, newer], &display
|
394
|
+
end
|
395
|
+
|
396
|
+
it 'orders nulls first (asc)' do
|
397
|
+
space = Post.seek([:published_at, :asc, nulls: :first])
|
398
|
+
expect_order space, [null_1, null_2, newer, older], &display
|
399
|
+
end
|
400
|
+
|
401
|
+
it 'orders nulls last (desc)' do
|
402
|
+
space = Post.seek([:published_at, :desc, nulls: :last])
|
403
|
+
expect_order space, [older, newer, null_1, null_2], &display
|
404
|
+
end
|
405
|
+
|
406
|
+
it 'orders nulls last (asc)' do
|
407
|
+
space = Post.seek([:published_at, :asc, nulls: :last])
|
408
|
+
expect_order space, [newer, older, null_1, null_2], &display
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
context 'after/before no strict' do
|
413
|
+
context 'by middle attribute in search order' do
|
414
|
+
let! :base do
|
415
|
+
Post.create! pinned: true, published_at: Time.now
|
416
|
+
end
|
417
|
+
let! :older do
|
418
|
+
Post.create! pinned: true, published_at: Time.now + 1.hour
|
419
|
+
end
|
420
|
+
let! :newer do
|
421
|
+
Post.create! pinned: true, published_at: Time.now - 1.hour
|
422
|
+
end
|
423
|
+
|
424
|
+
it 'includes first element' do
|
425
|
+
point = Post.order_list_at(base)
|
426
|
+
|
427
|
+
expect(point.after.count).to eq 1
|
428
|
+
expect(point.after.to_a).to eq [newer]
|
429
|
+
|
430
|
+
expect(point.after(false).count).to eq 2
|
431
|
+
expect(point.after(false).to_a).to eq [base, newer]
|
432
|
+
expect(point.before(false).to_a).to eq [base, older]
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
436
|
+
context 'by last attribute in search order' do
|
437
|
+
let!(:base) do
|
438
|
+
Post.create! pinned: true,
|
439
|
+
published_at: Time.new(2016, 5, 1, 5, 4, 3),
|
440
|
+
id: 6
|
441
|
+
end
|
442
|
+
let!(:previous) do
|
443
|
+
Post.create! pinned: true,
|
444
|
+
published_at: Time.new(2016, 5, 1, 5, 4, 3),
|
445
|
+
id: 4
|
446
|
+
end
|
447
|
+
let!(:next_one) do
|
448
|
+
Post.create! pinned: true,
|
449
|
+
published_at: Time.new(2016, 5, 1, 5, 4, 3),
|
450
|
+
id: 9
|
451
|
+
end
|
452
|
+
|
453
|
+
it 'includes first element' do
|
454
|
+
point = Post.order_list_at(base)
|
455
|
+
|
456
|
+
expect(point.after.count).to eq 1
|
457
|
+
expect(point.after.to_a).to eq [previous]
|
458
|
+
|
459
|
+
expect(point.after(false).count).to eq 2
|
460
|
+
expect(point.after(false).to_a).to eq [base, previous]
|
461
|
+
expect(point.before(false).to_a).to eq [base, next_one]
|
274
462
|
end
|
275
463
|
end
|
276
464
|
end
|
@@ -282,10 +470,12 @@ describe 'OrderQuery' do
|
|
282
470
|
ActiveRecord::Schema.define do
|
283
471
|
self.verbose = false
|
284
472
|
create_table :posts do |t|
|
473
|
+
t.string :title
|
285
474
|
t.boolean :pinned
|
286
475
|
t.datetime :published_at
|
287
476
|
end
|
288
477
|
end
|
478
|
+
Post.reset_column_information
|
289
479
|
end
|
290
480
|
after :all do
|
291
481
|
ActiveRecord::Migration.drop_table :posts
|
@@ -298,7 +488,8 @@ describe 'OrderQuery' do
|
|
298
488
|
context 'wrap top-level OR on' do
|
299
489
|
wrap_top_level_or true
|
300
490
|
it 'wraps top-level OR' do
|
301
|
-
after_scope = User.create!(updated_at: Date.parse('2014-09-06'))
|
491
|
+
after_scope = User.create!(updated_at: Date.parse('2014-09-06'))
|
492
|
+
.seek([%i[updated_at desc], %i[id desc]]).after
|
302
493
|
expect(after_scope.to_sql).to include('<=')
|
303
494
|
end
|
304
495
|
end
|
@@ -306,7 +497,8 @@ describe 'OrderQuery' do
|
|
306
497
|
context 'wrap top-level OR off' do
|
307
498
|
wrap_top_level_or false
|
308
499
|
it 'does not wrap top-level OR' do
|
309
|
-
after_scope = User.create!(updated_at: Date.parse('2014-09-06'))
|
500
|
+
after_scope = User.create!(updated_at: Date.parse('2014-09-06'))
|
501
|
+
.seek([%i[updated_at desc], %i[id desc]]).after
|
310
502
|
expect(after_scope.to_sql).to_not include('<=')
|
311
503
|
end
|
312
504
|
end
|
@@ -322,6 +514,7 @@ describe 'OrderQuery' do
|
|
322
514
|
t.datetime :updated_at, null: false
|
323
515
|
end
|
324
516
|
end
|
517
|
+
User.reset_column_information
|
325
518
|
end
|
326
519
|
|
327
520
|
after :all do
|