muster 0.0.6 → 0.0.7
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.
- data/Authors +2 -0
- data/Changes +12 -0
- data/README.md +26 -7
- data/lib/muster/strategies/active_record.rb +23 -5
- data/lib/muster/strategies/joins_expression.rb +84 -0
- data/lib/muster/version.rb +1 -1
- data/spec/muster/strategies/active_record_spec.rb +19 -1
- data/spec/muster/strategies/joins_expression_spec.rb +118 -0
- metadata +6 -2
data/Authors
ADDED
data/Changes
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
= 0.0.7
|
2
|
+
|
3
|
+
* Added JoinsExpressions to ActiveRecord Strategy (neektza)
|
4
|
+
|
5
|
+
= 0.0.6
|
6
|
+
|
7
|
+
* Moved from OpenString to method_missing for dot notation on Results
|
8
|
+
|
9
|
+
= 0.0.5
|
10
|
+
|
11
|
+
* Added OpenStruct for dot notation on Results
|
12
|
+
|
1
13
|
= 0.0.4
|
2
14
|
|
3
15
|
* Rack middleware now returns Muster::Results
|
data/README.md
CHANGED
@@ -52,6 +52,12 @@ Returns options with support for dirctional indicators for use in sorting.
|
|
52
52
|
?order=name&order=age #=> { 'order' => ['name asc', 'age asc'] }
|
53
53
|
?order=name:asc&age:desc #=> { 'order' => ['name asc', 'age desc'] }
|
54
54
|
|
55
|
+
### JoinsExpression
|
56
|
+
|
57
|
+
Returns an ActiveRecord style array of 'joins' options.
|
58
|
+
|
59
|
+
?joins=author.name,activity #=> { 'joins' => [{'author' => 'name'}, 'activity'] }
|
60
|
+
|
55
61
|
### Pagination
|
56
62
|
|
57
63
|
Returns options to support pagination with logic for default page size, not-a-number checks and offset calculations.
|
@@ -64,12 +70,25 @@ Returns options to support pagination with logic for default page size, not-a-nu
|
|
64
70
|
|
65
71
|
Combines many of the strategies above to output ActiveRecord Query interface compatible options.
|
66
72
|
|
67
|
-
?select=id,name&where=status:new&order=name:desc&page=3&page_size=10
|
73
|
+
?select=id,name&where=status:new&order=name:desc&joins=author.name,activity&page=3&page_size=10
|
68
74
|
|
69
|
-
{
|
75
|
+
{
|
76
|
+
'select' => ['id', 'name'],
|
77
|
+
'where' => {'status' => 'new'},
|
78
|
+
'order' => 'name desc',
|
79
|
+
'limit' => 10,
|
80
|
+
'offset' => 20,
|
81
|
+
'pagination' => {:page => 3, :per_page => 10},
|
82
|
+
'joins' => [{'author' => 'name'}, 'activity']
|
83
|
+
}
|
70
84
|
|
71
85
|
query = env['muster.query']
|
72
|
-
Person.select( query[:select] )
|
86
|
+
Person.select( query[:select] )
|
87
|
+
.where( query[:where] )
|
88
|
+
.order( query[:order] )
|
89
|
+
.offset( query[:offset] )
|
90
|
+
.limit( query[:limit] )
|
91
|
+
.joins( query[:joins] )
|
73
92
|
|
74
93
|
If you are using WillPaginate, you can also pass in :pagination:
|
75
94
|
|
@@ -127,7 +146,7 @@ You can combine multiple strategies, and their results will be merged
|
|
127
146
|
|
128
147
|
1. Fork it from https://github.com/claco/muster
|
129
148
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
130
|
-
3.
|
131
|
-
4.
|
132
|
-
5.
|
133
|
-
|
149
|
+
3. Added an entry in Changes/Authors
|
150
|
+
4. Commit your changes (`git commit -am 'Added some feature'`)
|
151
|
+
5. Push to the branch (`git push origin my-new-feature`)
|
152
|
+
6. Create new Pull Request
|
@@ -5,6 +5,7 @@ require 'muster/results'
|
|
5
5
|
require 'muster/strategies/filter_expression'
|
6
6
|
require 'muster/strategies/pagination'
|
7
7
|
require 'muster/strategies/sort_expression'
|
8
|
+
require 'muster/strategies/joins_expression'
|
8
9
|
|
9
10
|
module Muster
|
10
11
|
module Strategies
|
@@ -36,11 +37,12 @@ module Muster
|
|
36
37
|
pagination = self.parse_pagination( query_string )
|
37
38
|
|
38
39
|
parameters = Muster::Results.new(
|
39
|
-
:select
|
40
|
-
:order
|
41
|
-
:limit
|
42
|
-
:offset
|
43
|
-
:where
|
40
|
+
:select => self.parse_select(query_string),
|
41
|
+
:order => self.parse_order(query_string),
|
42
|
+
:limit => pagination[:limit],
|
43
|
+
:offset => pagination[:offset],
|
44
|
+
:where => self.parse_where(query_string),
|
45
|
+
:joins => self.parse_joins(query_string)
|
44
46
|
)
|
45
47
|
|
46
48
|
parameters.regular_writer('pagination', pagination[:pagination].symbolize_keys)
|
@@ -114,6 +116,22 @@ module Muster
|
|
114
116
|
return results[:where] || {}
|
115
117
|
end
|
116
118
|
|
119
|
+
# Returns joins clauses for AR queries
|
120
|
+
#
|
121
|
+
# @param query_string [String] the original query string to parse join statements from
|
122
|
+
#
|
123
|
+
# @return [Hash]
|
124
|
+
#
|
125
|
+
# @example
|
126
|
+
#
|
127
|
+
# value = self.parse_joins('joins=authors') #=> {'joins' => 'authors'}
|
128
|
+
def parse_joins( query_string )
|
129
|
+
strategy = Muster::Strategies::JoinsExpression.new(:field => :joins)
|
130
|
+
results = strategy.parse(query_string)
|
131
|
+
|
132
|
+
return results[:joins] || {}
|
133
|
+
end
|
134
|
+
|
117
135
|
end
|
118
136
|
end
|
119
137
|
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'muster/strategies/hash'
|
2
|
+
|
3
|
+
module Muster
|
4
|
+
module Strategies
|
5
|
+
|
6
|
+
# Query string parsing strategy with additional value handling options for separating filtering expressions
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
#
|
10
|
+
# strategy = Muster::Strategies::JoinsExpression.new
|
11
|
+
# results = strategy.parse('joins=author.name,activity') #=> { 'joins' => [{'author' => 'name'}, 'activity'] }
|
12
|
+
class JoinsExpression < Muster::Strategies::Hash
|
13
|
+
|
14
|
+
# @attribute [r] expression_separator
|
15
|
+
# @return [String,RegEx] when specified, each field value will be split into multiple expressions using the specified separator
|
16
|
+
attr_reader :expression_separator
|
17
|
+
|
18
|
+
# @attribute [r] field_separator
|
19
|
+
# @return [String,RegEx] when specified, each expression will be split into multiple field/values using the specified separator
|
20
|
+
attr_reader :field_separator
|
21
|
+
|
22
|
+
# Create a new Hash parsing strategy
|
23
|
+
#
|
24
|
+
# @param [Hash] options the options available for this method
|
25
|
+
# @option options [optional,Array<Symbol>] :fields when specified, only parse the specified fields
|
26
|
+
# You may also use :field if you only intend to pass one field
|
27
|
+
# @option options [optional,String,RegEx] :expression_separator (/,\s*/) when specified, splits the query string value into multiple expressions
|
28
|
+
# You may pass the separator as a string or a regular expression
|
29
|
+
# @option options [optional,String,RegEx] :field_separator (.) when specified, splits the expression value into multiple field/values
|
30
|
+
# You may pass the separator as a string or a regular expression
|
31
|
+
# @option options [optional,Boolean] :unique_values (true) when true, ensures field values do not contain duplicates
|
32
|
+
#
|
33
|
+
# @example
|
34
|
+
#
|
35
|
+
# strategy = Muster::Strategies::FilterExpression.new
|
36
|
+
# strategy = Muster::Strategies::FilterExpression.new(:unique_values => true)
|
37
|
+
def initialize( options={} )
|
38
|
+
super
|
39
|
+
|
40
|
+
@expression_separator = self.options.fetch(:expression_separator, /,\s*/)
|
41
|
+
@field_separator = self.options.fetch(:field_separator, '.')
|
42
|
+
@unique_values = self.options.fetch(:unique_values, true)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Processes a query string and returns an array of hashes that represent an ActiveRecord joins expression
|
46
|
+
#
|
47
|
+
# @param query_string [String] the query string to parse
|
48
|
+
#
|
49
|
+
# @return [Muster::Results]
|
50
|
+
#
|
51
|
+
# @example
|
52
|
+
#
|
53
|
+
# results = strategy.parse('joins=author.name,activity') #=> { 'joins' => [{'author' => 'name'}, 'activity'] }
|
54
|
+
def parse( query_string )
|
55
|
+
parameters = Muster::Results.new( self.fields_to_parse(query_string) )
|
56
|
+
|
57
|
+
parameters.each do |key, value|
|
58
|
+
value = value.uniq.first if self.unique_values == true && value.instance_of?(Array)
|
59
|
+
parameters[key] = self.make_nested_hash(value)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
protected
|
64
|
+
# Converts the array that represents the value to a nested hash
|
65
|
+
#
|
66
|
+
# @param value [Array] the value to convert
|
67
|
+
#
|
68
|
+
# @return [String,Array] An array of nested a Hash / Hashes
|
69
|
+
#
|
70
|
+
# @example
|
71
|
+
#
|
72
|
+
# value = self.make_nested_hash('activity,author.country.name') #=> ['activity', {'author' => {'country' => 'name'}}]
|
73
|
+
def make_nested_hash( value )
|
74
|
+
expressions = value.split(expression_separator)
|
75
|
+
expressions.map do |e|
|
76
|
+
fields = e.split(field_separator)
|
77
|
+
fields[0..-2].reverse.reduce(fields.last) { |a, n| { n => a } }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
data/lib/muster/version.rb
CHANGED
@@ -6,7 +6,7 @@ describe Muster::Strategies::ActiveRecord do
|
|
6
6
|
|
7
7
|
describe '#parse' do
|
8
8
|
it 'returns a Muster::Results instance' do
|
9
|
-
subject.parse('').should == {"select"=>[], "order"=>[], "limit"=>30, "offset"=>nil, "where"=>{}, "pagination"=>{:page=>1, :per_page=>30}}
|
9
|
+
subject.parse('').should == {"select"=>[], "order"=>[], "limit"=>30, "offset"=>nil, "where"=>{}, "joins"=>{}, "pagination"=>{:page=>1, :per_page=>30}}
|
10
10
|
subject.parse('').should be_an_instance_of(Muster::Results)
|
11
11
|
end
|
12
12
|
|
@@ -49,6 +49,24 @@ describe Muster::Strategies::ActiveRecord do
|
|
49
49
|
end
|
50
50
|
end
|
51
51
|
|
52
|
+
context 'joins' do
|
53
|
+
it 'returns single value in Array' do
|
54
|
+
subject.parse('joins=author')[:joins].should eq ['author']
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'returns multiple values in Array' do
|
58
|
+
subject.parse('joins=author,voter')[:joins].should eq ['author', 'voter']
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'returns a nested hash of separated values' do
|
62
|
+
subject.parse('joins=author.country.name')[:joins].should eq [{'author' => { 'country' => 'name'}}]
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'returns an array of nested hashes' do
|
66
|
+
subject.parse('joins=author.country.name,activity.rule')[:joins].should eq [{'author' => { 'country' => 'name'}}, {'activity' => 'rule'}]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
52
70
|
context 'pagination' do
|
53
71
|
it 'returns default will paginate compatible pagination' do
|
54
72
|
subject.parse('')[:pagination].should == {:page => 1, :per_page => 30}
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Muster::Strategies::JoinsExpression do
|
4
|
+
let(:options) { {} }
|
5
|
+
subject { Muster::Strategies::JoinsExpression.new(options) }
|
6
|
+
|
7
|
+
describe '#parse' do
|
8
|
+
|
9
|
+
context 'by default' do
|
10
|
+
it 'returns empty hash for empty query string' do
|
11
|
+
subject.parse('').should == {}
|
12
|
+
subject.parse('').should be_an_instance_of(Muster::Results)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'returns hash of all key/value pairs' do
|
16
|
+
subject.parse('joins=author,author.foop').should eq({ "joins" => ["author", {"author" => "foop"}] })
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'hash supports indifferent key access' do
|
20
|
+
hash = subject.parse('joins=author,activity')
|
21
|
+
hash[:joins][0].should eq('author')
|
22
|
+
hash[:joins][1].should eq('activity')
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'combines multiple expressions into an array' do
|
26
|
+
subject.parse('joins=author,activity').should == { 'joins' => ['author', 'activity'] }
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'discards non unique values' do
|
30
|
+
subject.parse('joins=author&joins=author').should == { 'joins' => ['author'] }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'with :expression_separator option' do
|
35
|
+
context 'as regex' do
|
36
|
+
before do
|
37
|
+
options[:expression_separator] = /\|\s*/
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'converts comma separated value into Array' do
|
41
|
+
subject.parse('joins=author|activity').should == { 'joins' => ['author', 'activity'] }
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'ignores spaces after a separator' do
|
45
|
+
subject.parse('joins=author|%20 activity').should == { 'joins' => ['author', 'activity'] }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'as string' do
|
50
|
+
before do
|
51
|
+
options[:expression_separator] = '|'
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'converts comma separated value into Array' do
|
55
|
+
subject.parse('joins=author|activity|rule').should == { 'joins' => ['author', 'activity', 'rule'] }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'with :field_separator option' do
|
61
|
+
context 'as regex' do
|
62
|
+
before { options[:field_separator] = /\s*:\s*/ }
|
63
|
+
|
64
|
+
it 'splits field from values' do
|
65
|
+
subject.parse('joins=author:country:name').should == { 'joins' => [{'author' => {'country' => 'name'}}] }
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'ignores spaces after field' do
|
69
|
+
subject.parse('joins=author : country').should == { 'joins' => [{'author' => 'country'}] }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'as string' do
|
74
|
+
before { options[:field_separator] = ':' }
|
75
|
+
|
76
|
+
it 'converts comma separated value into Array' do
|
77
|
+
subject.parse('joins=author:country').should == { 'joins' => [{'author' => 'country'}] }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context 'with :fields option' do
|
83
|
+
context 'as symbol' do
|
84
|
+
before { options[:field] = :includes }
|
85
|
+
|
86
|
+
it 'fields returns expressions for the key specified' do
|
87
|
+
subject.parse('includes=author').should == { 'includes' => ['author'] }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context 'as Array of symbols' do
|
92
|
+
before { options[:fields] = [:includes, :joins] }
|
93
|
+
|
94
|
+
it 'fields returns expressions for the keys specified' do
|
95
|
+
subject.parse('joins=author&includes=activity').should == { 'joins' => ['author'], 'includes' => ['activity'] }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context 'as string' do
|
100
|
+
before { options[:field] = 'includes' }
|
101
|
+
|
102
|
+
it 'fields returns expressions for the key specified' do
|
103
|
+
subject.parse('includes=author').should == { 'includes' => ['author'] }
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context 'as Array of strings' do
|
108
|
+
before { options[:fields] = ['includes', 'joins'] }
|
109
|
+
|
110
|
+
it 'fields returns expressions for the keys specified' do
|
111
|
+
subject.parse('includes=author&joins=activity').should == { 'includes' => ['author'], 'joins' => ['activity'] }
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: muster
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2013-03-13 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -134,6 +134,7 @@ files:
|
|
134
134
|
- .gitignore
|
135
135
|
- .rspec
|
136
136
|
- .yardopts
|
137
|
+
- Authors
|
137
138
|
- Changes
|
138
139
|
- Gemfile
|
139
140
|
- LICENSE
|
@@ -146,6 +147,7 @@ files:
|
|
146
147
|
- lib/muster/strategies/active_record.rb
|
147
148
|
- lib/muster/strategies/filter_expression.rb
|
148
149
|
- lib/muster/strategies/hash.rb
|
150
|
+
- lib/muster/strategies/joins_expression.rb
|
149
151
|
- lib/muster/strategies/pagination.rb
|
150
152
|
- lib/muster/strategies/rack.rb
|
151
153
|
- lib/muster/strategies/sort_expression.rb
|
@@ -156,6 +158,7 @@ files:
|
|
156
158
|
- spec/muster/strategies/active_record_spec.rb
|
157
159
|
- spec/muster/strategies/filter_expression_spec.rb
|
158
160
|
- spec/muster/strategies/hash_spec.rb
|
161
|
+
- spec/muster/strategies/joins_expression_spec.rb
|
159
162
|
- spec/muster/strategies/pagination_spec.rb
|
160
163
|
- spec/muster/strategies/sort_expression_spec.rb
|
161
164
|
- spec/spec_helper.rb
|
@@ -189,6 +192,7 @@ test_files:
|
|
189
192
|
- spec/muster/strategies/active_record_spec.rb
|
190
193
|
- spec/muster/strategies/filter_expression_spec.rb
|
191
194
|
- spec/muster/strategies/hash_spec.rb
|
195
|
+
- spec/muster/strategies/joins_expression_spec.rb
|
192
196
|
- spec/muster/strategies/pagination_spec.rb
|
193
197
|
- spec/muster/strategies/sort_expression_spec.rb
|
194
198
|
- spec/spec_helper.rb
|