order_query 0.3.1 → 0.3.2
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 +4 -4
- data/CHANGES.md +5 -0
- data/README.md +1 -1
- data/lib/order_query/column.rb +17 -14
- data/lib/order_query/direction.rb +32 -0
- data/lib/order_query/sql/order_by.rb +28 -24
- data/lib/order_query/sql/where.rb +4 -7
- data/lib/order_query/version.rb +1 -1
- data/spec/order_query_spec.rb +37 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6e69a0283a9dcc95fe70d2fd18c02f5aa62120d2
|
4
|
+
data.tar.gz: d1d0a0975074175ff4eb15d7fa5efdd20fc91c3f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0a5d6a00cd9096587584e9c32a1daf05f26577175d9e0bf6a783b8bc189a710482143b18166b82620b3ef315928c51ed837f6776e719ff53c53c644cfc278fa6
|
7
|
+
data.tar.gz: 6ae64249791c2c8ac500b10b3a50c9ed6cf439056301ca1d361f1d8f5559fa1180bcdf72113698922a08ad6fd85f5e3adcd3c25c5b9e9f50776225ce51c616f2
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
## 0.3.2
|
2
|
+
|
3
|
+
* Optimization: do not wrap top-level disjunctive in `AND` when the column has an enumerated order. [Read more](https://github.com/glebm/order_query/issues/3#issuecomment-54764638).
|
4
|
+
* Boolean enum columns (e.g. `[:pinned, [true, false]]`) are now automatically collapsed to `ORDER by column ASC|DESC`.
|
5
|
+
|
1
6
|
## 0.3.1
|
2
7
|
|
3
8
|
* Automatically add primary key when there is no unique column for the order
|
data/README.md
CHANGED
data/lib/order_query/column.rb
CHANGED
@@ -1,29 +1,32 @@
|
|
1
1
|
# coding: utf-8
|
2
|
+
require 'order_query/direction'
|
2
3
|
require 'order_query/sql/column'
|
3
4
|
module OrderQuery
|
4
5
|
# An order column (sort column)
|
5
6
|
class Column
|
6
|
-
attr_reader :name, :
|
7
|
+
attr_reader :name, :order_enum, :options
|
7
8
|
delegate :column_name, :quote, to: :@sql
|
8
9
|
|
9
10
|
# @option spec [String] :unique Mark the attribute as unique to avoid redundant columns
|
10
11
|
def initialize(spec, scope)
|
11
|
-
spec
|
12
|
-
options
|
13
|
-
@name
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
@order = spec[2] || :desc
|
18
|
-
else
|
19
|
-
@order = spec[1] || :asc
|
12
|
+
spec = spec.dup
|
13
|
+
options = spec.extract_options!
|
14
|
+
@name = spec[0]
|
15
|
+
if spec[1].is_a?(Array)
|
16
|
+
@order_enum = spec.delete_at(1)
|
17
|
+
spec[1] ||= :desc
|
20
18
|
end
|
21
|
-
@
|
19
|
+
@direction = Direction.parse!(spec[1] || :asc)
|
20
|
+
@options = options.reverse_merge(
|
22
21
|
unique: name.to_s == scope.primary_key,
|
23
22
|
complete: true
|
24
23
|
)
|
25
|
-
@unique
|
26
|
-
@sql
|
24
|
+
@unique = @options[:unique]
|
25
|
+
@sql = SQL::Column.new(self, scope)
|
26
|
+
end
|
27
|
+
|
28
|
+
def direction(reverse = false)
|
29
|
+
reverse ? Direction.reverse(@direction) : @direction
|
27
30
|
end
|
28
31
|
|
29
32
|
def unique?
|
@@ -40,7 +43,7 @@ module OrderQuery
|
|
40
43
|
ord = order_enum
|
41
44
|
pos = ord.index(value)
|
42
45
|
if pos
|
43
|
-
dir =
|
46
|
+
dir = direction
|
44
47
|
if side == :after && dir == :desc || side == :before && dir == :asc
|
45
48
|
ord.from pos + (strict ? 1 : 0)
|
46
49
|
else
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module OrderQuery
|
2
|
+
# Responsible for handling :asc and :desc
|
3
|
+
module Direction
|
4
|
+
extend self
|
5
|
+
|
6
|
+
DIRECTIONS = [:asc, :desc].freeze
|
7
|
+
DIRECTIONS_S = DIRECTIONS.map { |d| d.to_s.freeze }.freeze
|
8
|
+
|
9
|
+
def all
|
10
|
+
DIRECTIONS
|
11
|
+
end
|
12
|
+
|
13
|
+
# @param [:asc, :desc] direction
|
14
|
+
# @return [:asc, :desc]
|
15
|
+
def reverse(direction)
|
16
|
+
all[(all.index(direction) + 1) % 2].to_sym
|
17
|
+
end
|
18
|
+
|
19
|
+
# @param [:asc, :desc, String] direction
|
20
|
+
# @raise [ArgumentError]
|
21
|
+
# @return [:asc, :desc]
|
22
|
+
def parse!(direction)
|
23
|
+
if direction.is_a?(Symbol)
|
24
|
+
direction if DIRECTIONS.include?(direction)
|
25
|
+
else
|
26
|
+
direction = direction.to_s.downcase
|
27
|
+
direction.to_sym if DIRECTIONS_S.include?(direction)
|
28
|
+
end or
|
29
|
+
raise ArgumentError.new("sort direction must be in #{all.map(&:inspect).join(', ')}, is #{direction.inspect}")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -13,48 +13,52 @@ module OrderQuery
|
|
13
13
|
|
14
14
|
# @return [String]
|
15
15
|
def build_reverse
|
16
|
-
@reverse_sql ||= join_order_by_clauses
|
16
|
+
@reverse_sql ||= join_order_by_clauses order_by_sql_clauses(true)
|
17
17
|
end
|
18
18
|
|
19
19
|
protected
|
20
20
|
|
21
21
|
# @return [Array<String>]
|
22
|
-
def order_by_sql_clauses
|
23
|
-
@columns.map { |
|
22
|
+
def order_by_sql_clauses(reverse = false)
|
23
|
+
@columns.map { |col| column_clause col, reverse }
|
24
24
|
end
|
25
25
|
|
26
|
-
def column_clause(
|
27
|
-
|
28
|
-
|
29
|
-
if cond.order_enum
|
30
|
-
cond.order_enum.map { |v| "#{col_sql}=#{cond.quote v} #{dir_sql}" }.join(', ').freeze
|
26
|
+
def column_clause(col, reverse = false)
|
27
|
+
if col.order_enum
|
28
|
+
column_clause_enum col, reverse
|
31
29
|
else
|
32
|
-
|
30
|
+
column_clause_ray col, reverse
|
33
31
|
end
|
34
32
|
end
|
35
33
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
34
|
+
def column_clause_ray(col, reverse = false)
|
35
|
+
"#{col.column_name} #{sort_direction_sql(col, reverse)}".freeze
|
36
|
+
end
|
37
|
+
|
38
|
+
def column_clause_enum(col, reverse = false)
|
39
|
+
enum = col.order_enum
|
40
|
+
# Collapse boolean enum to `ORDER BY column ASC|DESC`
|
41
|
+
if enum == [false, true] || enum == [true, false]
|
42
|
+
return column_clause_ray col, reverse ^ enum.last
|
43
43
|
end
|
44
|
+
enum.map { |v|
|
45
|
+
"#{order_by_value_sql col, v} #{sort_direction_sql(col, reverse)}"
|
46
|
+
}.join(', ').freeze
|
47
|
+
end
|
48
|
+
|
49
|
+
def order_by_value_sql(col, v)
|
50
|
+
"#{col.column_name}=#{col.quote v}"
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [String]
|
54
|
+
def sort_direction_sql(col, reverse = false)
|
55
|
+
col.direction(reverse).to_s.upcase.freeze
|
44
56
|
end
|
45
57
|
|
46
58
|
# @param [Array<String>] clauses
|
47
59
|
def join_order_by_clauses(clauses)
|
48
60
|
clauses.join(', ').freeze
|
49
61
|
end
|
50
|
-
|
51
|
-
# @return [Array<String>]
|
52
|
-
def order_by_reverse_sql_clauses
|
53
|
-
swap = {'DESC' => 'ASC', 'ASC' => 'DESC'}
|
54
|
-
order_by_sql_clauses.map { |s|
|
55
|
-
s.gsub(/DESC|ASC/) { |m| swap[m] }
|
56
|
-
}
|
57
|
-
end
|
58
62
|
end
|
59
63
|
end
|
60
64
|
end
|
@@ -65,9 +65,8 @@ module OrderQuery
|
|
65
65
|
# Read more at https://github.com/glebm/order_query/issues/3
|
66
66
|
def wrap_top_level_or(query, terms, side)
|
67
67
|
top_term_i = terms.index(&:present?)
|
68
|
-
if top_term_i && terms[top_term_i].length == 2 &&
|
69
|
-
|
70
|
-
join_terms 'AND'.freeze, cond, wrap_term_with_parens(query)
|
68
|
+
if top_term_i && terms[top_term_i].length == 2 && !(col = @columns[top_term_i]).order_enum
|
69
|
+
join_terms 'AND'.freeze, where_side(col, side, false), wrap_term_with_parens(query)
|
71
70
|
else
|
72
71
|
query
|
73
72
|
end
|
@@ -107,11 +106,9 @@ module OrderQuery
|
|
107
106
|
[%Q(#{col.column_name} = ?).freeze, [value]]
|
108
107
|
end
|
109
108
|
|
109
|
+
RAY_OP = {asc: '>'.freeze, desc: '<'.freeze}.freeze
|
110
110
|
def where_ray(col, from, mode, strict = true)
|
111
|
-
|
112
|
-
ops = ops.reverse if mode == :after
|
113
|
-
op = {asc: ops[0], desc: ops[1]}[col.order || :asc]
|
114
|
-
["#{col.column_name} #{op}#{'=' unless strict} ?".freeze, [from]]
|
111
|
+
["#{col.column_name} #{RAY_OP[col.direction(mode == :before)]}#{'=' unless strict} ?".freeze, [from]]
|
115
112
|
end
|
116
113
|
|
117
114
|
WHERE_IDENTITY = [''.freeze, [].freeze].freeze
|
data/lib/order_query/version.rb
CHANGED
data/spec/order_query_spec.rb
CHANGED
@@ -57,7 +57,7 @@ end
|
|
57
57
|
describe 'OrderQuery' do
|
58
58
|
|
59
59
|
[false, true].each do |wrap_top_level_or|
|
60
|
-
context "(
|
60
|
+
context "(wtlo: #{wrap_top_level_or})" do
|
61
61
|
wrap_top_level_or wrap_top_level_or
|
62
62
|
|
63
63
|
context 'Issue test model' do
|
@@ -210,6 +210,42 @@ describe 'OrderQuery' do
|
|
210
210
|
expect(o2.next(true)).to eq(p1)
|
211
211
|
end
|
212
212
|
|
213
|
+
context 'boolean enum order' do
|
214
|
+
before do
|
215
|
+
create_post pinned: true
|
216
|
+
create_post pinned: false
|
217
|
+
end
|
218
|
+
after do
|
219
|
+
Post.delete_all
|
220
|
+
end
|
221
|
+
it 'ORDER BY is collapsed' do
|
222
|
+
expect(Post.seek([:pinned, [true, false]]).scope.to_sql).to include('ORDER BY "posts"."pinned" DESC')
|
223
|
+
end
|
224
|
+
it 'enum asc' do
|
225
|
+
expect(Post.seek([:pinned, [false, true], :asc]).scope.pluck(:pinned)).to eq([true, false])
|
226
|
+
expect(Post.seek([:pinned, [true, false], :asc]).scope.pluck(:pinned)).to eq([false, true])
|
227
|
+
end
|
228
|
+
it 'enum desc' do
|
229
|
+
expect(Post.seek([:pinned, [false, true], :desc]).scope.pluck(:pinned)).to eq([false, true])
|
230
|
+
expect(Post.seek([:pinned, [true, false], :desc]).scope.pluck(:pinned)).to eq([true, false])
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
xcontext 'nil in enum' do
|
235
|
+
states = [nil, false, true]
|
236
|
+
let!(:posts) { states.map { |state| create_post(pinned: state) } }
|
237
|
+
states.permutation do |p|
|
238
|
+
# There is no cross-DB SQL that can be generated to position nil results
|
239
|
+
# http://use-the-index-luke.com/sql/sorting-grouping/order-by-asc-desc-nulls-last
|
240
|
+
next unless p.first.nil? || p.last.nil?
|
241
|
+
# Positioning NULLs first or last can be achieved, but remains on the ToDo / contributions welcome list
|
242
|
+
it "nil in enum works for #{p}" do
|
243
|
+
expect(Post.seek([:pinned, p]).scope.all.map(&:pinned)).to eq(p)
|
244
|
+
expect(Post.seek([:pinned, p, :asc]).scope.all.map(&:pinned)).to eq(p.reverse)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
213
249
|
before do
|
214
250
|
Post.delete_all
|
215
251
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: order_query
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gleb Mazovetskiy
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-09-
|
11
|
+
date: 2014-09-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -79,6 +79,7 @@ files:
|
|
79
79
|
- Rakefile
|
80
80
|
- lib/order_query.rb
|
81
81
|
- lib/order_query/column.rb
|
82
|
+
- lib/order_query/direction.rb
|
82
83
|
- lib/order_query/point.rb
|
83
84
|
- lib/order_query/space.rb
|
84
85
|
- lib/order_query/sql/column.rb
|