muster 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
data/Authors ADDED
@@ -0,0 +1,2 @@
1
+ - Chris Laco <claco@chrislaco.com?
2
+ - Nikica Jokić (neektza@gmail.com)
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
- { 'select' => ['id', 'name'], 'where' => {'status' => 'new'}, 'order' => 'name desc', 'limit' => 10, 'offset' => 20, 'pagination' => {:page => 3, :per_page => 10} }
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] ).where( query[:where] ).order( query[:order] ).offset( query[:offset] ).limit( query[:limit] )
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. Commit your changes (`git commit -am 'Added some feature'`)
131
- 4. Push to the branch (`git push origin my-new-feature`)
132
- 5. Create new Pull Request
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 => self.parse_select(query_string),
40
- :order => self.parse_order(query_string),
41
- :limit => pagination[:limit],
42
- :offset => pagination[:offset],
43
- :where => self.parse_where(query_string)
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
+
@@ -1,4 +1,4 @@
1
1
  module Muster
2
2
  # Current version of Muster
3
- VERSION = "0.0.6"
3
+ VERSION = "0.0.7"
4
4
  end
@@ -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.6
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: 2012-12-31 00:00:00.000000000 Z
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