muster 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/Changes CHANGED
@@ -1,3 +1,7 @@
1
+ = 0.0.3
2
+
3
+ * Added Muster::Results to help filter strategy results values
4
+
1
5
  = 0.0.2
2
6
 
3
7
  * rack env['muster.query'] now supports indifferent access
data/lib/muster.rb CHANGED
@@ -1,3 +1,4 @@
1
1
  require 'muster/version'
2
2
  require 'muster/strategies'
3
+ require 'muster/results'
3
4
  require 'muster/rack'
@@ -0,0 +1,177 @@
1
+ require 'active_support/core_ext/array/wrap'
2
+ require 'active_support/core_ext/object/blank'
3
+ require 'active_support/hash_with_indifferent_access'
4
+
5
+ module Muster
6
+
7
+ # Query parsed results helper class
8
+ #
9
+ # As with most Muster classes, all hashes returned and options specified support with indifferent access.
10
+ #
11
+ # @param data [Hash] the hash of query string results after parsing to load
12
+ #
13
+ # @example
14
+ #
15
+ # data = { :select => [:id, :name, :created_at] }
16
+ # results = Muster::Results.new(data)
17
+ #
18
+ # # Filter values one at a time
19
+ # results.data #=> { 'select' => [:id, :name, :created_at] }
20
+ # results.filter(:select, :only => [:id, :name]) #=> [:id, :name]
21
+ # results.filter(:select, :except => :created_at) #=> [:id, :name]
22
+ # results.filter(:page, 1) #=> 1
23
+ # results.filter(:page) #=> KeyError: :page does not exist
24
+ #
25
+ # # Filter values in one pass
26
+ # results.add_filter(:select, :only => [:id, :name])
27
+ # results.add_filter(:page, 1)
28
+ # results.filtered #=> { 'select' => [:id, :name], 'page' => 1 }
29
+ class Results < ActiveSupport::HashWithIndifferentAccess
30
+
31
+ # @attribute [r] data
32
+ # @return [Hash] raw data specified during initialization
33
+ attr_reader :data
34
+
35
+ # @attribute [r] filters
36
+ # @return [Hash] filters specified using {#add_filter}
37
+ attr_reader :filters
38
+
39
+ # Create a new results instance
40
+ #
41
+ # @param data [Hash] the raw parsed query string data
42
+ # @param [Hash] options the options available for this method
43
+ # They'e aren't any options yet. :-)
44
+ #
45
+ # @example
46
+ #
47
+ # data = { :select => [:id, :name, :created_at] }
48
+ # results = Muster::Results.new(data)
49
+ def initialize( data, options={} )
50
+ super(data)
51
+
52
+ @data = data
53
+ @filters = {}
54
+ end
55
+
56
+ # Add a filter to be applied to the data in {#filtered} results
57
+ #
58
+ # @param key [String,Symbol] the key of the values in {#data} to filter
59
+ # @param [optional, Hash] options the options available for this filter
60
+ # @option options [optional] :only when specified, only return the matching values
61
+ # If you specify a single value, a single value will be returned
62
+ # If you specify an Array of values, an Array will be returned, even if only one value matches
63
+ # @option options [optional] :except return all values except the ones given here
64
+ # If the raw data value is a single value, a single value will be returned
65
+ # If the raw data value is an Array, and array will be returned, even if all values are excluded
66
+ # If nothing was excluded, the raw value is returned as-is
67
+ #
68
+ # If you pass a scalar value instead of a Hash into options, it will be treated as the default, just like
69
+ # Hash#fetch does.
70
+ #
71
+ # If you pass nothing into the options argument, it will return all values if the key exists or raise
72
+ # a KeyError like Hash#fetch.
73
+ #
74
+ # @return [void]
75
+ #
76
+ # @example
77
+ #
78
+ # results.add_filter(:select, :only => [:id, :name]
79
+ # results.add_filter(:select, :except => [:id]
80
+ # results.add_filter(:page, 1)
81
+ def add_filter( key, *options )
82
+ self.filters[key] = options
83
+ end
84
+
85
+ # Returns the raw data with all of the filters applied
86
+ #
87
+ # If no filters were added, this method simply returns self.
88
+ #
89
+ # @return [Muster::Results]
90
+ #
91
+ # @example
92
+ #
93
+ # results.add_filter(:select, :only => [:id, :name]
94
+ # results.add_dilter(:page, 1)
95
+ # results.filtered #=> { 'select' => [:id, :name], 'page' => 1 }
96
+ def filtered
97
+ return self if self.filters.empty?
98
+
99
+ filtered_results = self.filters.inject( {} ) do |results, (key, options)|
100
+ results[key] = self.filter( key, *options )
101
+
102
+ results
103
+ end
104
+
105
+ return self.class.new(filtered_results)
106
+ end
107
+
108
+ # Filters and returns the raw data values for the specifid key and options
109
+ #
110
+ # @param key [String,Symbol] the key of the values in {#data} to filter
111
+ # @param [optional, Hash] options the options available for this filter
112
+ # @option options [optional] :only when specified, only return the matching values
113
+ # If you specify a single value, a single value will be returned
114
+ # If you specify an Array of values, an Array will be returned, even if only one value matches
115
+ # @option options [optional] :except return all values except the ones given here
116
+ # If the raw data value is a single value, a single value will be returned
117
+ # If the raw data value is an Array, and array will be returned, even if all values are excluded
118
+ # If nothing was excluded, the raw value is returned as-is
119
+ #
120
+ # If you pass a scalar value instead of a Hash into options, it will be treated as the default, just like
121
+ # Hash#fetch does.
122
+ #
123
+ # If you pass nothing into the options argument, it will return all values if the key exists or raise
124
+ # a KeyError like Hash#fetch.
125
+ #
126
+ # @return [void]
127
+ #
128
+ # @example
129
+ #
130
+ # data = { :select => [:id, :name, :created_at]
131
+ # results = Muster::Results.new(data)
132
+ # results.filter(:select) #=> [:id, :name, :created_at]
133
+ # results.filter(:select, :only => :name) #=> :name
134
+ # results.filter(:select, :only => [:other, :name] #=> [:name]
135
+ # results.filter(:other, :default) #=> :default
136
+ # results.filter(:other) #=> KeyError
137
+ def filter( key, *options )
138
+ if options.present? && options.first.instance_of?(Hash)
139
+ options = options.first.with_indifferent_access
140
+
141
+ if options.has_key?(:only)
142
+ return filter_only_values( key, options[:only] )
143
+ elsif options.has_key?(:except)
144
+ return filter_excluded_values( key, options[:except] )
145
+ end
146
+ else
147
+ return self.fetch(key, *options)
148
+ end
149
+ end
150
+
151
+ private
152
+
153
+ def filter_excluded_values( key, excluded )
154
+ value = self[key]
155
+ excluded = Array.wrap(excluded)
156
+
157
+ if value.instance_of?(Array)
158
+ return value - excluded
159
+ elsif excluded.include?(value)
160
+ return nil
161
+ else
162
+ return value
163
+ end
164
+ end
165
+
166
+ def filter_only_values( key, allowed )
167
+ values = Array.wrap( self[key] )
168
+
169
+ if allowed.instance_of?(Array)
170
+ return values & allowed
171
+ elsif values.include?(allowed)
172
+ return allowed
173
+ end
174
+ end
175
+
176
+ end
177
+ end
@@ -1,6 +1,7 @@
1
1
  require 'active_support/core_ext/array/wrap'
2
2
  require 'active_support/hash_with_indifferent_access'
3
3
  require 'muster/strategies/hash'
4
+ require 'muster/results'
4
5
  require 'muster/strategies/filter_expression'
5
6
  require 'muster/strategies/pagination'
6
7
  require 'muster/strategies/sort_expression'
@@ -24,7 +25,7 @@ module Muster
24
25
  #
25
26
  # @param query_string [String] the query string to parse
26
27
  #
27
- # @return [Hash]
28
+ # @return [Muster::Results]
28
29
  #
29
30
  # @example
30
31
  #
@@ -34,7 +35,7 @@ module Muster
34
35
  def parse( query_string )
35
36
  pagination = self.parse_pagination( query_string )
36
37
 
37
- parameters = ActiveSupport::HashWithIndifferentAccess.new(
38
+ parameters = Muster::Results.new(
38
39
  :select => self.parse_select(query_string),
39
40
  :order => self.parse_order(query_string),
40
41
  :limit => pagination[:limit],
@@ -1,4 +1,5 @@
1
1
  require 'active_support/core_ext/array/wrap'
2
+ require 'muster/results'
2
3
  require 'muster/strategies/hash'
3
4
 
4
5
  module Muster
@@ -49,18 +50,20 @@ module Muster
49
50
  #
50
51
  # @param query_string [String] the query string to parse
51
52
  #
52
- # @return [Hash]
53
+ # @return [Muster::Results]
53
54
  #
54
55
  # @example
55
56
  #
56
57
  # results = strategy.parse('where=id:1&name:Bob') #=> { 'where' => {'id' => '1', 'name' => 'Bob'} }
57
58
  def parse( query_string )
58
- parameters = self.fields_to_parse(query_string)
59
+ parameters = Muster::Results.new( self.fields_to_parse(query_string) )
59
60
 
60
61
  parameters.each do |key, value|
61
62
  parameters[key] = self.separate_expressions(value)
62
63
  parameters[key] = self.separate_fields(parameters[key])
63
64
  end
65
+
66
+ return parameters
64
67
  end
65
68
 
66
69
  protected
@@ -45,7 +45,7 @@ module Muster
45
45
  #
46
46
  # @param query_string [String] the query string to parse
47
47
  #
48
- # @return [Hash]
48
+ # @return [Muster::Results]
49
49
  #
50
50
  # @example
51
51
  #
@@ -1,4 +1,5 @@
1
1
  require 'active_support/core_ext/hash/slice'
2
+ require 'muster/results'
2
3
  require 'muster/strategies/hash'
3
4
 
4
5
  module Muster
@@ -43,7 +44,7 @@ module Muster
43
44
  #
44
45
  # @param query_string [String] the query string to parse
45
46
  #
46
- # @return [Hash]
47
+ # @return [Muster::Results]
47
48
  #
48
49
  # @example
49
50
  #
@@ -61,10 +62,10 @@ module Muster
61
62
  parameters = parameters.merge(:pagination => {:page => page, :per_page => page_size}, :limit => page_size, :offset => offset)
62
63
 
63
64
  if self.fields.present?
64
- parameters = parameters.slice(*self.fields).with_indifferent_access
65
+ parameters = parameters.slice(*self.fields)
65
66
  end
66
67
 
67
- parameters
68
+ return Muster::Results.new(parameters)
68
69
  end
69
70
 
70
71
  protected
@@ -2,6 +2,7 @@ require 'active_support/core_ext/object/blank'
2
2
  require 'active_support/core_ext/hash/indifferent_access'
3
3
  require 'active_support/core_ext/array/wrap'
4
4
  require 'rack/utils'
5
+ require 'muster/results'
5
6
 
6
7
  module Muster
7
8
  module Strategies
@@ -43,13 +44,13 @@ module Muster
43
44
  #
44
45
  # @param query_string [String] the query string to parse
45
46
  #
46
- # @return [Hash]
47
+ # @return [Muster::Results]
47
48
  #
48
49
  # @example
49
50
  #
50
51
  # results = strategy.parse('name=value&choices=1&choices=1') #=> { 'name' => 'value', 'choices' => ['1', '2'] }
51
52
  def parse( query_string )
52
- self.fields_to_parse(query_string)
53
+ Muster::Results.new( self.fields_to_parse(query_string) )
53
54
  end
54
55
 
55
56
  protected
@@ -1,4 +1,4 @@
1
1
  module Muster
2
2
  # Current version of Muster
3
- VERSION = "0.0.2"
3
+ VERSION = "0.0.3"
4
4
  end
@@ -0,0 +1,87 @@
1
+ require 'spec_helper'
2
+
3
+ describe Muster::Results do
4
+ let(:data) { {:name => [1, 2, 3]} }
5
+ let(:options) { {} }
6
+ subject(:results) { Muster::Results.new(data, options) }
7
+
8
+ its(:data) { should eq data }
9
+
10
+ describe '#filter' do
11
+ context 'without options hash' do
12
+ it 'returns the same as fetch' do
13
+ results.filter(:name).should eq [1,2,3]
14
+ end
15
+
16
+ it 'supports a default value' do
17
+ results.filter(:bogons, :default).should eq :default
18
+ end
19
+
20
+ it 'throws exception without default' do
21
+ expect{ results.filter(:bogons) }.to raise_error(KeyError)
22
+ end
23
+ end
24
+
25
+ context 'with options hash' do
26
+ context 'with :except option' do
27
+ context 'with data value as Array' do
28
+ before { data[:name] = [1,2,3] }
29
+
30
+ it 'returns values not listed in :except as array' do
31
+ results.filter(:name, :except => [2]).should eq [1,3]
32
+ end
33
+
34
+ it 'returns values not listed in :except as scalar' do
35
+ results.filter(:name, :except => 2).should eq [1,3]
36
+ end
37
+ end
38
+
39
+ context 'with data value as scalar' do
40
+ before { data[:name] = 1 }
41
+
42
+ it 'returns value not listed in :except as scalar' do
43
+ results.filter(:name, :except => 2).should eq 1
44
+ end
45
+
46
+ it 'returns value not listed in :except as scalar' do
47
+ results.filter(:name, :except => 1).should be_nil
48
+ end
49
+ end
50
+ end
51
+
52
+ context 'with :only option' do
53
+ it 'returns value listed in :only as scalar' do
54
+ results.filter(:name, :only => 1).should eq 1
55
+ end
56
+
57
+ it 'returns values listed in :only as array' do
58
+ results.filter(:name, :only => [1,3]).should eq [1,3]
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ describe '#filtered' do
65
+ context 'without filters' do
66
+ it 'returns self' do
67
+ results.filtered.object_id.should == results.object_id
68
+ end
69
+
70
+ its(:filters) { should be_an_instance_of(Hash) }
71
+ its(:filters) { should be_empty }
72
+ end
73
+
74
+ context 'with filters' do
75
+ it 'applies filters to data' do
76
+ results.add_filter(:name, :only => [2, 3])
77
+ results.add_filter(:page, 1)
78
+ results.add_filter(:order, :only => [:first])
79
+ results.add_filter(:where, :only => :second)
80
+
81
+ filtered_results = results.filtered
82
+ filtered_results.should be_an_instance_of(Muster::Results)
83
+ filtered_results.should == {"name"=>[2, 3], "page"=>1, "order"=>[], "where"=>nil}
84
+ end
85
+ end
86
+ end
87
+ end
@@ -5,6 +5,11 @@ describe Muster::Strategies::ActiveRecord do
5
5
  subject { Muster::Strategies::ActiveRecord.new(options) }
6
6
 
7
7
  describe '#parse' do
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}}
10
+ subject.parse('').should be_an_instance_of(Muster::Results)
11
+ end
12
+
8
13
  context 'selects' do
9
14
  it 'returns single value as Array' do
10
15
  subject.parse('select=id')[:select].should == ['id']
@@ -9,6 +9,7 @@ describe Muster::Strategies::FilterExpression do
9
9
  context 'by default' do
10
10
  it 'returns empty hash for empty query string' do
11
11
  subject.parse('').should == {}
12
+ subject.parse('').should be_an_instance_of(Muster::Results)
12
13
  end
13
14
 
14
15
  it 'returns hash of all key/value pairs' do
@@ -9,6 +9,7 @@ describe Muster::Strategies::Hash do
9
9
  context 'by default' do
10
10
  it 'returns empty hash for empty query string' do
11
11
  subject.parse('').should == {}
12
+ subject.parse('').should be_an_instance_of(Muster::Results)
12
13
  end
13
14
 
14
15
  it 'returns hash of all key/value pairs' do
@@ -5,6 +5,11 @@ describe Muster::Strategies::Pagination do
5
5
  subject { Muster::Strategies::Pagination.new(options) }
6
6
 
7
7
  describe '#parse' do
8
+ it 'returns a Muster::Results instance' do
9
+ subject.parse('').should == {"pagination"=>{"page"=>1, "per_page"=>30}, "limit"=>30, "offset"=>nil}
10
+ subject.parse('').should be_an_instance_of(Muster::Results)
11
+ end
12
+
8
13
  context 'by default' do
9
14
  it 'returns default hash for empty query string' do
10
15
  subject.parse('').should == {'pagination' => {'page' => 1, 'per_page' => 30}, 'offset' => nil, 'limit' => 30}
@@ -9,6 +9,7 @@ describe Muster::Strategies::SortExpression do
9
9
  context 'by default' do
10
10
  it 'returns empty hash for empty query string' do
11
11
  subject.parse('').should == {}
12
+ subject.parse('').should be_an_instance_of(Muster::Results)
12
13
  end
13
14
 
14
15
  it 'returns hash of all key/value pairs' do
data/spec/spec_helper.rb CHANGED
@@ -1,5 +1,7 @@
1
- require 'simplecov'
2
- SimpleCov.start
1
+ if ENV['COVERAGE']
2
+ require 'simplecov'
3
+ SimpleCov.start
4
+ end
3
5
 
4
6
  require 'rack/mock'
5
7
  require 'muster'
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.2
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-10-05 00:00:00.000000000 Z
12
+ date: 2012-10-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
16
- requirement: &70144206747920 !ruby/object:Gem::Requirement
16
+ requirement: &70287572649780 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '3.0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70144206747920
24
+ version_requirements: *70287572649780
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rack
27
- requirement: &70144206744660 !ruby/object:Gem::Requirement
27
+ requirement: &70287572649080 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '1.4'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70144206744660
35
+ version_requirements: *70287572649080
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rspec
38
- requirement: &70144206743960 !ruby/object:Gem::Requirement
38
+ requirement: &70287572648560 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ~>
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: 2.11.0
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *70144206743960
46
+ version_requirements: *70287572648560
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: redcarpet
49
- requirement: &70144206743440 !ruby/object:Gem::Requirement
49
+ requirement: &70287572648080 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ~>
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '2.1'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *70144206743440
57
+ version_requirements: *70287572648080
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: simplecov
60
- requirement: &70144206742920 !ruby/object:Gem::Requirement
60
+ requirement: &70287572647660 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: '0'
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *70144206742920
68
+ version_requirements: *70287572647660
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: yard
71
- requirement: &70144206601360 !ruby/object:Gem::Requirement
71
+ requirement: &70287572647100 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ~>
@@ -76,7 +76,7 @@ dependencies:
76
76
  version: 0.8.2
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *70144206601360
79
+ version_requirements: *70287572647100
80
80
  description: Muster is a gem that turns query strings of varying formats into data
81
81
  structures suitable for easier consumption in AR/DataMapper scopes and queries.
82
82
  email:
@@ -95,6 +95,7 @@ files:
95
95
  - Rakefile
96
96
  - lib/muster.rb
97
97
  - lib/muster/rack.rb
98
+ - lib/muster/results.rb
98
99
  - lib/muster/strategies.rb
99
100
  - lib/muster/strategies/active_record.rb
100
101
  - lib/muster/strategies/filter_expression.rb
@@ -105,6 +106,7 @@ files:
105
106
  - lib/muster/version.rb
106
107
  - muster.gemspec
107
108
  - spec/muster/rack_spec.rb
109
+ - spec/muster/results_spec.rb
108
110
  - spec/muster/strategies/active_record_spec.rb
109
111
  - spec/muster/strategies/filter_expression_spec.rb
110
112
  - spec/muster/strategies/hash_spec.rb
@@ -137,6 +139,7 @@ specification_version: 3
137
139
  summary: Muster various query string formats into a more reusable data structure.
138
140
  test_files:
139
141
  - spec/muster/rack_spec.rb
142
+ - spec/muster/results_spec.rb
140
143
  - spec/muster/strategies/active_record_spec.rb
141
144
  - spec/muster/strategies/filter_expression_spec.rb
142
145
  - spec/muster/strategies/hash_spec.rb