muster 0.0.2 → 0.0.3

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/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