truss-router 0.0.3 → 0.0.4

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/README.md CHANGED
@@ -1,5 +1,6 @@
1
1
  # Truss Router
2
2
  [![Build Status](https://travis-ci.org/truss-io/router.png)](https://travis-ci.org/truss-io/router)
3
+ [![Code Climate](https://codeclimate.com/github/truss-io/router.png)](https://codeclimate.com/github/truss-io/router)
3
4
 
4
5
  Truss Router is the first released part of Truss - a new wrapper around Rack to make writing performant Ruby web endpoints easier.
5
6
  Truss Router is currently considered alpha software, and as such please don't use in any production environment, but feel free to
@@ -7,10 +8,10 @@ explore and see how it handles for you.
7
8
 
8
9
  Truss supports the following Ruby platforms in Pure Ruby:
9
10
 
10
- * MRI 1.9.3
11
- * MRI 2.0.0
12
- * JRuby 1.7
13
- * RBX 1.9 mode
11
+ * MRI 1.9.3 - [benchmarks](https://github.com/truss-io/router/wiki/Benchmarks-MRI-1.9.3)
12
+ * MRI 2.0.0 - [benchmarks](https://github.com/truss-io/router/wiki/Benchmarks-MRI-2.0.0)
13
+ * JRuby 1.7 - [benchmarks](https://github.com/truss-io/router/wiki/Benchmarks-JRuby)
14
+ * RBX 1.9 mode - [benchmarks](https://github.com/truss-io/router/wiki/Benchmarks-RBX)
14
15
 
15
16
  ## Installation
16
17
 
@@ -83,3 +84,11 @@ MRI 1.9.3, which is worth investigating.
83
84
  3. Commit your changes (`git commit -am 'Add some feature'`)
84
85
  4. Push to the branch (`git push origin my-new-feature`)
85
86
  5. Create new Pull Request
87
+
88
+ ## Thanks and Inspiration
89
+
90
+ Thanks first and foremost have to go to [Music Glue](http://musicglue.com) where I work for letting me play with amazing projects
91
+ and do what I love for a living.
92
+
93
+ The inspiration for this project came from using [joshbuddy](https://github.com/joshboddy)'s [http_router](https://github.com/joshbuddy/http_router)
94
+ project.
@@ -0,0 +1,77 @@
1
+ require 'rack'
2
+ require 'http_router'
3
+ require 'usher'
4
+ require 'benchmark'
5
+ $:.unshift File.expand_path("../../lib", __FILE__)
6
+ require File.expand_path("../../lib/truss-router", __FILE__)
7
+
8
+ REQUEST = Rack::MockRequest.env_for("/home", method: "GET")
9
+ APP = ->(env){ [200, {'Content-Type' => 'text/plain'}, ["Hello World"]] }
10
+ TIMES = 50_000
11
+
12
+ puts "Running with a single route\n\n"
13
+
14
+ h = HttpRouter.new
15
+ h.add('/home').to(APP)
16
+
17
+ r = Usher::Interface.for(:rack) do
18
+ add('/home').to(APP)
19
+ end
20
+
21
+ Truss::Router.draw do |r|
22
+ r.get('/home', APP)
23
+ end
24
+
25
+ Benchmark.bmbm(12) do |x|
26
+ x.report("HTTP Router") { TIMES.times { h.call(REQUEST) } }
27
+ x.report("Usher Router") { TIMES.times { r.call(REQUEST) } }
28
+ x.report("Truss Router") { TIMES.times { Truss::Router.call(REQUEST) } }
29
+ end
30
+
31
+ puts "\n\nRunning with 10 routes\n\n"
32
+ BLOG_POST_REQ = Rack::MockRequest.env_for("/blog/posts/9", method: "GET")
33
+
34
+ a = HttpRouter.new
35
+ a.get('/home').to(APP)
36
+ a.get('/news').to(APP)
37
+ a.get('/articles/:id').to(APP)
38
+ a.get('/staff/:name').to(APP)
39
+ a.get('/contact-us').to(APP)
40
+ a.get('/about-us').to(APP)
41
+ a.post('/login').to(APP)
42
+ a.delete('/logout').to(APP)
43
+ a.get('/blog').to(APP)
44
+ a.get('/blog/posts/:id').to(APP)
45
+
46
+ routes = Usher::Interface.for(:rack) do
47
+ add('/home').to(APP)
48
+ add('/news').to(APP)
49
+ add('/articles/:id').to(APP)
50
+ add('/staff/:name').to(APP)
51
+ add('/contact-us').to(APP)
52
+ add('/about-us').to(APP)
53
+ add('/login').to(APP)
54
+ add('/logout').to(APP)
55
+ add('/blog').to(APP)
56
+ add('/blog/posts/:id').to(APP)
57
+ end
58
+
59
+ Truss::Router.reset!
60
+ Truss::Router.draw do |r|
61
+ r.get("/home", APP)
62
+ r.get("/news", APP)
63
+ r.get("/articles/:id", APP)
64
+ r.get("/staff/:name", APP)
65
+ r.get("/contact-us", APP)
66
+ r.get("/about-us", APP)
67
+ r.post("/login", APP)
68
+ r.delete("/logout", APP)
69
+ r.get("/blog", APP)
70
+ r.get("/blog/posts/:id", APP)
71
+ end
72
+
73
+ Benchmark.bmbm(12) do |x|
74
+ x.report("HTTP Router") { TIMES.times { a.call(BLOG_POST_REQ) } }
75
+ x.report("Usher Router") { TIMES.times { routes.call(BLOG_POST_REQ) } }
76
+ x.report("Truss Router") { TIMES.times { Truss::Router.call(BLOG_POST_REQ) } }
77
+ end
@@ -2,11 +2,15 @@ module Truss
2
2
  module Router
3
3
  class Node
4
4
  attr_accessor :request_method, :path, :has_dynamic_segments,
5
- :endpoint, :matchable_regex, :options
5
+ :endpoint, :matchable_regex, :options, :allowed_methods,
6
+ :path_segments
7
+
6
8
  def initialize(method, path, endpoint, options={})
7
9
  @request_method, @path, @endpoint = method, path, endpoint
8
- @has_dynamic_segments = false
10
+ @has_dynamic_segments = false
9
11
  @matchable_regex = build_matchable_regex(method, path, options)
12
+ @allowed_methods = discover_allowed_methods(method, path, options)
13
+ @path_segments = get_path_segments(path)
10
14
  @options = options
11
15
  end
12
16
 
@@ -30,23 +34,23 @@ module Truss
30
34
  def build_matchable_regex(method, path, options)
31
35
  if path.include?(":")
32
36
  self.has_dynamic_segments = true
33
- /\A#{method_group(method)}#{segment_string(path)}\Z/
37
+ /\A#{segment_string(path)}\Z/
34
38
  else
35
- %r[\A#{method_group(method)}#{path}\Z]
39
+ %r[\A#{path}\Z]
36
40
  end
37
41
  end
38
42
 
39
- def method_group(method)
40
- origin = case method
41
- when :get
42
- "(GET|HEAD|OPTIONS)"
43
- when :options
44
- "OPTIONS"
45
- else
46
- "(#{method.to_s.upcase}|OPTIONS)"
47
- end
43
+ def discover_allowed_methods(method, path, options)
44
+ allowed = [method.to_s.upcase]
45
+ allowed << "OPTIONS" if options.has_key?(:cors)
46
+ allowed << "HEAD" if (method == :get)
47
+ allowed
48
48
  end
49
49
 
50
+ def get_path_segments(path)
51
+ path.split("/").reject(&:empty?).count
52
+ end
53
+
50
54
  def segment_string(path)
51
55
  components = path.split("/").map do |comp|
52
56
  if comp[0] == ":"
@@ -8,8 +8,10 @@ module Truss
8
8
  super(*args)
9
9
  end
10
10
 
11
- def routing_path
12
- "#{request_method}#{path}"
11
+ def routing_path; path end
12
+
13
+ def segment_length
14
+ @seglength ||= path.split("/").reject(&:empty?).count
13
15
  end
14
16
  end
15
17
  end
@@ -3,28 +3,30 @@ module Truss
3
3
  class Routeset
4
4
 
5
5
  attr_accessor :nodes
6
- def initialize(nodes=[])
6
+ def initialize(nodes={})
7
7
  @nodes = nodes
8
+ %w[GET HEAD OPTIONS POST PATCH PUT DELETE].each do |req|
9
+ @nodes[req] ||= []
10
+ end
8
11
  end
9
12
 
10
13
  def add_node(node)
11
- @nodes << node
12
- end
13
-
14
- def <<(node)
15
- @nodes << node
14
+ node.allowed_methods.each do |req|
15
+ @nodes[req] << node
16
+ end
16
17
  end
17
18
 
18
- def [](node)
19
- @nodes[node]
19
+ def nodes_for(type)
20
+ nodes[type]
20
21
  end
21
22
 
22
- def length
23
- @nodes.length
23
+ def total_nodes
24
+ nodes.map{|k,v| v.count}.flatten.inject(:+)
24
25
  end
25
26
 
26
27
  def find_route request
27
- @nodes.detect do |node|
28
+ req_nodes = nodes_for(request.request_method).select{|n| (n.path_segments == request.segment_length) }
29
+ req_nodes.detect do |node|
28
30
  node.matches?(request)
29
31
  end
30
32
  end
@@ -1,5 +1,5 @@
1
1
  module Truss
2
2
  module Router
3
- VERSION = "0.0.3"
3
+ VERSION = "0.0.4"
4
4
  end
5
5
  end
data/lib/truss/router.rb CHANGED
@@ -60,7 +60,7 @@ module Truss
60
60
 
61
61
  protected
62
62
  def build_node method, path, endpoint, opts
63
- routeset << Routes.const_get(method).new(path, endpoint, opts)
63
+ routeset.add_node Routes.const_get(method).new(path, endpoint, opts)
64
64
  end
65
65
  end
66
66
  end
@@ -42,13 +42,16 @@ describe Truss::Router do
42
42
  subject.draw(&map)
43
43
  end
44
44
  it "should have two route" do
45
- subject.routeset.length.should eq(2)
45
+ subject.routeset.total_nodes.should eq(3)
46
46
  end
47
- it "should have a Get route as the first member of the routeset" do
48
- subject.routeset[0].should be_kind_of(Truss::Router::Routes::Get)
47
+ it "should have a Get routeset with one member" do
48
+ subject.routeset.nodes_for("GET").length.should eq(1)
49
49
  end
50
- it "should have a Post route as the second member of the routeset" do
51
- subject.routeset[1].should be_kind_of(Truss::Router::Routes::Post)
50
+ it "should have an Head routeset with one member" do
51
+ subject.routeset.nodes_for("HEAD").length.should eq(1)
52
+ end
53
+ it "should have a Post routeset with one member" do
54
+ subject.routeset.nodes_for("POST").length.should eq(1)
52
55
  end
53
56
  end
54
57
  end
@@ -17,7 +17,7 @@ describe Truss::Router do
17
17
  end
18
18
 
19
19
  it "should return the first route for a matching path" do
20
- subject[0].should_receive(:call).exactly(1).times.with(kind_of(Truss::Router::Request))
20
+ subject.nodes_for("GET")[0].should_receive(:call).exactly(1).times.with(kind_of(Truss::Router::Request))
21
21
  described_class.call(env)
22
22
  end
23
23
 
@@ -30,9 +30,9 @@ describe Truss::Router do
30
30
  context "multiple routes present" do
31
31
  let(:multiple_map) { ->(r){
32
32
  r.get("/", app)
33
- r.get("/home", app)
33
+ r.get("/home", app, cors: true)
34
34
  r.get("/about", app)
35
- r.post("/login", app)
35
+ r.post("/login", app, cors: true)
36
36
  r.post("/home", app)
37
37
  }
38
38
  }
@@ -43,49 +43,49 @@ describe Truss::Router do
43
43
  end
44
44
 
45
45
  it "should call the second node given a get request for /home" do
46
- subject[1].should_receive(:call).exactly(1).times.with(kind_of(Truss::Router::Request))
47
- described_class.call(env)
46
+ subject.nodes_for("GET")[1].should_receive(:call).exactly(1).times.with(kind_of(Truss::Router::Request))
47
+ puts described_class.call(env)
48
48
  end
49
49
 
50
50
  it "should call the third node given a get request for /about" do
51
51
  about_env = Rack::MockRequest.env_for("/about", method: "GET")
52
- subject[2].should_receive(:call).exactly(1).times.with(kind_of(Truss::Router::Request))
52
+ subject.nodes_for("GET")[2].should_receive(:call).exactly(1).times.with(kind_of(Truss::Router::Request))
53
53
  described_class.call(about_env)
54
54
  end
55
55
 
56
56
  it "should call the fourth node given a post request for /login" do
57
57
  login_env = Rack::MockRequest.env_for("/login", method: "POST")
58
- subject[3].should_receive(:call).exactly(1).times.with(kind_of(Truss::Router::Request))
58
+ subject.nodes_for("POST")[0].should_receive(:call).exactly(1).times.with(kind_of(Truss::Router::Request))
59
59
  described_class.call(login_env)
60
60
  end
61
61
 
62
62
  it "should call the fifth node given a post request for /home" do
63
63
  post_home_env = Rack::MockRequest.env_for("/home", method: "POST")
64
- subject[4].should_receive(:call).exactly(1).times.with(kind_of(Truss::Router::Request))
64
+ subject.nodes_for("POST")[1].should_receive(:call).exactly(1).times.with(kind_of(Truss::Router::Request))
65
65
  described_class.call(post_home_env)
66
66
  end
67
67
 
68
68
  it "should call the second node given an options request for /home" do
69
69
  options_home_env = Rack::MockRequest.env_for("/home", method: "OPTIONS")
70
- subject[1].should_receive(:call).exactly(1).times.with(kind_of(Truss::Router::Request))
70
+ subject.nodes_for("OPTIONS")[0].should_receive(:call).exactly(1).times.with(kind_of(Truss::Router::Request))
71
71
  described_class.call(options_home_env)
72
72
  end
73
73
 
74
74
  it "should call the second node given a head request for /home" do
75
75
  head_home_env = Rack::MockRequest.env_for("/home", method: "HEAD")
76
- subject[1].should_receive(:call).exactly(1).times.with(kind_of(Truss::Router::Request))
76
+ subject.nodes_for("HEAD")[1].should_receive(:call).exactly(1).times.with(kind_of(Truss::Router::Request))
77
77
  described_class.call(head_home_env)
78
78
  end
79
79
 
80
80
  it "should call the fourth node given an options request for /login" do
81
81
  options_login_env = Rack::MockRequest.env_for("/login", method: "OPTIONS")
82
- subject[3].should_receive(:call).exactly(1).times.with(kind_of(Truss::Router::Request))
82
+ subject.nodes_for("OPTIONS")[1].should_receive(:call).exactly(1).times.with(kind_of(Truss::Router::Request))
83
83
  described_class.call(options_login_env)
84
84
  end
85
85
 
86
86
  it "should call the first node given a get request for /" do
87
87
  home_env = Rack::MockRequest.env_for("/", method: "GET")
88
- subject[0].should_receive(:call).exactly(1).times.with(kind_of(Truss::Router::Request))
88
+ subject.nodes_for("GET")[0].should_receive(:call).exactly(1).times.with(kind_of(Truss::Router::Request))
89
89
  described_class.call(home_env)
90
90
  end
91
91
 
@@ -11,52 +11,66 @@ describe Truss::Router::Node do
11
11
  it { should respond_to(:matchable_regex) }
12
12
  it { should respond_to(:call) }
13
13
 
14
- describe "routing regexes" do
14
+ describe "routing constraints" do
15
15
  context "for get requests" do
16
16
  subject { Truss::Router::Node.new(:get, "/home", app) }
17
- its(:matchable_regex) { should eq(%r[\A(GET|HEAD|OPTIONS)/home\Z]) }
17
+ its(:matchable_regex) { should eq(%r[\A/home\Z]) }
18
+ its(:path_segments) { should eq(1) }
19
+ its(:allowed_methods) { should eq(%w[GET HEAD]) }
18
20
  end
19
21
 
20
22
  context "for post requests" do
21
23
  subject { Truss::Router::Node.new(:post, "/home", app) }
22
- its(:matchable_regex) { should eq(%r[\A(POST|OPTIONS)/home\Z]) }
24
+ its(:matchable_regex) { should eq(%r[\A/home\Z]) }
25
+ its(:path_segments) { should eq(1) }
26
+ its(:allowed_methods) { should eq(%w[POST]) }
23
27
  end
24
28
 
25
29
  context "for put requests" do
26
30
  subject { Truss::Router::Node.new(:put, "/home", app) }
27
- its(:matchable_regex) { should eq(%r[\A(PUT|OPTIONS)/home\Z]) }
31
+ its(:matchable_regex) { should eq(%r[\A/home\Z]) }
32
+ its(:path_segments) { should eq(1) }
33
+ its(:allowed_methods) { should eq(%w[PUT]) }
28
34
  end
29
35
 
30
36
  context "for patch requests" do
31
37
  subject { Truss::Router::Node.new(:patch, "/home", app) }
32
- its(:matchable_regex) { should eq(%r[\A(PATCH|OPTIONS)/home\Z]) }
38
+ its(:matchable_regex) { should eq(%r[\A/home\Z]) }
39
+ its(:path_segments) { should eq(1) }
40
+ its(:allowed_methods) { should eq(%w[PATCH]) }
33
41
  end
34
42
 
35
43
  context "for head requests" do
36
44
  subject { Truss::Router::Node.new(:head, "/home", app) }
37
- its(:matchable_regex) { should eq(%r[\A(HEAD|OPTIONS)/home\Z]) }
45
+ its(:matchable_regex) { should eq(%r[\A/home\Z]) }
46
+ its(:path_segments) { should eq(1) }
47
+ its(:allowed_methods) { should eq(%w[HEAD]) }
38
48
  end
39
49
 
40
50
  context "for options requests" do
41
51
  subject { Truss::Router::Node.new(:options, "/home", app) }
42
- its(:matchable_regex) { should eq(%r[\AOPTIONS/home\Z]) }
52
+ its(:matchable_regex) { should eq(%r[\A/home\Z]) }
53
+ its(:path_segments) { should eq(1) }
54
+ its(:allowed_methods) { should eq(%w[OPTIONS]) }
43
55
  end
44
56
 
45
57
  context "for delete requests" do
46
58
  subject { Truss::Router::Node.new(:delete, "/home", app) }
47
- its(:matchable_regex) { should eq(%r[\A(DELETE|OPTIONS)/home\Z]) }
59
+ its(:matchable_regex) { should eq(%r[\A/home\Z]) }
60
+ its(:path_segments) { should eq(1) }
61
+ its(:allowed_methods) { should eq(%w[DELETE]) }
48
62
  end
49
63
  end
50
64
 
51
65
  describe "dynamic segments" do
52
66
  context "single dynamic matcher" do
53
67
  subject { Truss::Router::Node.new(:delete, "/:id", app) }
54
- its(:matchable_regex) { should eq(/\A(DELETE|OPTIONS)\/(?<id>[\w\-]+)\Z/) }
68
+ it { subject.matchable_regex.to_s.should eq(/\A\/(?<id>[\w\-]+)\Z/.to_s) }
55
69
  end
56
70
 
57
71
  context "multiple dynamic matchers" do
58
72
  subject { Truss::Router::Node.new(:delete, "/posts/:post_id/comments/:id", app) }
59
- its(:matchable_regex) { should eq(/\A(DELETE|OPTIONS)\/posts\/(?<post_id>[\w\-]+)\/comments\/(?<id>[\w\-]+)\Z/) }
73
+ it { subject.matchable_regex.to_s.should eq(/\A\/posts\/(?<post_id>[\w\-]+)\/comments\/(?<id>[\w\-]+)\Z/.to_s) }
60
74
  end
61
75
  end
62
76
  end
@@ -8,4 +8,5 @@ describe Truss::Router::Request do
8
8
  it { should respond_to(:routing_path) }
9
9
  it { should respond_to(:routing_params) }
10
10
  its(:routing_params) { should eq({}) }
11
+ its(:segment_length) { should eq(1) }
11
12
  end
@@ -3,18 +3,20 @@ require 'truss/router/node'
3
3
 
4
4
  describe Truss::Router::Routeset do
5
5
  it { should respond_to(:nodes) }
6
+ it { should respond_to(:nodes_for) }
6
7
  it { should respond_to(:add_node) }
8
+ it { should respond_to(:total_nodes) }
9
+ it { subject.nodes.keys.sort.should eq(%w[GET HEAD OPTIONS PUT PATCH POST DELETE].sort) }
7
10
 
8
11
  context "adding nodes" do
9
- subject { Truss::Router::Routeset.new }
10
12
  let(:app) { ->(env){[200, {'Content-Type' => 'text/plain'}, ["Hello World"]]} }
11
13
  let(:node) { Truss::Router::Node.new(:get, "/home", app, {}) }
12
14
  before(:each) { subject.add_node(node) }
13
15
 
14
- it { subject.nodes.length.should eq(1) }
16
+ it { subject.nodes_for("GET").length.should eq(1) }
15
17
  it "can contain multiple nodes" do
16
18
  subject.add_node(node)
17
- subject.nodes.length.should eq(2)
19
+ subject.nodes_for("GET").length.should eq(2)
18
20
  end
19
21
  end
20
22
  end
data/truss-router.gemspec CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
10
10
  spec.email = ["john@musicglue.com"]
11
11
  spec.description = %q{Truss Router is a modular Rack Router for Truss}
12
12
  spec.summary = %q{Truss Router is a modular Rack Router for Truss}
13
- spec.homepage = "http://truss-io.github.io"
13
+ spec.homepage = "http://truss-io.github.io/router"
14
14
  spec.license = "MIT"
15
15
 
16
16
  spec.files = `git ls-files`.split($/)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: truss-router
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
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: 2013-05-26 00:00:00.000000000 Z
12
+ date: 2013-05-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -89,6 +89,7 @@ files:
89
89
  - LICENSE.txt
90
90
  - README.md
91
91
  - Rakefile
92
+ - benchmarks/comparison_bench.rb
92
93
  - benchmarks/simple_bench.rb
93
94
  - lib/truss-router.rb
94
95
  - lib/truss/router.rb
@@ -121,7 +122,7 @@ files:
121
122
  - spec/lib/truss/router_spec.rb
122
123
  - spec/spec_helper.rb
123
124
  - truss-router.gemspec
124
- homepage: http://truss-io.github.io
125
+ homepage: http://truss-io.github.io/router
125
126
  licenses:
126
127
  - MIT
127
128
  post_install_message: