muster 0.0.10 → 0.0.11
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 +13 -5
- data/.rubocop.yml +14 -0
- data/Changes +4 -0
- data/Rakefile +6 -1
- data/lib/muster/rack.rb +8 -11
- data/lib/muster/results.rb +22 -25
- data/lib/muster/strategies/active_record.rb +28 -33
- data/lib/muster/strategies/filter_expression.rb +51 -40
- data/lib/muster/strategies/hash.rb +12 -15
- data/lib/muster/strategies/joins_expression.rb +14 -17
- data/lib/muster/strategies/pagination.rb +24 -27
- data/lib/muster/strategies/rack.rb +10 -13
- data/lib/muster/strategies/sort_expression.rb +10 -13
- data/lib/muster/version.rb +2 -1
- data/muster.gemspec +14 -12
- data/spec/muster/rack_spec.rb +8 -8
- data/spec/muster/results_spec.rb +10 -10
- data/spec/muster/strategies/active_record_spec.rb +49 -40
- data/spec/muster/strategies/filter_expression_spec.rb +16 -18
- data/spec/muster/strategies/hash_spec.rb +11 -13
- data/spec/muster/strategies/joins_expression_spec.rb +14 -17
- data/spec/muster/strategies/pagination_spec.rb +9 -9
- data/spec/muster/strategies/sort_expression_spec.rb +3 -5
- metadata +37 -22
@@ -4,7 +4,6 @@ require 'muster/strategies/rack'
|
|
4
4
|
|
5
5
|
module Muster
|
6
6
|
module Strategies
|
7
|
-
|
8
7
|
# Query string parsing strategy with additional value handling options for separating values and uniqueness
|
9
8
|
#
|
10
9
|
# @example
|
@@ -12,7 +11,6 @@ module Muster
|
|
12
11
|
# strategy = Muster::Strategies::Hash.new(:unique_values => true, :value_separator => ',')
|
13
12
|
# results = strategy.parse('name=value&choices=1,2,1') #=> { 'name' => 'value', 'choices' => ['1', '2'] }
|
14
13
|
class Hash < Muster::Strategies::Rack
|
15
|
-
|
16
14
|
# @attribute [r] value_separator
|
17
15
|
# @return [String,RegEx] when specified, each field value will be split into multiple values using the specified separator
|
18
16
|
attr_reader :value_separator
|
@@ -34,7 +32,7 @@ module Muster
|
|
34
32
|
#
|
35
33
|
# strategy = Muster::Strategies::Hash.new(:fields => [:name, :state], :value_separator => '|')
|
36
34
|
# strategy = Muster::Strategies::Hash.new(:unique_values => false)
|
37
|
-
def initialize(
|
35
|
+
def initialize(options = {})
|
38
36
|
super
|
39
37
|
|
40
38
|
@unique_values = self.options.fetch(:unique_values, true)
|
@@ -48,29 +46,29 @@ module Muster
|
|
48
46
|
# @return [Muster::Results]
|
49
47
|
#
|
50
48
|
# @example
|
51
|
-
#
|
49
|
+
#
|
52
50
|
# results = strategy.parse('name=value&choices=1,2') #=> { 'name' => 'value', 'choices' => ['1', '2'] }
|
53
|
-
def parse(
|
51
|
+
def parse(query_string)
|
54
52
|
parameters = super
|
55
53
|
|
56
54
|
parameters.each do |key, value|
|
57
|
-
if
|
58
|
-
parameters[key] =
|
55
|
+
if value_separator.present?
|
56
|
+
parameters[key] = separate_values(value)
|
59
57
|
end
|
60
58
|
|
61
|
-
if
|
59
|
+
if unique_values == true && value.instance_of?(Array)
|
62
60
|
parameters[key].uniq!
|
63
61
|
end
|
64
62
|
end
|
65
63
|
|
66
|
-
|
64
|
+
parameters
|
67
65
|
end
|
68
66
|
|
69
67
|
protected
|
70
68
|
|
71
69
|
# Separates values into an Array of values using :values_separator
|
72
70
|
#
|
73
|
-
# @param
|
71
|
+
# @param values_string [String,Array] the original query string field value to separate
|
74
72
|
#
|
75
73
|
# @return [String,Array] String if a single value exists, Array otherwise
|
76
74
|
#
|
@@ -78,16 +76,15 @@ module Muster
|
|
78
76
|
#
|
79
77
|
# value = self.separate_values('1') #=> '1'
|
80
78
|
# value = self.separate_values('1,2') #=> ['1', '2']
|
81
|
-
def separate_values(
|
82
|
-
values = Array.wrap(
|
79
|
+
def separate_values(values_string)
|
80
|
+
values = Array.wrap(values_string)
|
83
81
|
|
84
82
|
values = values.map do |value|
|
85
|
-
value.split(
|
83
|
+
value.split(value_separator)
|
86
84
|
end.flatten
|
87
85
|
|
88
|
-
|
86
|
+
(values.size > 1) ? values : values_string
|
89
87
|
end
|
90
|
-
|
91
88
|
end
|
92
89
|
end
|
93
90
|
end
|
@@ -2,7 +2,6 @@ require 'muster/strategies/hash'
|
|
2
2
|
|
3
3
|
module Muster
|
4
4
|
module Strategies
|
5
|
-
|
6
5
|
# Query string parsing strategy with additional value handling options for separating filtering expressions
|
7
6
|
#
|
8
7
|
# @example
|
@@ -10,7 +9,6 @@ module Muster
|
|
10
9
|
# strategy = Muster::Strategies::JoinsExpression.new
|
11
10
|
# results = strategy.parse('joins=author.name,activity') #=> { 'joins' => [{'author' => 'name'}, 'activity'] }
|
12
11
|
class JoinsExpression < Muster::Strategies::Hash
|
13
|
-
|
14
12
|
# @attribute [r] expression_separator
|
15
13
|
# @return [String,RegEx] when specified, each field value will be split into multiple expressions using the specified separator
|
16
14
|
attr_reader :expression_separator
|
@@ -34,12 +32,12 @@ module Muster
|
|
34
32
|
#
|
35
33
|
# strategy = Muster::Strategies::FilterExpression.new
|
36
34
|
# strategy = Muster::Strategies::FilterExpression.new(:unique_values => true)
|
37
|
-
def initialize(
|
35
|
+
def initialize(options = {})
|
38
36
|
super
|
39
37
|
|
40
|
-
@expression_separator =
|
41
|
-
@field_separator =
|
42
|
-
@unique_values =
|
38
|
+
@expression_separator = options.fetch(:expression_separator, /,\s*/)
|
39
|
+
@field_separator = options.fetch(:field_separator, '.')
|
40
|
+
@unique_values = options.fetch(:unique_values, true)
|
43
41
|
end
|
44
42
|
|
45
43
|
# Processes a query string and returns an array of hashes that represent an ActiveRecord joins expression
|
@@ -49,18 +47,19 @@ module Muster
|
|
49
47
|
# @return [Muster::Results]
|
50
48
|
#
|
51
49
|
# @example
|
52
|
-
#
|
50
|
+
#
|
53
51
|
# results = strategy.parse('joins=author.name,activity') #=> { 'joins' => [{'author' => 'name'}, 'activity'] }
|
54
|
-
def parse(
|
55
|
-
parameters = Muster::Results.new(
|
52
|
+
def parse(query_string)
|
53
|
+
parameters = Muster::Results.new(fields_to_parse(query_string))
|
56
54
|
|
57
55
|
parameters.each do |key, value|
|
58
|
-
value = value.uniq.first if
|
59
|
-
parameters[key] =
|
56
|
+
value = value.uniq.first if unique_values == true && value.instance_of?(Array)
|
57
|
+
parameters[key] = make_nested_hash(value)
|
60
58
|
end
|
61
59
|
end
|
62
60
|
|
63
61
|
protected
|
62
|
+
|
64
63
|
# Converts the array that represents the value to a nested hash
|
65
64
|
#
|
66
65
|
# @param value [Array] the value to convert
|
@@ -70,15 +69,13 @@ module Muster
|
|
70
69
|
# @example
|
71
70
|
#
|
72
71
|
# value = self.make_nested_hash('activity,author.country.name') #=> ['activity', {'author' => {'country' => 'name'}}]
|
73
|
-
def make_nested_hash(
|
72
|
+
def make_nested_hash(value)
|
74
73
|
expressions = value.split(expression_separator)
|
75
|
-
expressions.map do |
|
76
|
-
fields =
|
77
|
-
fields[0..-2].reverse.reduce(fields.last) { |a,
|
74
|
+
expressions.map do |expression|
|
75
|
+
fields = expression.split(field_separator)
|
76
|
+
fields[0..-2].reverse.reduce(fields.last) { |a, e| { e => a } }
|
78
77
|
end
|
79
78
|
end
|
80
|
-
|
81
79
|
end
|
82
80
|
end
|
83
81
|
end
|
84
|
-
|
@@ -4,7 +4,6 @@ require 'muster/strategies/hash'
|
|
4
4
|
|
5
5
|
module Muster
|
6
6
|
module Strategies
|
7
|
-
|
8
7
|
# Query string parsing strategy with logic to handle pagination options
|
9
8
|
#
|
10
9
|
# @example
|
@@ -12,7 +11,6 @@ module Muster
|
|
12
11
|
# strategy = Muster::Strategies::Pagination.new
|
13
12
|
# results = strategy.parse('page=3&per_page=10') #=> { 'pagination' => {'page' => 3, 'per_page' => 10}, 'limit' => 10, 'offset' => 20 }
|
14
13
|
class Pagination < Muster::Strategies::Rack
|
15
|
-
|
16
14
|
# @attribute [r] default_page_size
|
17
15
|
# @return [Fixnum] when specified, will override the default page size of 30 when no page_size is parsed
|
18
16
|
attr_accessor :default_page_size
|
@@ -31,13 +29,14 @@ module Muster
|
|
31
29
|
#
|
32
30
|
# strategy = Muster::Strategies::Pagination.new
|
33
31
|
# strategy = Muster::Strategies::Pagination.new(:default_page_size => 10)
|
34
|
-
def initialize(
|
32
|
+
def initialize(options = {})
|
35
33
|
super
|
36
34
|
|
37
|
-
self.default_page_size =
|
38
|
-
|
39
|
-
|
40
|
-
|
35
|
+
self.default_page_size = options[:default_page_size].to_i
|
36
|
+
|
37
|
+
return unless default_page_size < 3
|
38
|
+
|
39
|
+
self.default_page_size = 30
|
41
40
|
end
|
42
41
|
|
43
42
|
# Processes a query string and returns a hash of its fields/values
|
@@ -47,25 +46,24 @@ module Muster
|
|
47
46
|
# @return [Muster::Results]
|
48
47
|
#
|
49
48
|
# @example
|
50
|
-
#
|
49
|
+
#
|
51
50
|
# results = strategy.parse('page=3&per_page=10') #=> { 'pagination' => {'page' => 3, 'per_page' => 10}, 'limit' => 10, 'offset' => 20 }
|
52
|
-
def parse(
|
53
|
-
parameters =
|
54
|
-
|
55
|
-
page = self.parse_page(parameters)
|
56
|
-
page_size = self.parse_page_size(parameters)
|
51
|
+
def parse(query_string)
|
52
|
+
parameters = parse_query_string(query_string)
|
57
53
|
|
54
|
+
page = parse_page(parameters)
|
55
|
+
page_size = parse_page_size(parameters)
|
58
56
|
|
59
57
|
offset = (page - 1) * page_size
|
60
58
|
offset = nil if offset < 1
|
61
59
|
|
62
|
-
parameters = parameters.merge(:pagination => {:page => page, :per_page => page_size}, :limit => page_size, :offset => offset)
|
63
|
-
|
64
|
-
if
|
65
|
-
parameters = parameters.slice(*
|
60
|
+
parameters = parameters.merge(:pagination => { :page => page, :per_page => page_size }, :limit => page_size, :offset => offset)
|
61
|
+
|
62
|
+
if fields.present?
|
63
|
+
parameters = parameters.slice(*fields)
|
66
64
|
end
|
67
65
|
|
68
|
-
|
66
|
+
Muster::Results.new(parameters)
|
69
67
|
end
|
70
68
|
|
71
69
|
protected
|
@@ -80,9 +78,9 @@ module Muster
|
|
80
78
|
#
|
81
79
|
# @example
|
82
80
|
#
|
83
|
-
# page =
|
84
|
-
# page =
|
85
|
-
def parse_page(
|
81
|
+
# page = parse_page(:page => 2) #=> 2
|
82
|
+
# page = parse_page(:page => nil) #=> 1
|
83
|
+
def parse_page(parameters)
|
86
84
|
page = parameters.delete(:page).to_i
|
87
85
|
page = 1 unless page > 0
|
88
86
|
page
|
@@ -98,15 +96,14 @@ module Muster
|
|
98
96
|
#
|
99
97
|
# @example
|
100
98
|
#
|
101
|
-
# page_size =
|
102
|
-
# page_size =
|
103
|
-
# page_size =
|
104
|
-
def parse_page_size(
|
99
|
+
# page_size = parse_page(:page_size => 10) #=> 10
|
100
|
+
# page_size = parse_page(:per_page => 10) #=> 10
|
101
|
+
# page_size = parse_page(:per_page => nil) #=> 30
|
102
|
+
def parse_page_size(parameters)
|
105
103
|
page_size = (parameters.delete(:page_size) || parameters.delete(:per_page)).to_i
|
106
|
-
page_size =
|
104
|
+
page_size = default_page_size unless page_size > 0
|
107
105
|
page_size
|
108
106
|
end
|
109
|
-
|
110
107
|
end
|
111
108
|
end
|
112
109
|
end
|
@@ -6,7 +6,6 @@ require 'muster/results'
|
|
6
6
|
|
7
7
|
module Muster
|
8
8
|
module Strategies
|
9
|
-
|
10
9
|
# Query string parsing strategy based on Rack::Utils#parse_query
|
11
10
|
#
|
12
11
|
# @example
|
@@ -14,7 +13,6 @@ module Muster
|
|
14
13
|
# strategy = Muster::Strategies::Rack.new
|
15
14
|
# results = strategy.parse('name=value&choices=1&choices=2') #=> { 'name' => 'value', 'choices' => ['1', '2'] }
|
16
15
|
class Rack
|
17
|
-
|
18
16
|
# @attribute [r] options
|
19
17
|
# @return [Hash] options specified during initialization
|
20
18
|
attr_reader :options
|
@@ -33,11 +31,11 @@ module Muster
|
|
33
31
|
#
|
34
32
|
# strategy = Muster::Strategies::Rack.new(:fields => [:name, :state])
|
35
33
|
# strategy = Muster::Strategies::Rack.new(:field => :name)
|
36
|
-
def initialize(
|
34
|
+
def initialize(options = {})
|
37
35
|
@options = options.with_indifferent_access
|
38
36
|
|
39
37
|
@fields = Array.wrap(@options[:field] || @options[:fields])
|
40
|
-
@fields.map!
|
38
|
+
@fields.map!(&:to_sym)
|
41
39
|
end
|
42
40
|
|
43
41
|
# Processes a query string and returns a hash of its fields/values
|
@@ -47,10 +45,10 @@ module Muster
|
|
47
45
|
# @return [Muster::Results]
|
48
46
|
#
|
49
47
|
# @example
|
50
|
-
#
|
48
|
+
#
|
51
49
|
# results = strategy.parse('name=value&choices=1&choices=1') #=> { 'name' => 'value', 'choices' => ['1', '2'] }
|
52
|
-
def parse(
|
53
|
-
Muster::Results.new(
|
50
|
+
def parse(query_string)
|
51
|
+
Muster::Results.new(fields_to_parse(query_string))
|
54
52
|
end
|
55
53
|
|
56
54
|
protected
|
@@ -64,7 +62,7 @@ module Muster
|
|
64
62
|
# @example
|
65
63
|
#
|
66
64
|
# fields = self.parse_query_string('name=value&choices=1&choices=1') #=> { 'name' => 'value', 'choices' => ['1', '2'] }
|
67
|
-
def parse_query_string(
|
65
|
+
def parse_query_string(query_string)
|
68
66
|
::Rack::Utils.parse_query(query_string).with_indifferent_access
|
69
67
|
end
|
70
68
|
|
@@ -75,16 +73,15 @@ module Muster
|
|
75
73
|
# If the :fields option was specified, only those fields will be returned. Otherwise, all fields will be returned.
|
76
74
|
#
|
77
75
|
# @return [Hash]
|
78
|
-
def fields_to_parse(
|
79
|
-
fields =
|
76
|
+
def fields_to_parse(query_string)
|
77
|
+
fields = parse_query_string(query_string)
|
80
78
|
|
81
79
|
if self.fields.present?
|
82
|
-
fields = fields.select{ |key,
|
80
|
+
fields = fields.select { |key, _| self.fields.include?(key.to_sym) }
|
83
81
|
end
|
84
82
|
|
85
|
-
|
83
|
+
fields.with_indifferent_access
|
86
84
|
end
|
87
|
-
|
88
85
|
end
|
89
86
|
end
|
90
87
|
end
|
@@ -3,7 +3,6 @@ require 'muster/strategies/hash'
|
|
3
3
|
|
4
4
|
module Muster
|
5
5
|
module Strategies
|
6
|
-
|
7
6
|
# Query string parsing strategy with additional value handling for sort orders
|
8
7
|
#
|
9
8
|
# @example
|
@@ -11,7 +10,6 @@ module Muster
|
|
11
10
|
# strategy = Muster::Strategies::SortExpression.new
|
12
11
|
# results = strategy.parse('sort=name:desc') #=> { 'sort' => 'name desc' }
|
13
12
|
class SortExpression < Muster::Strategies::Hash
|
14
|
-
|
15
13
|
# Processes a query string and returns a hash of its fields/values
|
16
14
|
#
|
17
15
|
# @param query_string [String] the query string to parse
|
@@ -19,15 +17,15 @@ module Muster
|
|
19
17
|
# @return [Hash]
|
20
18
|
#
|
21
19
|
# @example
|
22
|
-
#
|
20
|
+
#
|
23
21
|
# results = strategy.parse('order=name') #=> { 'order' => 'name asc' }
|
24
22
|
# results = strategy.parse('order=name:desc') #=> { 'order' => 'name desc' }
|
25
23
|
# results = strategy.parse('order=name,date:desc') #=> { 'order' => ['name asc', 'date desc'] }
|
26
|
-
def parse(
|
27
|
-
parameters
|
24
|
+
def parse(query_string)
|
25
|
+
parameters = super
|
28
26
|
|
29
27
|
parameters.each do |key, value|
|
30
|
-
parameters[key] =
|
28
|
+
parameters[key] = parse_sort_expression(value)
|
31
29
|
end
|
32
30
|
end
|
33
31
|
|
@@ -35,7 +33,7 @@ module Muster
|
|
35
33
|
|
36
34
|
# Separates the values into their field and direction
|
37
35
|
#
|
38
|
-
# @param
|
36
|
+
# @param expression [String] the value being parsed
|
39
37
|
#
|
40
38
|
# @return [String,Arrary] String if a single value, Array otherwise
|
41
39
|
#
|
@@ -43,17 +41,17 @@ module Muster
|
|
43
41
|
#
|
44
42
|
# value = self.parse_sort_expression('name:asc') #=> 'name asc'
|
45
43
|
# value = self.parse_sort_expression(['name:asc', 'date']) #=> ['name asc', 'date asc']
|
46
|
-
def parse_sort_expression(
|
47
|
-
values = Array.wrap(
|
44
|
+
def parse_sort_expression(expression)
|
45
|
+
values = Array.wrap(expression)
|
48
46
|
|
49
47
|
values = values.map do |value|
|
50
48
|
name, direction = value.split(':', 2)
|
51
|
-
direction =
|
49
|
+
direction = parse_direction(direction)
|
52
50
|
|
53
51
|
"#{name} #{direction}"
|
54
52
|
end.flatten
|
55
53
|
|
56
|
-
|
54
|
+
(values.size > 1) ? values : values.first
|
57
55
|
end
|
58
56
|
|
59
57
|
# Parse and normalize the sot expression direction
|
@@ -66,10 +64,9 @@ module Muster
|
|
66
64
|
#
|
67
65
|
# direction = self.parse_direction('ascending') #=> 'asc'
|
68
66
|
# direction = self.parse_direction('descending') #=> 'desc'
|
69
|
-
def parse_direction(
|
67
|
+
def parse_direction(direction)
|
70
68
|
direction.to_s.match(/^desc/i) ? 'desc' : 'asc'
|
71
69
|
end
|
72
|
-
|
73
70
|
end
|
74
71
|
end
|
75
72
|
end
|
data/lib/muster/version.rb
CHANGED
data/muster.gemspec
CHANGED
@@ -1,26 +1,28 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
require File.expand_path('../lib/muster/version', __FILE__)
|
3
3
|
|
4
|
+
# rubocop:disable Metrics/LineLength
|
4
5
|
Gem::Specification.new do |gem|
|
5
|
-
gem.authors = [
|
6
|
-
gem.email = [
|
7
|
-
gem.description =
|
8
|
-
gem.summary =
|
9
|
-
gem.homepage =
|
6
|
+
gem.authors = ['Christopher H. Laco']
|
7
|
+
gem.email = ['claco@chrislaco.com']
|
8
|
+
gem.description = 'Muster is a gem that turns query strings of varying formats into data structures suitable for easier consumption in AR/DataMapper scopes and queries.'
|
9
|
+
gem.summary = 'Muster various query string formats into a more reusable data structure.'
|
10
|
+
gem.homepage = 'https://github.com/claco/muster'
|
10
11
|
|
11
|
-
gem.files = `git ls-files`.split(
|
12
|
-
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
12
|
+
gem.files = `git ls-files`.split($ORS)
|
13
|
+
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
13
14
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
-
gem.name =
|
15
|
-
gem.require_paths = [
|
15
|
+
gem.name = 'muster'
|
16
|
+
gem.require_paths = ['lib']
|
16
17
|
gem.version = Muster::VERSION
|
17
18
|
|
18
19
|
gem.add_dependency 'activesupport', '>= 3.0'
|
19
|
-
gem.add_dependency 'rack',
|
20
|
+
gem.add_dependency 'rack', '~> 1.4'
|
20
21
|
|
21
22
|
gem.add_development_dependency 'pry'
|
22
|
-
gem.add_development_dependency 'rspec',
|
23
|
+
gem.add_development_dependency 'rspec', '~> 2.11.0'
|
23
24
|
gem.add_development_dependency 'redcarpet', '~> 2.1'
|
25
|
+
gem.add_development_dependency 'rubocop', '~> 0.33.0'
|
24
26
|
gem.add_development_dependency 'simplecov'
|
25
|
-
gem.add_development_dependency 'yard',
|
27
|
+
gem.add_development_dependency 'yard', '~> 0.8.2'
|
26
28
|
end
|