lotus-router 0.0.0 → 0.1.0

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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.coveralls.yml +2 -0
  3. data/.gitignore +5 -13
  4. data/.travis.yml +5 -0
  5. data/.yardopts +3 -0
  6. data/Gemfile +10 -3
  7. data/README.md +470 -6
  8. data/Rakefile +10 -1
  9. data/benchmarks/callable +23 -0
  10. data/benchmarks/named_routes +72 -0
  11. data/benchmarks/resource +44 -0
  12. data/benchmarks/resources +58 -0
  13. data/benchmarks/routes +67 -0
  14. data/benchmarks/run.sh +11 -0
  15. data/benchmarks/utils.rb +56 -0
  16. data/lib/lotus-router.rb +1 -0
  17. data/lib/lotus/router.rb +752 -3
  18. data/lib/lotus/router/version.rb +2 -2
  19. data/lib/lotus/routing/endpoint.rb +114 -0
  20. data/lib/lotus/routing/endpoint_resolver.rb +251 -0
  21. data/lib/lotus/routing/http_router.rb +130 -0
  22. data/lib/lotus/routing/namespace.rb +86 -0
  23. data/lib/lotus/routing/resource.rb +73 -0
  24. data/lib/lotus/routing/resource/action.rb +340 -0
  25. data/lib/lotus/routing/resource/options.rb +48 -0
  26. data/lib/lotus/routing/resources.rb +40 -0
  27. data/lib/lotus/routing/resources/action.rb +123 -0
  28. data/lib/lotus/routing/route.rb +53 -0
  29. data/lotus-router.gemspec +16 -12
  30. data/test/fixtures.rb +193 -0
  31. data/test/integration/client_error_test.rb +16 -0
  32. data/test/integration/pass_on_response_test.rb +13 -0
  33. data/test/named_routes_test.rb +123 -0
  34. data/test/namespace_test.rb +289 -0
  35. data/test/new_test.rb +67 -0
  36. data/test/redirect_test.rb +33 -0
  37. data/test/resource_test.rb +128 -0
  38. data/test/resources_test.rb +136 -0
  39. data/test/routing/endpoint_resolver_test.rb +110 -0
  40. data/test/routing/resource/options_test.rb +36 -0
  41. data/test/routing_test.rb +99 -0
  42. data/test/test_helper.rb +32 -0
  43. data/test/version_test.rb +7 -0
  44. metadata +102 -10
@@ -0,0 +1,33 @@
1
+ require 'test_helper'
2
+
3
+ describe Lotus::Router do
4
+ before do
5
+ @router = Lotus::Router.new
6
+ end
7
+
8
+ describe '#redirect' do
9
+ it 'recognizes string endpoint' do
10
+ endpoint = ->(env) { [200, {}, ['Redirect destination!']] }
11
+ @router.get('/redirect_destination', to: endpoint)
12
+ @router.redirect('/redirect', to: '/redirect_destination')
13
+
14
+ env = Rack::MockRequest.env_for('/redirect')
15
+ status, headers, _ = @router.call(env)
16
+
17
+ status.must_equal 302
18
+ headers['Location'].must_equal '/redirect_destination'
19
+ end
20
+
21
+ it 'recognizes string endpoint with custom http code' do
22
+ endpoint = ->(env) { [200, {}, ['Redirect destination!']] }
23
+ @router.get('/redirect_destination', to: endpoint)
24
+ @router.redirect('/redirect', to: '/redirect_destination', code: 301)
25
+
26
+ env = Rack::MockRequest.env_for('/redirect')
27
+ status, headers, _ = @router.call(env)
28
+
29
+ status.must_equal 301
30
+ headers['Location'].must_equal '/redirect_destination'
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,128 @@
1
+ require 'test_helper'
2
+
3
+ describe Lotus::Router do
4
+ before do
5
+ @router = Lotus::Router.new
6
+ @app = Rack::MockRequest.new(@router)
7
+ end
8
+
9
+ after do
10
+ @router.reset!
11
+ end
12
+
13
+ def endpoint(response)
14
+ ->(env) { response }
15
+ end
16
+
17
+ describe '#resource' do
18
+ before do
19
+ @router.resource 'avatar'
20
+ end
21
+
22
+ it 'recognizes get new' do
23
+ @router.path(:new_avatar).must_equal '/avatar/new'
24
+ @app.request('GET', '/avatar/new').body.must_equal 'Avatar::New'
25
+ end
26
+
27
+ it 'recognizes post create' do
28
+ @router.path(:avatar).must_equal '/avatar'
29
+ @app.request('POST', '/avatar').body.must_equal 'Avatar::Create'
30
+ end
31
+
32
+ it 'recognizes get show' do
33
+ @router.path(:avatar).must_equal '/avatar'
34
+ @app.request('GET', '/avatar').body.must_equal 'Avatar::Show'
35
+ end
36
+
37
+ it 'recognizes get edit' do
38
+ @router.path(:edit_avatar).must_equal '/avatar/edit'
39
+ @app.request('GET', '/avatar/edit').body.must_equal 'Avatar::Edit'
40
+ end
41
+
42
+ it 'recognizes patch update' do
43
+ @router.path(:avatar).must_equal '/avatar'
44
+ @app.request('PATCH', '/avatar').body.must_equal 'Avatar::Update'
45
+ end
46
+
47
+ it 'recognizes delete destroy' do
48
+ @router.path(:avatar).must_equal '/avatar'
49
+ @app.request('DELETE', '/avatar').body.must_equal 'Avatar::Destroy'
50
+ end
51
+
52
+ describe ':only option' do
53
+ before do
54
+ @router.resource 'profile', only: [:edit, :update]
55
+ end
56
+
57
+ it 'recognizes only specified paths' do
58
+ @router.path(:edit_profile).must_equal '/profile/edit'
59
+ @app.request('GET', '/profile/edit').body.must_equal 'Profile::Edit'
60
+
61
+ @router.path(:profile).must_equal '/profile'
62
+ @app.request('PATCH', '/profile').body.must_equal 'Profile::Update'
63
+ end
64
+
65
+ it 'does not recognize other paths' do
66
+ @app.request('GET', '/profile/new').status.must_equal 405
67
+ @app.request('POST', '/profile').status.must_equal 405
68
+ @app.request('GET', '/profile').status.must_equal 405
69
+ @app.request('DELETE', '/profile').status.must_equal 405
70
+
71
+ -> { @router.path(:new_profile) }.must_raise HttpRouter::InvalidRouteException
72
+ end
73
+ end
74
+
75
+ describe ':except option' do
76
+ before do
77
+ @router.resource 'profile', except: [:new, :show, :create, :destroy]
78
+ end
79
+
80
+ it 'recognizes only the non-rejected paths' do
81
+ @router.path(:edit_profile).must_equal '/profile/edit'
82
+ @app.request('GET', '/profile/edit').body.must_equal 'Profile::Edit'
83
+
84
+ @router.path(:profile).must_equal '/profile'
85
+ @app.request('PATCH', '/profile').body.must_equal 'Profile::Update'
86
+ end
87
+
88
+ it 'does not recognize other paths' do
89
+ @app.request('GET', '/profile/new').status.must_equal 405
90
+ @app.request('POST', '/profile').status.must_equal 405
91
+ @app.request('GET', '/profile').status.must_equal 405
92
+ @app.request('DELETE', '/profile').status.must_equal 405
93
+
94
+ -> { @router.path(:new_profile) }.must_raise HttpRouter::InvalidRouteException
95
+ end
96
+ end
97
+
98
+ describe 'member' do
99
+ before do
100
+ @router.resource 'profile', only: [] do
101
+ member do
102
+ patch 'activate'
103
+ end
104
+ end
105
+ end
106
+
107
+ it 'recognizes the path' do
108
+ @router.path(:activate_profile).must_equal '/profile/activate'
109
+ @app.request('PATCH', '/profile/activate').body.must_equal 'Profile::Activate'
110
+ end
111
+ end
112
+
113
+ describe 'collection' do
114
+ before do
115
+ @router.resource 'profile', only: [] do
116
+ collection do
117
+ get 'keys'
118
+ end
119
+ end
120
+ end
121
+
122
+ it 'recognizes the path' do
123
+ @router.path(:keys_profile).must_equal '/profile/keys'
124
+ @app.request('GET', '/profile/keys').body.must_equal 'Profile::Keys'
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,136 @@
1
+ require 'test_helper'
2
+
3
+ describe Lotus::Router do
4
+ before do
5
+ @router = Lotus::Router.new
6
+ @app = Rack::MockRequest.new(@router)
7
+ end
8
+
9
+ after do
10
+ @router.reset!
11
+ end
12
+
13
+ def endpoint(response)
14
+ ->(env) { response }
15
+ end
16
+
17
+ describe '#resources' do
18
+ before do
19
+ @router.resources 'flowers'
20
+ end
21
+
22
+ it 'recognizes get index' do
23
+ @router.path(:flowers).must_equal '/flowers'
24
+ @app.request('GET', '/flowers').body.must_equal 'Flowers::Index'
25
+ end
26
+
27
+ it 'recognizes get new' do
28
+ @router.path(:new_flowers).must_equal '/flowers/new'
29
+ @app.request('GET', '/flowers/new').body.must_equal 'Flowers::New'
30
+ end
31
+
32
+ it 'recognizes post create' do
33
+ @router.path(:flowers).must_equal '/flowers'
34
+ @app.request('POST', '/flowers').body.must_equal 'Flowers::Create'
35
+ end
36
+
37
+ it 'recognizes get show' do
38
+ @router.path(:flowers, id: 23).must_equal '/flowers/23'
39
+ @app.request('GET', '/flowers/23').body.must_equal 'Flowers::Show 23'
40
+ end
41
+
42
+ it 'recognizes get edit' do
43
+ @router.path(:edit_flowers, id: 23).must_equal '/flowers/23/edit'
44
+ @app.request('GET', '/flowers/23/edit').body.must_equal 'Flowers::Edit 23'
45
+ end
46
+
47
+ it 'recognizes patch update' do
48
+ @router.path(:flowers, id: 23).must_equal '/flowers/23'
49
+ @app.request('PATCH', '/flowers/23').body.must_equal 'Flowers::Update 23'
50
+ end
51
+
52
+ it 'recognizes delete destroy' do
53
+ @router.path(:flowers, id: 23).must_equal '/flowers/23'
54
+ @app.request('DELETE', '/flowers/23').body.must_equal 'Flowers::Destroy 23'
55
+ end
56
+
57
+ describe ':only option' do
58
+ before do
59
+ @router.resources 'keyboards', only: [:index, :edit]
60
+ end
61
+
62
+ it 'recognizes only specified paths' do
63
+ @router.path(:keyboards).must_equal '/keyboards'
64
+ @app.request('GET', '/keyboards').body.must_equal 'Keyboards::Index'
65
+
66
+ @router.path(:edit_keyboards, id: 23).must_equal '/keyboards/23/edit'
67
+ @app.request('GET', '/keyboards/23/edit').body.must_equal 'Keyboards::Edit 23'
68
+ end
69
+
70
+ it 'does not recognize other paths' do
71
+ @app.request('GET', '/keyboards/new').status.must_equal 404
72
+ @app.request('POST', '/keyboards').status.must_equal 405
73
+ @app.request('GET', '/keyboards/23').status.must_equal 404
74
+ @app.request('PATCH', '/keyboards/23').status.must_equal 405
75
+ @app.request('DELETE', '/keyboards/23').status.must_equal 405
76
+
77
+ -> { @router.path(:new_keyboards) }.must_raise HttpRouter::InvalidRouteException
78
+ end
79
+ end
80
+
81
+ describe ':except option' do
82
+ before do
83
+ @router.resources 'keyboards', except: [:new, :show, :update, :destroy]
84
+ end
85
+
86
+ it 'recognizes only the non-rejected paths' do
87
+ @router.path(:keyboards).must_equal '/keyboards'
88
+ @app.request('GET', '/keyboards').body.must_equal 'Keyboards::Index'
89
+
90
+ @router.path(:edit_keyboards, id: 23).must_equal '/keyboards/23/edit'
91
+ @app.request('GET', '/keyboards/23/edit').body.must_equal 'Keyboards::Edit 23'
92
+
93
+ @router.path(:keyboards).must_equal '/keyboards'
94
+ @app.request('POST', '/keyboards').body.must_equal 'Keyboards::Create'
95
+ end
96
+
97
+ it 'does not recognize other paths' do
98
+ @app.request('GET', '/keyboards/new').status.must_equal 404
99
+ @app.request('PATCH', '/keyboards/23').status.must_equal 405
100
+ @app.request('DELETE', '/keyboards/23').status.must_equal 405
101
+
102
+ -> { @router.path(:new_keyboards) }.must_raise HttpRouter::InvalidRouteException
103
+ end
104
+ end
105
+
106
+ describe 'member' do
107
+ before do
108
+ @router.resources 'keyboards', only: [] do
109
+ member do
110
+ get 'screenshot'
111
+ end
112
+ end
113
+ end
114
+
115
+ it 'recognizes the path' do
116
+ @router.path(:screenshot_keyboards, id: 23).must_equal '/keyboards/23/screenshot'
117
+ @app.request('GET', '/keyboards/23/screenshot').body.must_equal 'Keyboards::Screenshot 23'
118
+ end
119
+ end
120
+
121
+ describe 'collection' do
122
+ before do
123
+ @router.resources 'keyboards', only: [] do
124
+ collection do
125
+ get 'search'
126
+ end
127
+ end
128
+ end
129
+
130
+ it 'recognizes the path' do
131
+ @router.path(:search_keyboards).must_equal '/keyboards/search'
132
+ @app.request('GET', '/keyboards/search').body.must_equal 'Keyboards::Search'
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,110 @@
1
+ require 'test_helper'
2
+
3
+ describe Lotus::Routing::EndpointResolver do
4
+ before do
5
+ @resolver = Lotus::Routing::EndpointResolver.new
6
+ end
7
+
8
+ it 'recognizes :to when it is a callable object' do
9
+ endpoint = Object.new
10
+ endpoint.define_singleton_method(:call) { }
11
+
12
+ options = { to: endpoint }
13
+
14
+ @resolver.resolve(options).must_equal(endpoint)
15
+ end
16
+
17
+ it 'recognizes :to when it is a string that references a class that can be retrieved now' do
18
+ options = { to: 'test_endpoint' }
19
+ @resolver.resolve(options).call({}).must_equal 'Hi from TestEndpoint!'
20
+ end
21
+
22
+ describe 'when :to references a missing class' do
23
+ it 'if the class is available when invoking call, it succeed' do
24
+ options = { to: 'lazy_controller' }
25
+ endpoint = @resolver.resolve(options)
26
+ LazyController = Class.new(Object) { define_method(:call) {|env| env } }
27
+
28
+ endpoint.call({}).must_equal({})
29
+ end
30
+
31
+ it 'if the class is not available when invoking call, it raises error' do
32
+ options = { to: 'missing_endpoint' }
33
+ endpoint = @resolver.resolve(options)
34
+
35
+ -> { endpoint.call({}) }.must_raise Lotus::Routing::EndpointNotFound
36
+ end
37
+ end
38
+
39
+ it 'recognizes :to when it is a string with separator' do
40
+ options = { to: 'test#show' }
41
+ @resolver.resolve(options).call({}).must_equal 'Hi from Test::Show!'
42
+ end
43
+
44
+ it 'returns the default endpoint when cannot match anything' do
45
+ options = { to: 23 }
46
+ @resolver.resolve(options).call({}).first.must_equal 404
47
+ end
48
+
49
+ describe 'namespace' do
50
+ before do
51
+ @resolver = Lotus::Routing::EndpointResolver.new(namespace: TestApp)
52
+ end
53
+
54
+ it 'recognizes :to when it is a string and an explicit namespace' do
55
+ options = { to: 'test_endpoint' }
56
+ @resolver.resolve(options).call({}).must_equal 'Hi from TestApp::TestEndpoint!'
57
+ end
58
+
59
+ it 'recognizes :to when it is a string with separator and it has an explicit namespace' do
60
+ options = { to: 'test2#show' }
61
+ @resolver.resolve(options).call({}).must_equal 'Hi from TestApp::Test2Controller::Show!'
62
+ end
63
+ end
64
+
65
+ describe 'custom endpoint' do
66
+ before :all do
67
+ class CustomEndpoint
68
+ def initialize(endpoint)
69
+ @endpoint = endpoint
70
+ end
71
+ end
72
+
73
+ @resolver = Lotus::Routing::EndpointResolver.new(endpoint: CustomEndpoint)
74
+ end
75
+
76
+ after do
77
+ Object.send(:remove_const, :CustomEndpoint)
78
+ end
79
+
80
+ it 'returns specified endpoint instance' do
81
+ @resolver.resolve({}).class.must_equal(CustomEndpoint)
82
+ end
83
+ end
84
+
85
+ describe 'custom separator' do
86
+ before do
87
+ @resolver = Lotus::Routing::EndpointResolver.new(action_separator: action_separator)
88
+ end
89
+
90
+ let(:action_separator) { '@' }
91
+
92
+ it 'matches controller and action with a custom separator' do
93
+ options = { to: "test#{ action_separator }show" }
94
+ @resolver.resolve(options).call({}).must_equal 'Hi from Test::Show!'
95
+ end
96
+ end
97
+
98
+ describe 'custom suffix' do
99
+ before do
100
+ @resolver = Lotus::Routing::EndpointResolver.new(suffix: suffix)
101
+ end
102
+
103
+ let(:suffix) { 'Controller::' }
104
+
105
+ it 'matches controller and action with a custom separator' do
106
+ options = { to: 'test#show' }
107
+ @resolver.resolve(options).call({}).must_equal 'Hi from Test::Show!'
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,36 @@
1
+ require 'test_helper'
2
+
3
+ describe Lotus::Routing::Resource::Options do
4
+ before do
5
+ @actions = [:index, :new, :create, :show, :edit, :update, :destroy]
6
+ end
7
+
8
+ it 'returns all the actions when no exceptions are requested' do
9
+ Lotus::Routing::Resources::Options.new(@actions).actions.must_equal @actions
10
+ end
11
+
12
+ it 'returns only the action requested via the :only option' do
13
+ options = Lotus::Routing::Resources::Options.new(@actions, only: :show)
14
+ options.actions.must_equal [:show]
15
+ end
16
+
17
+ it 'returns only the actions requested via the :only options' do
18
+ options = Lotus::Routing::Resources::Options.new(@actions, only: [:create, :edit])
19
+ options.actions.must_equal [:create, :edit]
20
+ end
21
+
22
+ it 'returns only the action not rejected via the :except option' do
23
+ options = Lotus::Routing::Resources::Options.new(@actions, except: :destroy)
24
+ options.actions.must_equal [:index, :new, :create, :show, :edit, :update]
25
+ end
26
+
27
+ it 'returns only the actions requested via the :only options' do
28
+ options = Lotus::Routing::Resources::Options.new(@actions, except: [:index, :new, :edit])
29
+ options.actions.must_equal [:create, :show, :update, :destroy]
30
+ end
31
+
32
+ it 'allow access to values' do
33
+ options = Lotus::Routing::Resources::Options.new(@actions, name: :lotus)
34
+ options[:name].must_equal :lotus
35
+ end
36
+ end