order_query 0.3.3 → 0.5.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.
- 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
|