rack-si 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -2,4 +2,4 @@ source :rubygems
2
2
 
3
3
  gemspec :path => '.'
4
4
 
5
- gem 'herbalist', :path => '~/herbalist'
5
+ gem 'herbalist', :path => '~/herbalist' #:git => 'git://github.com/dkastner/herbalist.git', :branch => 'basic'
data/README.markdown CHANGED
@@ -0,0 +1,26 @@
1
+ # Rack::SI
2
+
3
+ Convert parameters into SI (metric) base units.
4
+
5
+ We all know the metric system is far superior to the imperial/customary system (except when performing mental math on lengths and volumes) so now there is a Rack middleware that will normalize all of your measurement input into SI!
6
+
7
+ # Installation
8
+
9
+ $ gem install rack-si
10
+
11
+
12
+ # config.ru
13
+ require 'rack/si'
14
+
15
+ use Rack::SI, options
16
+ run MyApp
17
+
18
+ # Configuration
19
+
20
+ Rack::SI accepts several configuration options:
21
+
22
+ * *env*: If true, the converted params will appear in the env['si.params'] hash. If set to a string, the converted params will appear in the env[custom\_string] hash.
23
+ * *basic*: If true, and dkastner-herbalist is installed, keep Herbalist from matching spelled-out numbers and fractions. This helps performance.
24
+ * *path*: A single path or array of paths defined as strings and/or regexes. If set, params are translated only for specified paths.
25
+ * *whitelist*: A list of params that should be converted. All others are ignored. If left blank, all params are converted (unless blacklisted).
26
+ * *blacklist*: A list of params that should *not* be converted. All others are converted (if whitelisted). If left blank, all params are converted.
data/lib/rack/si.rb CHANGED
@@ -1,33 +1,109 @@
1
1
  require 'herbalist'
2
- Herbalist.basic = true
3
2
 
4
3
  module Rack
5
4
 
6
5
  # A Rack middleware for converting params to base SI units
7
6
  #
8
- class Rack::SI
7
+ class SI
8
+ attr_accessor :app, :options
9
+
10
+ # These are the base SI units to which measurement params will be converted
9
11
  BASE_UNITS = [:metre, :metres, :meter, :meters, :litre, :litres, :liter, :liters, :joule, :joules, :gram, :grams, :watt, :watts]
10
12
 
11
- def initialize(app)
12
- @app = app
13
+ DEFAULT_OPTIONS = {
14
+ :env => false,
15
+ :basic => false,
16
+ :whitelist => [],
17
+ :blacklist => [],
18
+ :path => []
19
+ }
20
+
21
+ # use Rack::SI my_options
22
+ def initialize(app, options = {})
23
+ self.app = app
24
+ self.options = DEFAULT_OPTIONS.merge(options)
25
+
26
+ normalize_options
27
+ Herbalist.basic = true if Herbalist.respond_to?(:basic) && options[:basic]
28
+ end
29
+
30
+ # Make sure options are in the right format
31
+ def normalize_options
32
+ self.options[:whitelist] = self.options[:whitelist].map(&:to_s)
33
+ self.options[:blacklist] = self.options[:blacklist].map(&:to_s)
34
+ self.options[:path] = [self.options[:path]] unless self.options[:path].is_a?(Array)
35
+ end
36
+
37
+ # Called on each request
38
+ def call(env, options = {})
39
+ req = Request.new(env)
40
+ convert_params(env, req) if path_matches?(req)
41
+ app.call(env)
42
+ end
43
+
44
+ def path_matches?(req)
45
+ options[:path].empty? || options[:path].find do |path|
46
+ (path.is_a?(String) && req.path == path) ||
47
+ (path.is_a?(Regexp) && req.path =~ path)
48
+ end
13
49
  end
14
50
 
15
- def call(env)
16
- convert_params(env)
17
- @app.call(env)
51
+ def convert_params(env, req)
52
+ if options[:env]
53
+ convert_params_in_hash(env, req)
54
+ else
55
+ convert_params_in_situ(env, req)
56
+ end
18
57
  end
19
58
 
20
- def convert_params(env)
21
- params = ::Rack::Request.new(env).params
22
- env['si.params'] = params.inject({}) do |hsh, (name, value)|
23
- if measurement = Herbalist.parse(value)
24
- value = normalize(measurement)
59
+ # Convert parameters, but put them in a special hash and
60
+ # leave the regular Rack ENV params alone.
61
+ # You can specify the name of the env[] key that stores
62
+ # the params by configuring the app with the :env option
63
+ def convert_params_in_hash(env, req)
64
+ params = req.params
65
+ hash_name = options[:env].is_a?(String) ? options[:env] : 'si.params'
66
+ env[hash_name] = params.inject({}) do |hsh, (name, value)|
67
+ herbalize(hsh, name, value)
68
+ end
69
+ end
70
+
71
+ # Convert parameters "in place" - that is, modify all the
72
+ # params in env so that every other middleware sees
73
+ # the changes
74
+ def convert_params_in_situ(env, req)
75
+ env['si.original_params'] = req.params.dup
76
+ [:GET, :POST].each do |method|
77
+ hsh = req.send(method)
78
+ hsh.each do |name, value|
79
+ herbalize(hsh, name, value)
25
80
  end
26
- hsh[name] = value
27
- hsh
28
81
  end
29
82
  end
30
83
 
84
+ # Convert a parameter with Herbalist
85
+ def herbalize(hsh, name, value)
86
+ if herbalizable?(name) && measurement = Herbalist.parse(value)
87
+ hsh[name] = normalize(measurement)
88
+ end
89
+ hsh
90
+ end
91
+
92
+ # Decides if a param should be converted with Herbalist
93
+ def herbalizable?(param)
94
+ whitelisted?(param) && !blacklisted?(param)
95
+ end
96
+
97
+ def whitelisted?(param)
98
+ options[:whitelist].empty? || options[:whitelist].include?(param)
99
+ end
100
+
101
+ def blacklisted?(param)
102
+ !options[:blacklist].empty? && options[:blacklist].include?(param)
103
+ end
104
+
105
+ # Convert a param to a base unit using Alchemist's
106
+ # conversion table
31
107
  def normalize(measurement)
32
108
  group_name = Alchemist.conversion_table.keys.find do |k|
33
109
  Alchemist.conversion_table[k].include?(measurement.unit_name)
@@ -1,5 +1,5 @@
1
1
  module Rack
2
2
  class SI
3
- VERSION = '0.0.2'
3
+ VERSION = '0.1.0'
4
4
  end
5
5
  end
data/spec/si_spec.rb CHANGED
@@ -5,50 +5,216 @@ describe Rack::SI do
5
5
  include Rack::Test::Methods
6
6
 
7
7
  def app
8
- Rack::SI.new lambda { |env| [200, {}, []] }
8
+ Rack::SI.new lambda { |env| [200, {}, []] }, options
9
9
  end
10
10
 
11
11
  describe '#call' do
12
- it 'returns control to the app' do
13
- get '/foo'
14
- last_response.should be_ok
12
+ context 'with default options' do
13
+ let(:options) { {} }
14
+
15
+ it 'returns control to the app' do
16
+ get '/foo'
17
+ last_response.should be_ok
18
+ end
19
+
20
+ it 'ignores missing parameters' do
21
+ post '/bar'
22
+ last_request.params.should == {}
23
+ end
24
+
25
+ it 'does not convert non-unit params' do
26
+ post '/automobile_trips', {
27
+ 'destination' => '2,3',
28
+ 'distance' => 233
29
+ }
30
+ last_request.params.should ==({
31
+ 'destination' => '2,3',
32
+ 'distance' => '233'
33
+ })
34
+ end
35
+
36
+ it 'converts any unit-ed params to base SI units' do
37
+ post '/automobile_trips', {
38
+ 'destination' => '2,3',
39
+ 'distance' => '233 miles'
40
+ }
41
+ last_request.params['destination'].should == '2,3'
42
+ last_request.params['distance'].to_f.should == 374977.152
43
+ end
44
+
45
+ it 'keeps the original params close by' do
46
+ post '/automobile_trips', {
47
+ 'destination' => '2,3',
48
+ 'distance' => '233 miles'
49
+ }
50
+ last_request.env['si.original_params']['destination'].should == '2,3'
51
+ last_request.env['si.original_params']['distance'].should == '233 miles'
52
+ end
53
+
54
+ it 'converts compound measurements' do
55
+ pending
56
+ #post '/automobile_trips', {
57
+ #'fuel_efficiency' => '54 miles per gallon'
58
+ #}
59
+ #last_request.env['si.params']['fuel_efficiency'].to_f.should == 86904.576
60
+ end
61
+
62
+ it 'converts dates' do
63
+ pending
64
+ end
15
65
  end
16
66
 
17
- it 'ignores missing parameters' do
18
- post '/bar'
19
- last_request.env['si.params'].should == {}
67
+ context 'with env option' do
68
+ let(:options) { { :env => true } }
69
+
70
+ it 'saves SI params to a separate ENV hash if desired' do
71
+ post '/automobile_trips', {
72
+ 'destination' => '2,3',
73
+ 'distance' => '233 miles'
74
+ }
75
+ last_request.params.should == {
76
+ 'destination' => '2,3',
77
+ 'distance' => '233 miles'
78
+ }
79
+ last_request.env['si.params']['destination'].should be_nil
80
+ last_request.env['si.params']['distance'].to_f.should == 374977.152
81
+ end
20
82
  end
21
83
 
22
- it 'does not convert non-unit params' do
23
- post '/automobile_trips', {
24
- 'destination' => '2,3',
25
- 'distance' => 233
26
- }
27
- last_request.env['si.params'].should ==({
28
- 'destination' => '2,3',
29
- 'distance' => '233'
30
- })
84
+ context 'with custom env option' do
85
+ let(:options) { { :env => 'my.hash' } }
86
+
87
+ it 'saves SI params to a custom ENV hash if desired' do
88
+ post '/automobile_trips', {
89
+ 'destination' => '2,3',
90
+ 'distance' => '233 miles'
91
+ }
92
+ last_request.params.should == {
93
+ 'destination' => '2,3',
94
+ 'distance' => '233 miles'
95
+ }
96
+ last_request.env['my.hash']['destination'].should be_nil
97
+ last_request.env['my.hash']['distance'].to_f.should == 374977.152
98
+ end
31
99
  end
32
100
 
33
- it 'converts any unit-ed params to base SI units' do
34
- post '/automobile_trips', {
35
- 'destination' => '2,3',
36
- 'distance' => '233 miles'
37
- }
38
- last_request.env['si.params']['destination'].should == '2,3'
39
- last_request.env['si.params']['distance'].to_f.should == 374977.152
101
+ context 'with whitelist' do
102
+ let(:options) { { :whitelist => %w{distance} } }
103
+
104
+ it 'converts only whitelisted SI params' do
105
+ post '/automobile_trips', {
106
+ 'destination' => '2,3',
107
+ 'distance' => '233 miles',
108
+ 'weight' => '2013 lbs'
109
+ }
110
+ last_request.params.should == {
111
+ 'destination' => '2,3',
112
+ 'distance' => '374977.152',
113
+ 'weight' => '2013 lbs'
114
+ }
115
+ end
40
116
  end
41
117
 
42
- #it 'converts compound measurements' do
43
- #post '/automobile_trips', {
44
- #'fuel_efficiency' => '54 miles per gallon'
45
- #}
46
- #last_request.env['si.params']['fuel_efficiency'].to_f.should == 86904.576
47
- #end
118
+ context 'with blacklist' do
119
+ let(:options) { { :blacklist => %w{distance} } }
120
+
121
+ it 'does not convert blacklisted SI params' do
122
+ post '/automobile_trips', {
123
+ 'destination' => '2,3',
124
+ 'distance' => '233 miles',
125
+ 'weight' => '2013 lbs'
126
+ }
127
+ last_request.params.should == {
128
+ 'destination' => '2,3',
129
+ 'distance' => '233 miles',
130
+ 'weight' => '913081.4408100001'
131
+ }
132
+ end
133
+ end
48
134
 
49
- #it 'converts dates' do
135
+ context 'with path option' do
136
+ let(:options) { { :path => ['/accepted', /also_accepted/] } }
50
137
 
51
- #end
138
+ it 'converts params for a path matching a string' do
139
+ post '/accepted', {
140
+ 'distance' => '233 miles'
141
+ }
142
+ last_request.params.should == {
143
+ 'distance' => '374977.152'
144
+ }
145
+ end
146
+ it 'converts params for a path matching a regex' do
147
+ post '/also_accepted', {
148
+ 'distance' => '233 miles'
149
+ }
150
+ last_request.params.should == {
151
+ 'distance' => '374977.152'
152
+ }
153
+ end
154
+ it 'skips params conversion for a non-matching path' do
155
+ post '/fogettaboutit', {
156
+ 'distance' => '233 miles'
157
+ }
158
+ last_request.params.should == {
159
+ 'distance' => '233 miles'
160
+ }
161
+ end
162
+ end
163
+ end
164
+
165
+ describe '#herbalizable?' do
166
+ let(:options) { {} }
167
+ let(:si) { app }
168
+
169
+ it 'returns true if param is whitelisted and not blacklisted' do
170
+ si.stub!(:whitelisted?).and_return true
171
+ si.stub!(:blacklisted?).and_return false
172
+ si.herbalizable?('foo').should be_true
173
+ end
174
+ it 'returns false if param is not whitelisted' do
175
+ si.stub!(:whitelisted?).and_return false
176
+ si.herbalizable?('foo').should be_false
177
+ end
178
+ it 'returns false if param is blacklisted' do
179
+ si.stub!(:whitelisted?).and_return true
180
+ si.stub!(:blacklisted?).and_return true
181
+ si.herbalizable?('foo').should be_false
182
+ end
183
+ end
184
+
185
+ describe '#whitelisted?' do
186
+ let(:options) { {} }
187
+ let(:si) { app }
188
+
189
+ it 'returns true if the whitelist is empty' do
190
+ si.options[:whitelist] = []
191
+ si.whitelisted?('foo').should be_true
192
+ end
193
+ it 'returns true if the param is in the whitelist' do
194
+ si.options[:whitelist] = ['foo']
195
+ si.whitelisted?('foo').should be_true
196
+ end
197
+ it 'returns false if the param is not in the whitelist' do
198
+ si.options[:whitelist] = ['foo']
199
+ si.whitelisted?('bar').should be_false
200
+ end
52
201
  end
53
- end
54
202
 
203
+ describe '#blacklisted?' do
204
+ let(:options) { {} }
205
+ let(:si) { app }
206
+
207
+ it 'returns false if the blacklist is empty' do
208
+ si.options[:blacklist] = []
209
+ si.blacklisted?('foo').should be_false
210
+ end
211
+ it 'returns true if the param is in the blacklist' do
212
+ si.options[:blacklist] = ['foo']
213
+ si.blacklisted?('foo').should be_true
214
+ end
215
+ it 'returns false if the param is not in the blacklist' do
216
+ si.options[:blacklist] = ['foo']
217
+ si.blacklisted?('bar').should be_false
218
+ end
219
+ end
220
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-si
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
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-04-30 00:00:00.000000000Z
12
+ date: 2012-05-01 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: herbalist
16
- requirement: &2158550580 !ruby/object:Gem::Requirement
16
+ requirement: &2165760380 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *2158550580
24
+ version_requirements: *2165760380
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rack
27
- requirement: &2166031240 !ruby/object:Gem::Requirement
27
+ requirement: &2165759960 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *2166031240
35
+ version_requirements: *2165759960
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rack-test
38
- requirement: &2166030820 !ruby/object:Gem::Requirement
38
+ requirement: &2165759540 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *2166030820
46
+ version_requirements: *2165759540
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: rspec
49
- requirement: &2166030400 !ruby/object:Gem::Requirement
49
+ requirement: &2163226240 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,7 +54,7 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *2166030400
57
+ version_requirements: *2163226240
58
58
  description: Choose params that are converted with Herbalist to base SI units (meters,
59
59
  grams, etc)
60
60
  email: