sorted 2.0.3 → 2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +0 -3
- data/.rubocop_todo.yml +8 -4
- data/README.md +4 -0
- data/lib/sorted.rb +0 -272
- data/lib/sorted/elasticsearch_query.rb +27 -0
- data/lib/sorted/json_query.rb +16 -0
- data/lib/sorted/params_query.rb +38 -0
- data/lib/sorted/parse.rb +14 -0
- data/lib/sorted/set.rb +238 -0
- data/lib/sorted/sql_query.rb +26 -0
- data/lib/sorted/uri_query.rb +22 -0
- data/lib/sorted/version.rb +1 -1
- data/sorted.gemspec +1 -1
- data/spec/elasticsearch_query_spec.rb +2 -0
- data/spec/json_query_spec.rb +2 -0
- data/spec/params_query_spec.rb +2 -0
- data/spec/set_spec.rb +17 -1
- data/spec/spec_helper.rb +0 -1
- data/spec/sql_query_spec.rb +3 -3
- data/spec/uri_query_spec.rb +2 -0
- metadata +9 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b9b0ab137af7f8964eb8b6d8bee93e782fae7dce
|
4
|
+
data.tar.gz: 373d2c35b43cdddecb7d0eb1e0b60b6cd2ebcbb0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 53e9a1614a66a52f42ec5b219c25927978c410ae43ce4eb12cd0facb1be64c2e64cb2dba457909043f4f12320a04e3313336e60d1be7be5d57e817caf5656c4d
|
7
|
+
data.tar.gz: 57eaad9eb987b624cebe8f320a786e541450d4f24424a31f41153f233d0e12f55f38c8d8eb73edb045117dc7faccb2f4192f64077857cc1b8eb62f2589377c8d
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
data/.rubocop_todo.yml
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# This configuration was generated by `rubocop --auto-gen-config`
|
2
|
-
# on 2015-
|
2
|
+
# on 2015-09-23 09:55:31 +1000 using RuboCop version 0.28.0.
|
3
3
|
# The point is for the user to remove these configuration records
|
4
4
|
# one by one as the offenses are removed from the code base.
|
5
5
|
# Note that changes in the inspected code, or installation of new
|
@@ -12,9 +12,9 @@ Metrics/AbcSize:
|
|
12
12
|
# Offense count: 1
|
13
13
|
# Configuration parameters: CountComments.
|
14
14
|
Metrics/ClassLength:
|
15
|
-
Max:
|
15
|
+
Max: 130
|
16
16
|
|
17
|
-
# Offense count:
|
17
|
+
# Offense count: 18
|
18
18
|
# Configuration parameters: AllowURI, URISchemes.
|
19
19
|
Metrics/LineLength:
|
20
20
|
Max: 131
|
@@ -24,7 +24,7 @@ Metrics/LineLength:
|
|
24
24
|
Metrics/MethodLength:
|
25
25
|
Max: 13
|
26
26
|
|
27
|
-
# Offense count:
|
27
|
+
# Offense count: 9
|
28
28
|
Style/Documentation:
|
29
29
|
Enabled: false
|
30
30
|
|
@@ -37,3 +37,7 @@ Style/For:
|
|
37
37
|
# Configuration parameters: MaxLineLength.
|
38
38
|
Style/IfUnlessModifier:
|
39
39
|
Enabled: false
|
40
|
+
|
41
|
+
# Offense count: 3
|
42
|
+
Style/OpMethod:
|
43
|
+
Enabled: false
|
data/README.md
CHANGED
@@ -10,6 +10,8 @@ attributes in weird and wonderful ways.
|
|
10
10
|
The secret sauce is the `Sorted::Set` object, in this example we 'toggle' email:
|
11
11
|
|
12
12
|
```ruby
|
13
|
+
require 'sorted/set'
|
14
|
+
|
13
15
|
a = Sorted::Set.new([['email', 'asc'], ['name', 'asc']])
|
14
16
|
b = Sorted::Set.new([['email', 'asc'], ['phone', 'asc']])
|
15
17
|
|
@@ -26,6 +28,8 @@ when you sort by various columns, `Sorted::Set` pretty much just does that.
|
|
26
28
|
Parsers return a `Sorted::Set` that can then be used by an encoder:
|
27
29
|
|
28
30
|
```ruby
|
31
|
+
require 'sorted/uri_query'
|
32
|
+
|
29
33
|
set = Sorted::URIQuery.parse('name_asc!email_asc')
|
30
34
|
Sorted::SQLQuery.encode(set) #=> 'name ASC email ASC'
|
31
35
|
```
|
data/lib/sorted.rb
CHANGED
@@ -1,274 +1,2 @@
|
|
1
1
|
module Sorted
|
2
|
-
class Set
|
3
|
-
include Enumerable
|
4
|
-
include Comparable
|
5
|
-
|
6
|
-
def initialize(set = [])
|
7
|
-
@set = set
|
8
|
-
end
|
9
|
-
|
10
|
-
def each(&block)
|
11
|
-
@set.each(&block)
|
12
|
-
end
|
13
|
-
|
14
|
-
##
|
15
|
-
# Gets the keys from the array pairs
|
16
|
-
#
|
17
|
-
# set = [["email", "name"], ["desc", "desc"]]
|
18
|
-
# set.transpose #=> [["email", "name"], ["desc", "desc"]]
|
19
|
-
# set.transpose.first #=> ["email", "name"]
|
20
|
-
|
21
|
-
def keys
|
22
|
-
@set.transpose.first || []
|
23
|
-
end
|
24
|
-
|
25
|
-
##
|
26
|
-
# Returns a resulting set with specific keys flipped
|
27
|
-
#
|
28
|
-
# a = Sorted::Set.new([['email', 'asc'], ['name', 'asc']])
|
29
|
-
# b = Sorted::Set.new([['email', 'asc'], ['phone', 'asc']])
|
30
|
-
# s = a.direction_intersect(b)
|
31
|
-
# s.to_a #=> [['email', 'desc'], ['phone', 'asc'], ['name', 'asc']]
|
32
|
-
|
33
|
-
def direction_intersect(other)
|
34
|
-
self.class.new.tap do |memo|
|
35
|
-
unless other.keys.empty?
|
36
|
-
a(memo, other)
|
37
|
-
b(memo, other)
|
38
|
-
end
|
39
|
-
c(memo)
|
40
|
-
d(memo, other)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
def -(other)
|
45
|
-
self.class.new.tap do |memo|
|
46
|
-
each do |a|
|
47
|
-
b = other.assoc(a.first)
|
48
|
-
next if b
|
49
|
-
memo << a
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
def +(other)
|
55
|
-
self.class.new(@set + other.to_a)
|
56
|
-
end
|
57
|
-
|
58
|
-
def <<(other)
|
59
|
-
self.class.new(@set << other.to_a)
|
60
|
-
end
|
61
|
-
|
62
|
-
def select(&block)
|
63
|
-
self.class.new(@set.select(&block))
|
64
|
-
end
|
65
|
-
|
66
|
-
def reject(&block)
|
67
|
-
self.class.new(@set.reject(&block))
|
68
|
-
end
|
69
|
-
|
70
|
-
def <=>(other)
|
71
|
-
@set <=> other.to_a
|
72
|
-
end
|
73
|
-
|
74
|
-
def uniq
|
75
|
-
self.class.new(@set.uniq)
|
76
|
-
end
|
77
|
-
|
78
|
-
def assoc(o)
|
79
|
-
@set.assoc(o)
|
80
|
-
end
|
81
|
-
|
82
|
-
def at(i)
|
83
|
-
@set.at(i)
|
84
|
-
end
|
85
|
-
|
86
|
-
def to_a
|
87
|
-
@set
|
88
|
-
end
|
89
|
-
|
90
|
-
def to_hash
|
91
|
-
@set.inject({}) { |a, e| a.merge(Hash[e[0], e[1]]) }
|
92
|
-
end
|
93
|
-
|
94
|
-
private
|
95
|
-
|
96
|
-
# If the order of keys match upto the size of the set then flip them
|
97
|
-
def a(memo, other)
|
98
|
-
if keys == other.keys.take(keys.size)
|
99
|
-
keys.each do |order|
|
100
|
-
if other.keys.include?(order)
|
101
|
-
memo << [order, flip_direction(other.assoc(order).last)]
|
102
|
-
end
|
103
|
-
end
|
104
|
-
else
|
105
|
-
keys.each do |order|
|
106
|
-
if other.keys.include?(order)
|
107
|
-
memo << other.assoc(order)
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
# Add items from other that are common and not already added
|
114
|
-
def b(memo, other)
|
115
|
-
other.keys.each do |sort|
|
116
|
-
if keys.include?(sort) && !memo.keys.include?(sort)
|
117
|
-
memo << other.assoc(sort)
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
# Add items not in memo
|
123
|
-
def c(memo)
|
124
|
-
each do |order|
|
125
|
-
unless memo.keys.include?(order[0])
|
126
|
-
memo << order
|
127
|
-
end
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
# Add items from other not in memo
|
132
|
-
def d(memo, other)
|
133
|
-
other.each do |sort|
|
134
|
-
unless memo.keys.include?(sort[0])
|
135
|
-
memo << sort
|
136
|
-
end
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
def flip_direction(direction)
|
141
|
-
case direction
|
142
|
-
when 'asc' then 'desc'
|
143
|
-
when 'desc'then 'asc'
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
module Parse
|
149
|
-
def split(raw, delim, &block)
|
150
|
-
return Set.new if raw.nil?
|
151
|
-
raw.to_s.split(delim).inject(Set.new, &block)
|
152
|
-
end
|
153
|
-
|
154
|
-
def parse_match(m)
|
155
|
-
[(m[2].nil? ? m[1] : m[2]), (m[3].nil? ? 'asc' : m[3].downcase)]
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
class URIQuery
|
160
|
-
extend Parse
|
161
|
-
|
162
|
-
REGEXP = /(([a-z0-9._]+)_([asc|desc]+)|[a-z0-9._]+)/i
|
163
|
-
|
164
|
-
def self.parse(raw)
|
165
|
-
split(raw, /!/) do |set, part|
|
166
|
-
m = part.match(REGEXP)
|
167
|
-
next unless m
|
168
|
-
set << parse_match(m)
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|
172
|
-
def self.encode(set)
|
173
|
-
set.map { |a| a.join('_') }.join('!')
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
class SQLQuery
|
178
|
-
extend Parse
|
179
|
-
|
180
|
-
REGEXP = /(([a-z0-9._]+)\s([asc|desc]+)|[a-z0-9._]+)/i
|
181
|
-
|
182
|
-
def self.parse(raw)
|
183
|
-
split(raw, /,/) do |set, part|
|
184
|
-
m = part.match(REGEXP)
|
185
|
-
next unless m
|
186
|
-
set << parse_match(m)
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
def self.encode(set, quote_proc = ->(f) { f })
|
191
|
-
set.map { |a| "#{column(a[0], quote_proc)} #{a[1].upcase}" }.join(', ')
|
192
|
-
end
|
193
|
-
|
194
|
-
def self.column(parts, quote_proc)
|
195
|
-
parts.split('.').map { |frag| quote_proc.call(frag) }.join('.')
|
196
|
-
end
|
197
|
-
private_class_method :column
|
198
|
-
end
|
199
|
-
|
200
|
-
class JSONQuery
|
201
|
-
extend Parse
|
202
|
-
|
203
|
-
JSON_TO_SORTED = { 1 => 'asc', -1 => 'desc' }
|
204
|
-
SORTED_TO_JSON = { 'asc' => 1, 'desc' => -1 }
|
205
|
-
|
206
|
-
def self.parse(raw)
|
207
|
-
Set.new(raw.map { |key, val| [key, JSON_TO_SORTED[val]] })
|
208
|
-
end
|
209
|
-
|
210
|
-
def self.encode(set)
|
211
|
-
set.inject({}) { |a, e| a.merge(Hash[e[0], SORTED_TO_JSON[e[1]]]) }
|
212
|
-
end
|
213
|
-
end
|
214
|
-
|
215
|
-
##
|
216
|
-
# Parses an Elasticsearch type set of order
|
217
|
-
#
|
218
|
-
# Parsing:
|
219
|
-
#
|
220
|
-
# params = [{ 'email' => {'order' => 'desc'}}]
|
221
|
-
# set = Sorted::ElasticsearchQuery.parse(params)
|
222
|
-
# set.to_a #=> [['email', 'desc']]
|
223
|
-
#
|
224
|
-
# Encoding:
|
225
|
-
#
|
226
|
-
# Sorted::ParamsQuery.encode(set) #=> [{ 'email' => {'order' => 'desc'}}]
|
227
|
-
#
|
228
|
-
|
229
|
-
class ElasticsearchQuery
|
230
|
-
extend Parse
|
231
|
-
|
232
|
-
def self.parse(raw)
|
233
|
-
Set.new(raw.each_with_object([]) { |hash, a| a << [hash.first.first, hash.first.last['order']] })
|
234
|
-
end
|
235
|
-
|
236
|
-
def self.encode(set)
|
237
|
-
set.to_a.each_with_object([]) { |f, a| a << { f.first => { 'order' => f.last } } }
|
238
|
-
end
|
239
|
-
end
|
240
|
-
|
241
|
-
##
|
242
|
-
# Parses an array of decoded query params
|
243
|
-
#
|
244
|
-
# This parser/encoder uses an already decoded array of sort strings parsed by
|
245
|
-
# a URI library.
|
246
|
-
#
|
247
|
-
# Parsing:
|
248
|
-
#
|
249
|
-
# params = ['phone_desc', 'name_asc']
|
250
|
-
# set = Sorted::ParamsQuery.parse(params)
|
251
|
-
# set.to_a #=> [['phone', 'desc'], ['name', asc']]
|
252
|
-
#
|
253
|
-
# Encoding:
|
254
|
-
#
|
255
|
-
# Sorted::ParamsQuery.encode(set) #=> ['phone_desc', 'name_asc']
|
256
|
-
|
257
|
-
class ParamsQuery
|
258
|
-
extend Parse
|
259
|
-
|
260
|
-
REGEXP = /(([a-z0-9._]+)_([asc|desc]+)|[a-z0-9._]+)/i
|
261
|
-
|
262
|
-
def self.parse(params)
|
263
|
-
params.inject(Set.new) do |set, part|
|
264
|
-
m = part.match(REGEXP)
|
265
|
-
next unless m
|
266
|
-
set << parse_match(m)
|
267
|
-
end
|
268
|
-
end
|
269
|
-
|
270
|
-
def self.encode(set)
|
271
|
-
set.map { |a| a.join('_') }
|
272
|
-
end
|
273
|
-
end
|
274
2
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'sorted/set'
|
2
|
+
|
3
|
+
module Sorted
|
4
|
+
##
|
5
|
+
# Parses an Elasticsearch type set of order
|
6
|
+
#
|
7
|
+
# Parsing:
|
8
|
+
#
|
9
|
+
# params = [{ 'email' => {'order' => 'desc'}}]
|
10
|
+
# set = Sorted::ElasticsearchQuery.parse(params)
|
11
|
+
# set.to_a #=> [['email', 'desc']]
|
12
|
+
#
|
13
|
+
# Encoding:
|
14
|
+
#
|
15
|
+
# Sorted::ParamsQuery.encode(set) #=> [{ 'email' => {'order' => 'desc'}}]
|
16
|
+
#
|
17
|
+
|
18
|
+
class ElasticsearchQuery
|
19
|
+
def self.parse(raw)
|
20
|
+
Set.new(raw.each_with_object([]) { |hash, a| a << [hash.first.first, hash.first.last['order']] })
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.encode(set)
|
24
|
+
set.to_a.each_with_object([]) { |f, a| a << { f.first => { 'order' => f.last } } }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'sorted/set'
|
2
|
+
|
3
|
+
module Sorted
|
4
|
+
class JSONQuery
|
5
|
+
JSON_TO_SORTED = { 1 => 'asc', -1 => 'desc' }
|
6
|
+
SORTED_TO_JSON = { 'asc' => 1, 'desc' => -1 }
|
7
|
+
|
8
|
+
def self.parse(raw)
|
9
|
+
Set.new(raw.map { |key, val| [key, JSON_TO_SORTED[val]] })
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.encode(set)
|
13
|
+
set.inject({}) { |a, e| a.merge(Hash[e[0], SORTED_TO_JSON[e[1]]]) }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'sorted/set'
|
2
|
+
require 'sorted/parse'
|
3
|
+
|
4
|
+
module Sorted
|
5
|
+
##
|
6
|
+
# Parses an array of decoded query params
|
7
|
+
#
|
8
|
+
# This parser/encoder uses an already decoded array of sort strings parsed by
|
9
|
+
# a URI library.
|
10
|
+
#
|
11
|
+
# Parsing:
|
12
|
+
#
|
13
|
+
# params = ['phone_desc', 'name_asc']
|
14
|
+
# set = Sorted::ParamsQuery.parse(params)
|
15
|
+
# set.to_a #=> [['phone', 'desc'], ['name', asc']]
|
16
|
+
#
|
17
|
+
# Encoding:
|
18
|
+
#
|
19
|
+
# Sorted::ParamsQuery.encode(set) #=> ['phone_desc', 'name_asc']
|
20
|
+
|
21
|
+
class ParamsQuery
|
22
|
+
extend Parse
|
23
|
+
|
24
|
+
REGEXP = /(([a-z0-9._]+)_([asc|desc]+)|[a-z0-9._]+)/i
|
25
|
+
|
26
|
+
def self.parse(params)
|
27
|
+
params.inject(Set.new) do |set, part|
|
28
|
+
m = part.match(REGEXP)
|
29
|
+
next unless m
|
30
|
+
set << parse_match(m)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.encode(set)
|
35
|
+
set.map { |a| a.join('_') }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/sorted/parse.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'sorted/set'
|
2
|
+
|
3
|
+
module Sorted
|
4
|
+
module Parse
|
5
|
+
def split(raw, delim, &block)
|
6
|
+
return Set.new if raw.nil?
|
7
|
+
raw.to_s.split(delim).inject(Set.new, &block)
|
8
|
+
end
|
9
|
+
|
10
|
+
def parse_match(m)
|
11
|
+
[(m[2].nil? ? m[1] : m[2]), (m[3].nil? ? 'asc' : m[3].downcase)]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/sorted/set.rb
ADDED
@@ -0,0 +1,238 @@
|
|
1
|
+
module Sorted
|
2
|
+
class Set
|
3
|
+
include Enumerable
|
4
|
+
include Comparable
|
5
|
+
|
6
|
+
def initialize(ary = [])
|
7
|
+
@ary = ary
|
8
|
+
end
|
9
|
+
|
10
|
+
##
|
11
|
+
# Calls the given block once for each element in self, passing that element
|
12
|
+
# as a parameter.
|
13
|
+
#
|
14
|
+
# An Enumerator is returned if no block is given.
|
15
|
+
|
16
|
+
def each
|
17
|
+
return to_enum(:each) unless block_given?
|
18
|
+
@ary.each { |item| yield item }
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# Returns the keys form the array pairs.
|
23
|
+
#
|
24
|
+
# set = Sorted::Set.new([["email", "desc"], ["name", "desc"]])
|
25
|
+
# set.keys #=> ["email", "name"]
|
26
|
+
|
27
|
+
def keys
|
28
|
+
@ary.transpose.first || []
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# Returns a new array containing elements common to the two arrays,
|
33
|
+
# excluding any duplicates.
|
34
|
+
#
|
35
|
+
# Any matching keys at matching indexes with the same order will have the
|
36
|
+
# order reversed.
|
37
|
+
#
|
38
|
+
# a = Sorted::Set.new([['email', 'asc'], ['name', 'asc']])
|
39
|
+
# b = Sorted::Set.new([['email', 'asc'], ['phone', 'asc']])
|
40
|
+
# s = a.direction_intersect(b)
|
41
|
+
# s.to_a #=> [['email', 'desc'], ['phone', 'asc'], ['name', 'asc']]
|
42
|
+
|
43
|
+
def direction_intersect(other_set)
|
44
|
+
self.class.new.tap do |memo|
|
45
|
+
unless other_set.keys.empty?
|
46
|
+
a(memo, other_set)
|
47
|
+
b(memo, other_set)
|
48
|
+
end
|
49
|
+
c(memo)
|
50
|
+
d(memo, other_set)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# Array Difference - Returns a new set that is a copy of the original set,
|
56
|
+
# removing any items that also appear in +other_set+. The order is preserved
|
57
|
+
# from the original set.
|
58
|
+
#
|
59
|
+
# set = Sorted::Set.new(['email', 'desc'])
|
60
|
+
# other_set = Sorted::Set.new(['phone', 'asc'])
|
61
|
+
# set - other_set #=> #<Sorted::Set:0x007fafde1ead80>
|
62
|
+
|
63
|
+
def -(other_set)
|
64
|
+
self.class.new.tap do |memo|
|
65
|
+
each do |a|
|
66
|
+
b = other_set.assoc(a.first)
|
67
|
+
next if b
|
68
|
+
memo << a
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
##
|
74
|
+
# Concatenation - Returns a new set built by concatenating the two sets
|
75
|
+
# together to produce a third set.
|
76
|
+
#
|
77
|
+
# set = Sorted::Set.new(['email', 'desc'])
|
78
|
+
# other_set = Sorted::Set.new(['phone', 'asc'])
|
79
|
+
# set + other_set #=> #<Sorted::Set:0x007fafde1ead80>
|
80
|
+
|
81
|
+
def +(other_set)
|
82
|
+
self.class.new(@ary + other_set.to_a)
|
83
|
+
end
|
84
|
+
|
85
|
+
##
|
86
|
+
# Append - Pushes the given order array on to the end of this set. This
|
87
|
+
# expression returns the set itself, so several appends may be chained
|
88
|
+
# together.
|
89
|
+
#
|
90
|
+
# set = Sorted::Set.new(['name', 'asc'])
|
91
|
+
# set << ['email', 'desc'] << ['phone', 'asc']
|
92
|
+
# set.to_a #=> [['name', 'asc'], ['email', 'desc'], ['phone', 'asc']]
|
93
|
+
|
94
|
+
def <<(ary)
|
95
|
+
@ary << ary
|
96
|
+
self
|
97
|
+
end
|
98
|
+
|
99
|
+
##
|
100
|
+
# Returns a new set containing all elements of self for which the given
|
101
|
+
# block returns a true value.
|
102
|
+
#
|
103
|
+
# If no block is given, an Enumerator is returned instead.
|
104
|
+
|
105
|
+
def select
|
106
|
+
return to_enum(:select) unless block_given?
|
107
|
+
self.class.new(@ary.select { |item| yield item })
|
108
|
+
end
|
109
|
+
|
110
|
+
##
|
111
|
+
# Returns a new set containing the items in self for which the given block
|
112
|
+
# is not true.
|
113
|
+
#
|
114
|
+
# If no block is given, an Enumerator is returned instead.
|
115
|
+
|
116
|
+
def reject
|
117
|
+
return to_enum(:reject) unless block_given?
|
118
|
+
self.class.new(@ary.reject { |item| yield item })
|
119
|
+
end
|
120
|
+
|
121
|
+
##
|
122
|
+
# Comparison - Returns an integer (-1, 0, or +1) if this array is less than,
|
123
|
+
# equal to, or greater than +other_set+.
|
124
|
+
|
125
|
+
def <=>(other_set)
|
126
|
+
@ary <=> other_set.to_a
|
127
|
+
end
|
128
|
+
|
129
|
+
##
|
130
|
+
# Returns a new set by removing duplicate values in self.
|
131
|
+
#
|
132
|
+
# If a block is given, it will use the return value of the block for comparison.
|
133
|
+
|
134
|
+
def uniq
|
135
|
+
return self.class.new(@ary.uniq) unless block_given?
|
136
|
+
self.class.new(@ary.uniq { |item| yield item })
|
137
|
+
end
|
138
|
+
|
139
|
+
##
|
140
|
+
# Searches through an array whose elements are also arrays comparing +item+
|
141
|
+
# with the first element of each contained array using +item+.==.
|
142
|
+
#
|
143
|
+
# Returns the first contained array that matches (that is, the first
|
144
|
+
# associated array), or nil if no match is found.
|
145
|
+
|
146
|
+
def assoc(item)
|
147
|
+
@ary.assoc(item)
|
148
|
+
end
|
149
|
+
|
150
|
+
##
|
151
|
+
# Returns key, order array pair at index +index+
|
152
|
+
|
153
|
+
def at(index)
|
154
|
+
@ary.at(index)
|
155
|
+
end
|
156
|
+
|
157
|
+
##
|
158
|
+
# Returns the underlying array for the set object.
|
159
|
+
#
|
160
|
+
# set = Sorted::Set.new(['name', 'asc'])
|
161
|
+
# set.to_a #=> [['name', 'asc']]
|
162
|
+
|
163
|
+
def to_a
|
164
|
+
@ary
|
165
|
+
end
|
166
|
+
|
167
|
+
##
|
168
|
+
# Returns the result of interpreting ary as an array of [key, value] pairs.
|
169
|
+
#
|
170
|
+
# set = Sorted::Set.new([['email', 'asc']])
|
171
|
+
# set.to_h #=> { 'email' => 'asc' }
|
172
|
+
|
173
|
+
def to_h
|
174
|
+
@ary.inject({}) { |a, e| a.merge(Hash[e[0], e[1]]) }
|
175
|
+
end
|
176
|
+
|
177
|
+
##
|
178
|
+
# Returns the number of elements in self. May be zero.
|
179
|
+
|
180
|
+
def length
|
181
|
+
@ary.length
|
182
|
+
end
|
183
|
+
alias_method :size, :length
|
184
|
+
|
185
|
+
private
|
186
|
+
|
187
|
+
# If the order of keys match upto the size of the set then flip them
|
188
|
+
def a(memo, other)
|
189
|
+
if keys == other.keys.take(keys.size)
|
190
|
+
keys.each do |order|
|
191
|
+
if other.keys.include?(order)
|
192
|
+
memo << [order, flip_direction(other.assoc(order).last)]
|
193
|
+
end
|
194
|
+
end
|
195
|
+
else
|
196
|
+
keys.each do |order|
|
197
|
+
if other.keys.include?(order)
|
198
|
+
memo << other.assoc(order)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# Add items from other that are common and not already added
|
205
|
+
def b(memo, other)
|
206
|
+
other.keys.each do |sort|
|
207
|
+
if keys.include?(sort) && !memo.keys.include?(sort)
|
208
|
+
memo << other.assoc(sort)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# Add items not in memo
|
214
|
+
def c(memo)
|
215
|
+
each do |order|
|
216
|
+
unless memo.keys.include?(order[0])
|
217
|
+
memo << order
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
# Add items from other not in memo
|
223
|
+
def d(memo, other)
|
224
|
+
other.each do |sort|
|
225
|
+
unless memo.keys.include?(sort[0])
|
226
|
+
memo << sort
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def flip_direction(direction)
|
232
|
+
case direction
|
233
|
+
when 'asc' then 'desc'
|
234
|
+
when 'desc' then 'asc'
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'sorted/set'
|
2
|
+
|
3
|
+
module Sorted
|
4
|
+
class SQLQuery
|
5
|
+
extend Parse
|
6
|
+
|
7
|
+
REGEXP = /(([a-z0-9._]+)\s([asc|desc]+)|[a-z0-9._]+)/i
|
8
|
+
|
9
|
+
def self.parse(raw)
|
10
|
+
split(raw, /,/) do |set, part|
|
11
|
+
m = part.match(REGEXP)
|
12
|
+
next unless m
|
13
|
+
set << parse_match(m)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.encode(set, quote_proc = ->(f) { f })
|
18
|
+
set.map { |a| "#{column(a[0], quote_proc)} #{a[1].upcase}" }.join(', ')
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.column(parts, quote_proc)
|
22
|
+
parts.split('.').map { |frag| quote_proc.call(frag) }.join('.')
|
23
|
+
end
|
24
|
+
private_class_method :column
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'sorted/set'
|
2
|
+
require 'sorted/parse'
|
3
|
+
|
4
|
+
module Sorted
|
5
|
+
class URIQuery
|
6
|
+
extend Parse
|
7
|
+
|
8
|
+
REGEXP = /(([a-z0-9._]+)_([asc|desc]+)|[a-z0-9._]+)/i
|
9
|
+
|
10
|
+
def self.parse(raw)
|
11
|
+
split(raw, /!/) do |set, part|
|
12
|
+
m = part.match(REGEXP)
|
13
|
+
next unless m
|
14
|
+
set << parse_match(m)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.encode(set)
|
19
|
+
set.map { |a| a.join('_') }.join('!')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/sorted/version.rb
CHANGED
data/sorted.gemspec
CHANGED
@@ -21,6 +21,6 @@ Gem::Specification.new do |s|
|
|
21
21
|
s.add_development_dependency 'rubocop', '>= 0.28'
|
22
22
|
|
23
23
|
s.files = `git ls-files`.split("\n")
|
24
|
-
s.executables = `git ls-files`.split("\n").map { |f| f =~ /^bin\/(.*)/ ? Regexp.last_match[1] : nil }.compact
|
24
|
+
s.executables = `git ls-files`.split("\n").map { |f| f =~ %r{/^bin\/(.*)/} ? Regexp.last_match[1] : nil }.compact
|
25
25
|
s.require_path = 'lib'
|
26
26
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
+
require 'sorted/elasticsearch_query'
|
4
|
+
|
3
5
|
describe Sorted::ElasticsearchQuery, 'decode' do
|
4
6
|
it 'should decode elasticsearch order hash to into set' do
|
5
7
|
json = [{ 'email' => { 'order' => 'desc' } }, { 'phone' => { 'order' => 'asc' } }, { 'name' => { 'order' => 'desc' } }]
|
data/spec/json_query_spec.rb
CHANGED
data/spec/params_query_spec.rb
CHANGED
data/spec/set_spec.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
+
require 'sorted/set'
|
4
|
+
|
3
5
|
describe Sorted::Set do
|
4
6
|
it 'should bring phone to first order importance but not toggle ascendance' do
|
5
7
|
orders = Sorted::Set.new([['email', 'asc'], ['phone', 'asc']])
|
@@ -69,7 +71,7 @@ describe Sorted::Set do
|
|
69
71
|
set = Sorted::Set.new([['email', 'asc']])
|
70
72
|
result = { 'email' => 'asc' }
|
71
73
|
|
72
|
-
expect(set.
|
74
|
+
expect(set.to_h).to eq(result)
|
73
75
|
end
|
74
76
|
|
75
77
|
it 'should return set when selecting items' do
|
@@ -83,4 +85,18 @@ describe Sorted::Set do
|
|
83
85
|
|
84
86
|
expect(set.reject { true }.class).to eq(Sorted::Set)
|
85
87
|
end
|
88
|
+
|
89
|
+
it 'should append value to set' do
|
90
|
+
set = Sorted::Set.new([['email', 'asc']])
|
91
|
+
set << ['name', 'asc'] << ['phone', 'asc']
|
92
|
+
|
93
|
+
expect(set.length).to eq(3)
|
94
|
+
expect(set.size).to eq(3)
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'should return a unique set' do
|
98
|
+
set = Sorted::Set.new([['email', 'asc'], ['email', 'asc']])
|
99
|
+
|
100
|
+
expect(set.uniq.to_a).to eq([['email', 'asc']])
|
101
|
+
end
|
86
102
|
end
|
data/spec/spec_helper.rb
CHANGED
data/spec/sql_query_spec.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
+
require 'sorted/sql_query'
|
4
|
+
|
3
5
|
describe Sorted::SQLQuery, 'decode' do
|
4
6
|
it 'should return a nice array from the order sql' do
|
5
7
|
sql = 'email ASC, phone ASC, name DESC'
|
@@ -41,9 +43,7 @@ describe Sorted::SQLQuery, 'encode' do
|
|
41
43
|
end
|
42
44
|
end
|
43
45
|
|
44
|
-
let(:quoter) {
|
45
|
-
->(frag) { FakeConnection.quote_column_name(frag) }
|
46
|
-
}
|
46
|
+
let(:quoter) { ->(frag) { FakeConnection.quote_column_name(frag) } }
|
47
47
|
|
48
48
|
it 'should properly escape sql column names' do
|
49
49
|
set = Sorted::Set.new([['users.name', 'desc']])
|
data/spec/uri_query_spec.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sorted
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rufus Post
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-12-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -83,6 +83,13 @@ files:
|
|
83
83
|
- README.md
|
84
84
|
- Rakefile
|
85
85
|
- lib/sorted.rb
|
86
|
+
- lib/sorted/elasticsearch_query.rb
|
87
|
+
- lib/sorted/json_query.rb
|
88
|
+
- lib/sorted/params_query.rb
|
89
|
+
- lib/sorted/parse.rb
|
90
|
+
- lib/sorted/set.rb
|
91
|
+
- lib/sorted/sql_query.rb
|
92
|
+
- lib/sorted/uri_query.rb
|
86
93
|
- lib/sorted/version.rb
|
87
94
|
- sorted.gemspec
|
88
95
|
- spec/elasticsearch_query_spec.rb
|