rocketio 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/lib/rocketio.rb +6 -3
  3. data/lib/rocketio/application.rb +3 -7
  4. data/lib/rocketio/controller.rb +62 -90
  5. data/lib/rocketio/controller/authentication.rb +38 -44
  6. data/lib/rocketio/controller/authorization.rb +8 -4
  7. data/lib/rocketio/controller/error_handlers.rb +12 -8
  8. data/lib/rocketio/controller/filters.rb +14 -19
  9. data/lib/rocketio/controller/helpers.rb +1 -1
  10. data/lib/rocketio/controller/middleware.rb +1 -1
  11. data/lib/rocketio/controller/render/engine.rb +3 -3
  12. data/lib/rocketio/controller/render/layout.rb +1 -1
  13. data/lib/rocketio/controller/render/layouts.rb +6 -6
  14. data/lib/rocketio/controller/render/template_vars.rb +3 -3
  15. data/lib/rocketio/controller/render/templates.rb +6 -6
  16. data/lib/rocketio/controller/sessions.rb +1 -1
  17. data/lib/rocketio/error_templates/409.html +11 -7
  18. data/lib/rocketio/error_templates/501.html +4 -4
  19. data/lib/rocketio/router.rb +35 -21
  20. data/lib/rocketio/version.rb +1 -1
  21. data/rocketio.gemspec +2 -0
  22. data/test/aliases_test.rb +2 -2
  23. data/test/api_test.rb +24 -117
  24. data/test/authentication_test.rb +96 -60
  25. data/test/authorization_test.rb +28 -17
  26. data/test/cache_control_test.rb +12 -12
  27. data/test/content_type_test.rb +7 -7
  28. data/test/cookies_test.rb +4 -4
  29. data/test/error_handlers_test.rb +14 -12
  30. data/test/etag_test.rb +32 -32
  31. data/test/filters_test.rb +96 -79
  32. data/test/halt_test.rb +1 -1
  33. data/test/helpers_test.rb +6 -6
  34. data/test/middleware_test.rb +4 -4
  35. data/test/redirect_test.rb +6 -7
  36. data/test/render/{post.erb → b.erb} +0 -0
  37. data/test/render/{put.erb → c.erb} +0 -0
  38. data/test/render/engine_test.rb +5 -5
  39. data/test/render/{get.erb → index.erb} +0 -0
  40. data/test/render/layout_test.rb +21 -17
  41. data/test/render/layouts_test.rb +14 -14
  42. data/test/render/render_test.rb +17 -14
  43. data/test/render/template_vars_test.rb +9 -9
  44. data/test/render/templates_test.rb +16 -16
  45. data/test/response_test.rb +4 -4
  46. data/test/routes_test.rb +21 -42
  47. data/test/sendfile_test.rb +8 -8
  48. data/test/sessions_test.rb +27 -27
  49. data/test/setup.rb +2 -0
  50. metadata +34 -6
@@ -13,8 +13,8 @@ module RocketIO
13
13
  #
14
14
  def self.token_auth *args, &block
15
15
  opts = args.last.is_a?(Hash) ? args.pop : {}
16
- (args.any? ? args.map!(&:to_sym) : RocketIO::REQUEST_METHODS.values).each do |rm|
17
- (@__token_auth__ ||= {})[rm] = {
16
+ (args.any? ? args.map(&:to_sym) : [:*]).each do |method|
17
+ (@__token_auth__ ||= {})[method] = {
18
18
  realm: opts[:realm] || RocketIO::DEFAULT_TOKEN_AUTH_REALM.freeze,
19
19
  block: block
20
20
  }
@@ -25,13 +25,13 @@ module RocketIO
25
25
  def self.define_token_auth_methods source = self
26
26
  prompts = allocate.token_auth.merge(source.instance_variable_get(:@__token_auth__) || {}).freeze
27
27
  return if prompts.empty?
28
- private_api << define_method(:token_auth) {prompts}
28
+ api.delete define_method(:token_auth) {prompts}
29
29
  end
30
30
 
31
31
  def token_auth; RocketIO::EMPTY_HASH end
32
32
 
33
33
  def validate_or_request_authorization_if_needed
34
- return unless auth = token_auth[requested_method]
34
+ return unless auth = authorization_required?
35
35
  return if validate_token_auth(&auth[:block])
36
36
  throw(:__response__, request_token_auth(auth[:realm]))
37
37
  end
@@ -47,5 +47,9 @@ module RocketIO
47
47
  def request_token_auth realm = RocketIO::DEFAULT_TOKEN_AUTH_REALM
48
48
  RocketIO::TokenAuth.authentication_request(realm)
49
49
  end
50
+
51
+ def authorization_required?
52
+ token_auth[requested_method] || token_auth[:*]
53
+ end
50
54
  end
51
55
  end
@@ -38,12 +38,12 @@ module RocketIO
38
38
  def self.define_error_handlers_methods source = self
39
39
  handlers = (source.instance_variable_get(:@__error_handlers__) || {}).each_with_object({}) do |(code,block),o|
40
40
  o[code] = :"__#{code}_error_handler__"
41
- private_api << define_method(o[code], &block)
41
+ api.delete define_method(o[code], &block)
42
42
  end
43
43
  handlers.update(allocate.error_handlers)
44
44
  return if handlers.empty?
45
45
  handlers.freeze
46
- private_api << define_method(:error_handlers) {handlers}
46
+ api.delete define_method(:error_handlers) {handlers}
47
47
  end
48
48
 
49
49
  def error_handlers; RocketIO::EMPTY_HASH end
@@ -76,17 +76,21 @@ module RocketIO
76
76
  # 409: Wrong number of arguments received
77
77
  error 409 do
78
78
  RocketIO.error_renderer(409, xhr?, {
79
- env: env,
80
- controller: self.class,
81
- resolved_path: url,
82
- expected_parameters: parameters_policy[env[RocketIO::REQUEST_METHOD]],
83
- received_parameters: path_params
79
+ url: url,
80
+ controller: self.class.to_s,
81
+ requested_method: requested_method,
82
+ expected: api[requested_method][:parameters_policy],
83
+ received: path_params_array
84
84
  })
85
85
  end
86
86
 
87
87
  # 501: Not Implemented
88
88
  error 501 do
89
- RocketIO.error_renderer(501, xhr?, env: env)
89
+ RocketIO.error_renderer(501, xhr?, {
90
+ url: url,
91
+ controller: self.class.to_s,
92
+ requested_method: requested_method
93
+ })
90
94
  end
91
95
  end
92
96
  end
@@ -11,47 +11,42 @@ module RocketIO
11
11
  # and can override them selectively, by name
12
12
  #
13
13
  # @note wildcard filters will run before/around/after any methods,
14
- # that's it, if defining `before {}` and `before(:get) {}` filters
15
- # `get` method will run `before` filter then `before(:get)`
14
+ # that's it, if defining `before {}` and `before(:index) {}` filters
15
+ # `index` method will run `before` filter then `before(:index)`
16
16
  #
17
- # @example run before any requested method, being it REST or websocket
17
+ # @example run before any method
18
18
  # before do
19
19
  # # some logic here
20
20
  # end
21
21
  #
22
- # @example run only before GET
23
- # before :get do
22
+ # @example run only before :create
23
+ # before :create do
24
24
  # # some logic here
25
25
  # end
26
26
  #
27
- # @example run only around PUT and POST
28
- # around :put, :post do |app|
27
+ # @example run only around :create and :edit
28
+ # around :create, :edit do |app|
29
29
  # # some logic here
30
30
  # app.call
31
31
  # end
32
32
  #
33
- # @example run only after :register websocket call
34
- # after :register do
35
- # # some logic here
36
- # end
37
- #
38
33
  # @example define a filter that does nothing. useful to override inherited filters.
39
- # before :get
34
+ # before {}
40
35
  #
41
- # @example run 2 blocks before GET
36
+ # @example run 2 blocks before :create
42
37
  # before do # wildcard filter, will run before any method
43
38
  # @user = User.find...
44
39
  # end
45
40
  #
46
- # before :get do # named filter, will run only before `get`
41
+ # before :create do # named filter, will run only before `create`
47
42
  # # wildcard filter already executed so we have `@user` variable here
48
43
  # @photo = @user.photos.find...
49
44
  # end
50
45
  #
51
- # # before calling `get` two filters will be executed:
46
+ # # at the moment we call :create two filters was executed:
52
47
  # # - wildcard one
53
48
  # # - named one
54
- # def get
49
+ # def create
55
50
  # # both @user and @photo variables available here
56
51
  # end
57
52
  #
@@ -75,12 +70,12 @@ module RocketIO
75
70
  define_singleton_method define_methods do |source = self|
76
71
  filters = (source.instance_variable_get(var) || {}).each_with_object({}) do |(meth,block),o|
77
72
  o[meth] = :"__#{filter}_#{meth}__"
78
- private_api << define_method(o[meth], &block)
73
+ api.delete define_method(o[meth], &block)
79
74
  end
80
75
  filters.update(allocate.__send__(filter))
81
76
  return unless filters.any?
82
77
  filters.freeze
83
- private_api << define_method(filter) {filters}
78
+ api.delete define_method(filter) {filters}
84
79
  end
85
80
  end
86
81
 
@@ -83,7 +83,7 @@ module RocketIO
83
83
  # pass User, :bob, :bobsen
84
84
  #
85
85
  def pass controller, *args
86
- halt controller.initialize_controller(RocketIO::REQUEST_METHODS[request_method], args).call(env)
86
+ halt controller.new(requested_method, args).call(env)
87
87
  end
88
88
 
89
89
  # stop executing any code and send response to browser.
@@ -24,7 +24,7 @@ module RocketIO
24
24
  def self.define_middleware_methods source = self
25
25
  middleware = ((source.instance_variable_get(:@__middleware__) || []) + allocate.middleware).uniq.freeze
26
26
  return if middleware.empty?
27
- private_api << define_method(:middleware) {middleware}
27
+ api.delete define_method(:middleware) {middleware}
28
28
  end
29
29
 
30
30
  def middleware; RocketIO::EMPTY_ARRAY end
@@ -60,14 +60,14 @@ module RocketIO
60
60
  return unless engine = source.instance_variable_get(:@__engine__)
61
61
  if Proc === engine
62
62
  selfengine = allocate.engine
63
- private_api << define_method(:__rocketio_engine__, &engine)
64
- private_api << define_method(:engine) {
63
+ api.delete define_method(:__rocketio_engine__, &engine)
64
+ api.delete define_method(:engine) {
65
65
  engine, *engine_options = __rocketio_engine__
66
66
  return selfengine unless engine
67
67
  [RocketIO.engine_class(engine), engine_options.freeze].freeze
68
68
  }
69
69
  else
70
- private_api << define_method(:engine) {engine}
70
+ api.delete define_method(:engine) {engine}
71
71
  end
72
72
  end
73
73
 
@@ -17,7 +17,7 @@ module RocketIO
17
17
  def self.define_layout_methods source = self
18
18
  return unless source.instance_variables.include?(:@__layout__)
19
19
  layout = source.instance_variable_get(:@__layout__)
20
- private_api << define_method(:layout) {layout}
20
+ api.delete define_method(:layout) {layout}
21
21
  end
22
22
 
23
23
  # by default no layout used, so this method returns nil.
@@ -59,25 +59,25 @@ module RocketIO
59
59
  o[name] = :"__#{name}_layout__"
60
60
  if setup[:block]
61
61
  # block given, do not search for file, use returned value instead
62
- private_api << define_method(o[name], &setup[:block])
62
+ api.delete define_method(o[name], &setup[:block])
63
63
  elsif setup[:file]
64
64
  # file given, search the file in original controller dirname
65
- private_api << meth_name = :"__#{name}_layout_file__"
65
+ meth_name = :"__#{name}_layout_file__"
66
66
  meth_proc = setup[:file].is_a?(::Proc) ? setup[:file] : -> {setup[:file]}
67
- private_api << define_method(meth_name, &meth_proc)
68
- private_api << define_method(o[name]) {
67
+ api.delete define_method(meth_name, &meth_proc)
68
+ api.delete define_method(o[name]) {
69
69
  engine, * = resolve_engine
70
70
  read_template(find_template(setup[:root], __send__(meth_name), engine))
71
71
  }
72
72
  else
73
73
  # only name given, search for a file with same name in controller's dirname
74
- private_api << define_method(o[name]) {
74
+ api.delete define_method(o[name]) {
75
75
  engine, * = resolve_engine
76
76
  read_template(find_template(self.dirname, setup[:name], engine))
77
77
  }
78
78
  end
79
79
  end.freeze
80
- private_api << define_method(:layouts) {layouts}
80
+ api.delete define_method(:layouts) {layouts}
81
81
  end
82
82
 
83
83
  def layouts; RocketIO::EMPTY_HASH end
@@ -17,12 +17,12 @@ module RocketIO
17
17
  vars = source.instance_variable_get(:@__template_vars__).each_with_object(allocate.__template_vars__.dup) do |(name,value),o|
18
18
  o[name] = :"__#{name}_template_var__"
19
19
  if value.is_a?(Proc)
20
- private_api << define_method(o[name], &value)
20
+ api.delete define_method(o[name], &value)
21
21
  else
22
- private_api << define_method(o[name]) {value}
22
+ api.delete define_method(o[name]) {value}
23
23
  end
24
24
  end.freeze
25
- private_api << define_method(:__template_vars__) {vars}
25
+ api.delete define_method(:__template_vars__) {vars}
26
26
  end
27
27
 
28
28
  def __template_vars__; RocketIO::EMPTY_HASH end
@@ -57,25 +57,25 @@ module RocketIO
57
57
  o[name] = :"__#{name}_template__"
58
58
  if setup[:block]
59
59
  # block given, do not search for file, use returned value instead
60
- private_api << define_method(o[name], &setup[:block])
60
+ api.delete define_method(o[name], &setup[:block])
61
61
  elsif setup[:file]
62
62
  # file given, search the file in original controller dirname
63
- private_api << meth_name = :"__#{name}_template_file__"
63
+ meth_name = :"__#{name}_template_file__"
64
64
  meth_proc = setup[:file].is_a?(Proc) ? setup[:file] : -> {setup[:file]}
65
- private_api << define_method(meth_name, &meth_proc)
66
- private_api << define_method(o[name]) {
65
+ api.delete define_method(meth_name, &meth_proc)
66
+ api.delete define_method(o[name]) {
67
67
  engine, * = resolve_engine
68
68
  read_template(find_template(setup[:root], __send__(meth_name), engine))
69
69
  }
70
70
  else
71
71
  # only name given, search for a file with same name in controller's dirname
72
- private_api << define_method(o[name]) {
72
+ api.delete define_method(o[name]) {
73
73
  engine, * = resolve_engine
74
74
  read_template(find_template(self.dirname, setup[:name], engine))
75
75
  }
76
76
  end
77
77
  end.freeze
78
- private_api << define_method(:templates) {templates}
78
+ api.delete define_method(:templates) {templates}
79
79
  end
80
80
 
81
81
  def templates; RocketIO::EMPTY_HASH end
@@ -56,7 +56,7 @@ module RocketIO
56
56
  def self.define_sessions_methods source = self
57
57
  return unless source.instance_variables.include?(:@__sessions__)
58
58
  sessions = source.instance_variable_get(:@__sessions__)
59
- private_api << define_method(:sessions) {sessions}
59
+ api.delete define_method(:sessions) {sessions}
60
60
  end
61
61
 
62
62
  def sessions; end
@@ -1,7 +1,11 @@
1
- <h1>Wrong number of arguments received</h1>
2
- <h2>
3
- Controller: {{controller}}<br>
4
- Resolved Path: {{resolved_path}}<br>
5
- Expected Parameters: {{expected_parameters}}<br>
6
- Received Parameters: {{received_parameters}}<br>
7
- </h2>
1
+ <h1>409: Wrong arguments</h1>
2
+ <h3>
3
+ <b>{{url}}</b> URL resolved to <b>{{controller}}#{{requested_method}}</b> method
4
+ but it was called with wrong arguments.
5
+ <div>
6
+ Expected: {min: {{expected.min}}, max: {{expected.max}}}
7
+ </div>
8
+ <div>
9
+ Received: {{received}}
10
+ </div>
11
+ </h3>
@@ -1,6 +1,6 @@
1
1
  <h1>501: Not Implemented</h1>
2
2
  <hr>
3
- <h2>
4
- "{{env.PATH_INFO}}" page found
5
- but it is not supposed to work through "{{env.REQUEST_METHOD}}" request method
6
- </h2>
3
+ <h3>
4
+ <b>{{url}}</b> URL resolved to <b>{{controller}}</b> controller
5
+ but it does not respond to <b>{{requested_method}}</b> method
6
+ </h3>
@@ -1,26 +1,42 @@
1
1
  module RocketIO
2
2
  class Router
3
- ROOT_PATH_PIECES = [''.freeze].freeze
3
+
4
+ ROOT_PATH_PIECES = [EMPTY_STRING].freeze
5
+ PATH_SPLITTER = /\/+/.freeze
6
+
4
7
  attr_reader :controllers, :routes
5
8
 
6
9
  def initialize *controllers
7
10
  @controllers = controllers.flatten.compact.uniq
8
- @routes = build_routes
11
+ @controller_routes = controller_routes()
9
12
  freeze!
10
13
  end
11
14
 
15
+ # fully traversing the tree and use the last matched controller
12
16
  def resolve_path path
13
- controllers = @routes
17
+ controller_routes = @controller_routes
14
18
  pieces = path_pieces(path)
15
- depth = -1
16
- controller = nil
17
- offset = 0
18
- while controllers = controllers[pieces[depth += 1]]
19
- next unless controllers[0]
20
- controller = controllers[0]
21
- offset = depth + 1
19
+ depth = -1
20
+ controller = nil
21
+ path_params = []
22
+
23
+ while controller_routes = controller_routes[pieces[depth += 1]]
24
+ # do not use `next unless controller = controller_routes[0]` here
25
+ # cause this may override earlier found controller.
26
+ next unless controller_routes[0]
27
+ controller = controller_routes[0]
28
+ path_params = pieces[ (depth + 1) .. -1 ]
22
29
  end
23
- [controller, pieces[offset .. -1]]
30
+
31
+ return EMPTY_ARRAY unless controller
32
+
33
+ method = if path_params.any? && controller.api[path_params[0].to_sym]
34
+ path_params.slice!(0).to_sym
35
+ else
36
+ INDEX_METHOD
37
+ end
38
+
39
+ [controller, method, path_params]
24
40
  end
25
41
 
26
42
  private
@@ -34,30 +50,28 @@ module RocketIO
34
50
  #
35
51
  def path_pieces path
36
52
  return ROOT_PATH_PIECES if path == SLASH # that's root path, /
37
- path.to_s.split(/\/+/)
53
+ path.to_s.split(PATH_SPLITTER)
38
54
  end
39
55
 
40
56
  # building a routing tree
41
57
  #
42
58
  # @return [Hash]
43
59
  #
44
- def build_routes
45
- map = {}
46
- @controllers.each do |controller|
60
+ def controller_routes
61
+ controllers.each_with_object({}) do |controller,map|
47
62
  [controller.url, *controller.aliases].each do |url|
48
63
  depth = 0
49
64
  pieces = path_pieces(url)
50
- pieces.inject(map) do |map,chunk|
51
- map[chunk] ||= {}
52
- map[chunk][0] = controller if pieces.size == (depth += 1)
53
- map[chunk]
65
+ pieces.reduce(map) do |pieces_map,chunk|
66
+ pieces_map[chunk] ||= {}
67
+ pieces_map[chunk][0] = controller if pieces.size == (depth += 1)
68
+ pieces_map[chunk]
54
69
  end
55
70
  end
56
71
  end
57
- map
58
72
  end
59
73
 
60
- # freezing the application cause modifying it at runtime may blow the universe
74
+ # freezing the router cause modifying it at runtime may blow the universe
61
75
  def freeze!
62
76
  self.freeze
63
77
  end
@@ -1,3 +1,3 @@
1
1
  module RocketIO
2
- VERSION = '0.1.0'.freeze
2
+ VERSION = '0.2.0'.freeze
3
3
  end
data/rocketio.gemspec CHANGED
@@ -24,4 +24,6 @@ Gem::Specification.new do |spec|
24
24
  spec.add_development_dependency 'rake', '~> 10.0'
25
25
  spec.add_development_dependency 'tokyo', '~> 0'
26
26
  spec.add_development_dependency 'rack-radar', '~> 0'
27
+ spec.add_development_dependency 'pry', '~> 0'
28
+ spec.add_development_dependency 'pry-byebug', '~> 3'
27
29
  end
data/test/aliases_test.rb CHANGED
@@ -4,13 +4,13 @@ class A < RocketIO::Controller
4
4
  alias_url :x
5
5
  alias_url :y
6
6
 
7
- def get; url end
7
+ def index; url end
8
8
 
9
9
  class B < self
10
10
  alias_url :x
11
11
  alias_url :y
12
12
 
13
- def get; url end
13
+ def index; url end
14
14
  end
15
15
  end
16
16