order_query 0.3.1 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|