optionsful 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +6 -2
- data/Rakefile +11 -0
- data/lib/baurets/optionsful.rb +93 -0
- data/lib/tasks/optionsful.rake +4 -0
- data/rails/init.rb +1 -1
- data/spec/optionsful_spec.rb +115 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +67 -0
- metadata +10 -5
- data/lib/optionsful.rb +0 -89
data/README.textile
CHANGED
@@ -34,9 +34,13 @@ Link: <http://baurets.net/api/resources>; type=text/html; rel=help
|
|
34
34
|
h2. INSTALLATION:
|
35
35
|
|
36
36
|
# Change directory to your Ruby on Rails web application,
|
37
|
-
#
|
37
|
+
# Add gem dependency to @config/environment.rb@:
|
38
38
|
<pre>
|
39
|
-
|
39
|
+
config.gem "optionsful"
|
40
|
+
</pre>
|
41
|
+
# To install it, run:
|
42
|
+
<pre>
|
43
|
+
$ gem install optionsful
|
40
44
|
</pre>
|
41
45
|
# Have fun!
|
42
46
|
|
data/Rakefile
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
module Baurets
|
2
|
+
|
3
|
+
class Optionsful
|
4
|
+
|
5
|
+
def initialize(app)
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
unless env["REQUEST_METHOD"] == "OPTIONS"
|
11
|
+
@app.call(env)
|
12
|
+
else
|
13
|
+
extract_options_information(env)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def extract_options_information(env)
|
20
|
+
[204, {"Allow" => extract_allowed_methods(env), "Link" => build_help_link}, ""]
|
21
|
+
end
|
22
|
+
|
23
|
+
def extract_allowed_methods(env)
|
24
|
+
# do routing introspection:
|
25
|
+
routes = do_routing_introspection
|
26
|
+
# do request path investigation
|
27
|
+
route_guess = guess_route(routes, env["PATH_INFO"])
|
28
|
+
allows = ""
|
29
|
+
# do the matches:
|
30
|
+
routes.each do |route|
|
31
|
+
if route.first == route_guess
|
32
|
+
allows += route[1].to_s.upcase! + "|"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
allows = allows.split("|").join(", ")
|
36
|
+
end
|
37
|
+
|
38
|
+
def build_help_link
|
39
|
+
#PENDING
|
40
|
+
"<http://baurets.net/api/resources>; type=text/html; rel=help"
|
41
|
+
end
|
42
|
+
|
43
|
+
def guess_route(routes, path)
|
44
|
+
parts = prepare_request_path(path)
|
45
|
+
guess = []
|
46
|
+
index = 0
|
47
|
+
parts.each do |part|
|
48
|
+
if is_part_static?(routes, index, part)
|
49
|
+
guess << part
|
50
|
+
else
|
51
|
+
guess << :dynamic
|
52
|
+
end
|
53
|
+
index += 1
|
54
|
+
end
|
55
|
+
guess
|
56
|
+
end
|
57
|
+
|
58
|
+
def is_part_static?(routes, index, value)
|
59
|
+
routes.each do |route|
|
60
|
+
if route[0][index] == value
|
61
|
+
return true
|
62
|
+
end
|
63
|
+
end
|
64
|
+
return false
|
65
|
+
end
|
66
|
+
|
67
|
+
def do_routing_introspection
|
68
|
+
routes = []
|
69
|
+
ActionController::Routing::Routes.routes.each do |route|
|
70
|
+
static_path = []
|
71
|
+
route.segments.each do |segment|
|
72
|
+
if segment.kind_of?(ActionController::Routing::StaticSegment)
|
73
|
+
static_path << segment.value if (segment.respond_to?(:value) && segment.value != "/")
|
74
|
+
elsif segment.kind_of?(ActionController::Routing::DynamicSegment)
|
75
|
+
#TODO ignoring (.:format), think about it:
|
76
|
+
static_path << :dynamic unless (segment.respond_to?(:key) && segment.key == :format)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
routes << [static_path, route.conditions[:method]] unless route.conditions[:method].nil?
|
80
|
+
end
|
81
|
+
routes
|
82
|
+
end
|
83
|
+
|
84
|
+
def prepare_request_path(path)
|
85
|
+
path = path[0..(path.rindex('.')-1)] if path.include?('.') #TODO ignoring (.:format), think about it
|
86
|
+
path_parts = path.split("/")
|
87
|
+
path_parts.delete("")
|
88
|
+
path_parts
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
data/rails/init.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
# Adding Optionsful to the Rack middleware stack:
|
2
|
-
ActionController::Dispatcher.middleware.use
|
2
|
+
ActionController::Dispatcher.middleware.use Baurets::Optionsful
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
|
4
|
+
describe "Baurets::Optionsful," do
|
5
|
+
|
6
|
+
include Rack::Test::Methods
|
7
|
+
|
8
|
+
before(:all) do
|
9
|
+
ActionController::Routing::Routes.draw do |map|
|
10
|
+
map.resources :posts, :has_many => :comments
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
describe "as a Rack middleware" do
|
16
|
+
|
17
|
+
it "is a Ruby object that responds to call;" do
|
18
|
+
assert Baurets::Optionsful.new(app).respond_to? :call
|
19
|
+
end
|
20
|
+
|
21
|
+
it "takes exactly one argument, (the environment) and returns an Array;" do
|
22
|
+
response = Baurets::Optionsful.new(app).call(mock_env({"REQUEST_METHOD" => "OPTIONS", "PATH_INFO" => "/posts"}))
|
23
|
+
assert response.kind_of?(Array)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "the returned Array must have exactly three values: the status, the headers and the body;" do
|
27
|
+
response = Baurets::Optionsful.new(app).call(mock_env({"REQUEST_METHOD" => "OPTIONS", "PATH_INFO" => "/posts"}))
|
28
|
+
assert response.size.should == 3
|
29
|
+
assert response[0].kind_of? Fixnum
|
30
|
+
assert response[1].kind_of? Hash
|
31
|
+
assert response[2].kind_of? String
|
32
|
+
end
|
33
|
+
|
34
|
+
it "must be nice, acting somewhere on a Rack middleware stack." do
|
35
|
+
response = fake_app.call(mock_env({"REQUEST_METHOD" => "OPTIONS", "PATH_INFO" => "/posts"}))
|
36
|
+
assert response.size.should == 3
|
37
|
+
assert response[0].kind_of? Fixnum
|
38
|
+
assert response[0].should == 204
|
39
|
+
assert response[1].kind_of? Hash
|
40
|
+
assert response[1]["Allow"]
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
describe "to collect the allowed HTTP methods" do
|
47
|
+
|
48
|
+
describe "overrides Rails routing recognition " do
|
49
|
+
|
50
|
+
describe "must understand the default Rails resource routing: " do
|
51
|
+
|
52
|
+
it "the index action displays a list of all posts in response of a GET request;" do
|
53
|
+
response = http_options_request("/posts")
|
54
|
+
assert response.kind_of?(Array)
|
55
|
+
assert allows?(response[1], "GET")
|
56
|
+
end
|
57
|
+
|
58
|
+
it "the new action return from a GET request an HTML form for creating a new post;" do
|
59
|
+
response = http_options_request("/posts/new")
|
60
|
+
assert response.kind_of?(Array)
|
61
|
+
assert allows?(response[1], "GET")
|
62
|
+
end
|
63
|
+
|
64
|
+
it "the create action uses POST to create a new post instance;" do
|
65
|
+
response = http_options_request("/posts")
|
66
|
+
assert response.kind_of?(Array)
|
67
|
+
assert allows?(response[1], "POST")
|
68
|
+
end
|
69
|
+
|
70
|
+
it "the show action display a specific post in response of a GET request;" do
|
71
|
+
response = http_options_request("/posts/1")
|
72
|
+
assert response.kind_of?(Array)
|
73
|
+
assert allows?(response[1], "GET")
|
74
|
+
end
|
75
|
+
|
76
|
+
it "the edit action return an HTML form for editing a post in response of a GET request;" do
|
77
|
+
response = http_options_request("/posts/1/edit")
|
78
|
+
assert response.kind_of?(Array)
|
79
|
+
assert allows?(response[1], "GET")
|
80
|
+
end
|
81
|
+
|
82
|
+
it "the update action uses PUT to update a specific post;" do
|
83
|
+
response = http_options_request("/posts/1")
|
84
|
+
assert response.kind_of?(Array)
|
85
|
+
assert allows?(response[1], "PUT")
|
86
|
+
end
|
87
|
+
|
88
|
+
it "the destroy action uses DELETE to delete a specific post." do
|
89
|
+
response = http_options_request("/posts/1")
|
90
|
+
assert response.kind_of?(Array)
|
91
|
+
assert allows?(response[1], "DELETE")
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
describe " dispatch the call to the application unless HTTP method equals OPTIONS " do
|
101
|
+
|
102
|
+
it " method GET example" do
|
103
|
+
complex_env = mock_env({"REQUEST_METHOD" => "GET", "PATH_INFO" => "/lobster" })
|
104
|
+
response = fake_app.call(complex_env)
|
105
|
+
assert response.kind_of?(Array)
|
106
|
+
assert response.size.should == 3
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
after(:all) do
|
112
|
+
ActionController::Routing::Routes.reload!
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'baurets','optionsful.rb')
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'sinatra'
|
5
|
+
require 'rack/test'
|
6
|
+
require 'rack'
|
7
|
+
require 'rack/lobster'
|
8
|
+
require 'spec'
|
9
|
+
require 'spec/autorun'
|
10
|
+
require 'spec/interop/test'
|
11
|
+
require 'action_controller'
|
12
|
+
require 'ruby-debug'
|
13
|
+
# require 'baurets/optionsful'
|
14
|
+
|
15
|
+
# set test environment set :environment, :test set :run, false set :raise_errors, true set :logging, false
|
16
|
+
|
17
|
+
DEFAULT_ENV = { "rack.version" => Rack::VERSION, "rack.input" => StringIO.new, "rack.errors" => StringIO.new,
|
18
|
+
"rack.multithread" => true, "rack.multiprocess" => true, "rack.run_once" => false, }
|
19
|
+
|
20
|
+
SAMPLE_ENV = {"rack.session"=>{:session_id=>"f66c8af1e89f318775e923159e046e71"}, "SERVER_NAME"=>"localhost",
|
21
|
+
"HTTP_HOST"=>"localhost:3332", "REMOTE_HOST"=>"localhost", "HTTP_USER_AGENT"=>"HTTP%20Client0.9.1
|
22
|
+
CFNetwork/438.14 Darwin/9.8.0 (i386) (MacBook4%2C1)", "rack.url_scheme"=>"http", "REQUEST_PATH"=>"/",
|
23
|
+
"SERVER_PROTOCOL"=>"HTTP/1.1", "rack.errors"=>"", "PATH_INFO"=>"/", "REMOTE_ADDR"=>"127.0.0.1",
|
24
|
+
"SERVER_SOFTWARE"=>"WEBrick/1.3.1 (Ruby/1.8.7/2010-06-23)", "rack.run_once"=>false, "rack.version"=>[1, 1],
|
25
|
+
"SCRIPT_NAME"=>"", "rack.multithread"=>false, "HTTP_VERSION"=>"HTTP/1.1", "REQUEST_URI"=>"",
|
26
|
+
"rack.multiprocess"=>false, "SERVER_PORT"=>"3332", "rack.session.options"=>{:domain=>"nilly", :path=>"/",
|
27
|
+
:key=>"_session_id", :httponly=>true, :expire_after=>300, :id=>"f66c8af1e89f318775e923159e046e71"},
|
28
|
+
"REQUEST_METHOD"=>"HEAD", "GATEWAY_INTERFACE"=>"CGI/1.1", "QUERY_STRING"=>"", "HTTP_CONNECTION"=>"close"}
|
29
|
+
|
30
|
+
FAKE_ENV = SAMPLE_ENV.merge!(DEFAULT_ENV)
|
31
|
+
|
32
|
+
def app
|
33
|
+
app = Rack::Builder.new {
|
34
|
+
use Rack::CommonLogger
|
35
|
+
use Rack::ShowExceptions
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
def fake_app
|
41
|
+
app = Rack::Builder.new {
|
42
|
+
use Rack::CommonLogger
|
43
|
+
use Rack::ShowExceptions
|
44
|
+
use Baurets::Optionsful
|
45
|
+
map "/lobster" do
|
46
|
+
use Rack::Lint
|
47
|
+
run Rack::Lobster.new
|
48
|
+
end
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
def mock_env(options = {})
|
53
|
+
FAKE_ENV.merge!(options)
|
54
|
+
end
|
55
|
+
|
56
|
+
def http_options_request(path)
|
57
|
+
complex_env = mock_env({"REQUEST_METHOD" => "OPTIONS", "PATH_INFO" => path })
|
58
|
+
response = Baurets::Optionsful.new(app).call(complex_env)
|
59
|
+
response
|
60
|
+
end
|
61
|
+
|
62
|
+
def allows?(headers, method)
|
63
|
+
headers["Allow"].include?(method)
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: optionsful
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 25
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
9
|
+
- 1
|
10
|
+
version: 0.1.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Marco Antonio Gonzalez Junior
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-07-
|
18
|
+
date: 2010-07-19 00:00:00 -03:00
|
19
19
|
default_executable:
|
20
20
|
dependencies: []
|
21
21
|
|
@@ -30,8 +30,13 @@ extra_rdoc_files: []
|
|
30
30
|
files:
|
31
31
|
- README.textile
|
32
32
|
- MIT-LICENSE
|
33
|
-
- lib/optionsful.rb
|
33
|
+
- lib/baurets/optionsful.rb
|
34
34
|
- rails/init.rb
|
35
|
+
- lib/tasks/optionsful.rake
|
36
|
+
- Rakefile
|
37
|
+
- spec/optionsful_spec.rb
|
38
|
+
- spec/spec.opts
|
39
|
+
- spec/spec_helper.rb
|
35
40
|
has_rdoc: true
|
36
41
|
homepage: http://optionsful.rubyforge.org
|
37
42
|
licenses: []
|
data/lib/optionsful.rb
DELETED
@@ -1,89 +0,0 @@
|
|
1
|
-
class Optionsful
|
2
|
-
|
3
|
-
def initialize(app)
|
4
|
-
@app = app
|
5
|
-
end
|
6
|
-
|
7
|
-
def call(env)
|
8
|
-
unless env["REQUEST_METHOD"] == "OPTIONS"
|
9
|
-
@app.call(env)
|
10
|
-
else
|
11
|
-
extract_options_information(env)
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
private
|
16
|
-
|
17
|
-
def extract_options_information(env)
|
18
|
-
[204, {"Allow" => extract_allowed_methods(env), "Link" => build_help_link}, ""]
|
19
|
-
end
|
20
|
-
|
21
|
-
def extract_allowed_methods(env)
|
22
|
-
# do routing introspection:
|
23
|
-
routes = do_routing_introspection
|
24
|
-
# do request path investigation
|
25
|
-
route_guess = guess_route(routes, env["PATH_INFO"])
|
26
|
-
allows = ""
|
27
|
-
# do the matches:
|
28
|
-
routes.each do |route|
|
29
|
-
if route.first == route_guess
|
30
|
-
allows += route[1].to_s.upcase! + "|"
|
31
|
-
end
|
32
|
-
end
|
33
|
-
allows = allows.split("|").join(", ")
|
34
|
-
end
|
35
|
-
|
36
|
-
def build_help_link
|
37
|
-
#PENDING
|
38
|
-
"<http://baurets.net/api/resources>; type=text/html; rel=help"
|
39
|
-
end
|
40
|
-
|
41
|
-
def guess_route(routes, path)
|
42
|
-
parts = prepare_request_path(path)
|
43
|
-
guess = []
|
44
|
-
index = 0
|
45
|
-
parts.each do |part|
|
46
|
-
if is_part_static?(routes, index, part)
|
47
|
-
guess << part
|
48
|
-
else
|
49
|
-
guess << :dynamic
|
50
|
-
end
|
51
|
-
index += 1
|
52
|
-
end
|
53
|
-
guess
|
54
|
-
end
|
55
|
-
|
56
|
-
def is_part_static?(routes, index, value)
|
57
|
-
routes.each do |route|
|
58
|
-
if route[0][index] == value
|
59
|
-
return true
|
60
|
-
end
|
61
|
-
end
|
62
|
-
return false
|
63
|
-
end
|
64
|
-
|
65
|
-
def do_routing_introspection
|
66
|
-
routes = []
|
67
|
-
ActionController::Routing::Routes.routes.each do |route|
|
68
|
-
static_path = []
|
69
|
-
route.segments.each do |segment|
|
70
|
-
if segment.kind_of?(ActionController::Routing::StaticSegment)
|
71
|
-
static_path << segment.value if (segment.respond_to?(:value) && segment.value != "/")
|
72
|
-
elsif segment.kind_of?(ActionController::Routing::DynamicSegment)
|
73
|
-
#TODO ignoring (.:format), think about it:
|
74
|
-
static_path << :dynamic unless (segment.respond_to?(:key) && segment.key == :format)
|
75
|
-
end
|
76
|
-
end
|
77
|
-
routes << [static_path, route.conditions[:method]] unless route.conditions[:method].nil?
|
78
|
-
end
|
79
|
-
routes
|
80
|
-
end
|
81
|
-
|
82
|
-
def prepare_request_path(path)
|
83
|
-
path = path[0..(path.rindex('.')-1)] if path.include?('.') #TODO ignoring (.:format), think about it
|
84
|
-
path_parts = path.split("/")
|
85
|
-
path_parts.delete("")
|
86
|
-
path_parts
|
87
|
-
end
|
88
|
-
|
89
|
-
end
|