sorted 2.0.3 → 2.1.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 +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
|