volt 0.8.27.beta6 → 0.8.27.beta7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -3
  3. data/VERSION +1 -1
  4. data/app/volt/models/user.rb +1 -1
  5. data/app/volt/tasks/query_tasks.rb +2 -2
  6. data/app/volt/tasks/store_tasks.rb +14 -4
  7. data/app/volt/tasks/user_tasks.rb +1 -1
  8. data/lib/volt/controllers/http_controller.rb +60 -0
  9. data/lib/volt/controllers/model_controller.rb +5 -1
  10. data/lib/volt/extra_core/string.rb +6 -0
  11. data/lib/volt/models/array_model.rb +6 -2
  12. data/lib/volt/models/associations.rb +1 -1
  13. data/lib/volt/models/buffer.rb +14 -3
  14. data/lib/volt/models/model.rb +28 -60
  15. data/lib/volt/models/permissions.rb +4 -4
  16. data/lib/volt/reactive/computation.rb +15 -15
  17. data/lib/volt/reactive/reactive_array.rb +1 -0
  18. data/lib/volt/router/routes.rb +67 -27
  19. data/lib/volt/server.rb +37 -6
  20. data/lib/volt/server/component_templates.rb +2 -2
  21. data/lib/volt/server/rack/http_request.rb +50 -0
  22. data/lib/volt/server/rack/http_resource.rb +41 -0
  23. data/lib/volt/server/rack/http_response_header.rb +33 -0
  24. data/lib/volt/server/rack/http_response_renderer.rb +41 -0
  25. data/lib/volt/spec/setup.rb +4 -2
  26. data/lib/volt/tasks/dispatcher.rb +7 -7
  27. data/lib/volt/tasks/task_handler.rb +1 -1
  28. data/lib/volt/volt/users.rb +12 -6
  29. data/spec/apps/kitchen_sink/app/main/config/routes.rb +18 -10
  30. data/spec/apps/kitchen_sink/app/main/controllers/main_controller.rb +2 -2
  31. data/spec/apps/kitchen_sink/app/main/controllers/server/simple_http_controller.rb +15 -0
  32. data/spec/apps/kitchen_sink/app/main/controllers/upload_controller.rb +22 -0
  33. data/spec/apps/kitchen_sink/app/main/views/main/yield.html +2 -2
  34. data/spec/apps/kitchen_sink/app/main/views/upload/index.html +15 -0
  35. data/spec/controllers/http_controller_spec.rb +130 -0
  36. data/spec/extra_core/string_transformation_test_cases.rb +8 -0
  37. data/spec/extra_core/string_transformations_spec.rb +12 -0
  38. data/spec/integration/http_endpoints_spec.rb +29 -0
  39. data/spec/integration/user_spec.rb +42 -42
  40. data/spec/models/associations_spec.rb +4 -4
  41. data/spec/models/buffer_spec.rb +15 -0
  42. data/spec/models/model_spec.rb +70 -25
  43. data/spec/models/model_state_spec.rb +1 -1
  44. data/spec/models/permissions_spec.rb +64 -2
  45. data/spec/models/persistors/params_spec.rb +8 -8
  46. data/spec/models/persistors/store_spec.rb +1 -1
  47. data/spec/models/user_validation_spec.rb +1 -1
  48. data/spec/router/routes_spec.rb +111 -43
  49. data/spec/server/rack/http_request_spec.rb +50 -0
  50. data/spec/server/rack/http_resource_spec.rb +59 -0
  51. data/spec/server/rack/http_response_header_spec.rb +34 -0
  52. data/spec/server/rack/http_response_renderer_spec.rb +33 -0
  53. data/spec/tasks/dispatcher_spec.rb +2 -2
  54. data/templates/component/config/routes.rb +2 -2
  55. data/templates/project/Gemfile.tt +3 -5
  56. data/templates/project/app/main/config/routes.rb +4 -4
  57. data/volt.gemspec +2 -2
  58. metadata +33 -8
@@ -40,9 +40,10 @@ module Volt
40
40
  def on_invalidate(&callback)
41
41
  if @invalidated
42
42
  # Call invalidate now, since its already invalidated
43
- Computation.run_without_tracking do
43
+ # Computation.run_without_tracking do
44
+ queue_flush!
44
45
  callback.call
45
- end
46
+ # end
46
47
  else
47
48
  # Store the invalidation
48
49
  @invalidations << callback
@@ -57,14 +58,7 @@ module Volt
57
58
  @invalidated = true
58
59
 
59
60
  unless @stopped || @computing
60
- @@flush_queue << self
61
-
62
- # If we are in the browser, we queue a flush for the next tick
63
- if Volt.in_browser?
64
- self.class.queue_flush!
65
- end
66
-
67
- # If we are not in the browser, the user must manually flush
61
+ queue_flush!
68
62
  end
69
63
 
70
64
  invalidations = @invalidations
@@ -112,7 +106,7 @@ module Volt
112
106
 
113
107
  @flushing = true
114
108
  # clear any timers
115
- @timer = nil
109
+ @@timer = nil
116
110
 
117
111
  computations = @@flush_queue
118
112
  @@flush_queue = []
@@ -122,10 +116,16 @@ module Volt
122
116
  @flushing = false
123
117
  end
124
118
 
125
- def self.queue_flush!
126
- unless @timer
127
- # Flush once everything else has finished running
128
- @timer = `setImmediate(function() { self['$flush!'](); });`
119
+ def queue_flush!
120
+ @@flush_queue << self
121
+
122
+ # If we are in the browser, we queue a flush for the next tick
123
+ # If we are not in the browser, the user must manually flush
124
+ if Volt.in_browser?
125
+ unless @@timer
126
+ # Flush once everything else has finished running
127
+ @@timer = `setImmediate(function() { self.$class()['$flush!'](); })`
128
+ end
129
129
  end
130
130
  end
131
131
  end
@@ -33,6 +33,7 @@ module Volt
33
33
  def count(&block)
34
34
  if block
35
35
  count = 0
36
+
36
37
  size.times do |index|
37
38
  if block.call(self[index]).true?
38
39
  count += 1
@@ -4,12 +4,12 @@ module Volt
4
4
  # The Routes class takes a set of routes and sets up methods to go from
5
5
  # a url to params, and params to url.
6
6
  # routes do
7
- # get "/about", _view: 'about'
8
- # get "/blog/{_id}/edit", _view: 'blog/edit', _action: 'edit'
9
- # get "/blog/{_id}", _view: 'blog/show', _action: 'show'
10
- # get "/blog", _view: 'blog'
11
- # get "/blog/new", _view: 'blog/new', _action: 'new'
12
- # get "/cool/{_name}", _view: 'cool'
7
+ # client "/about", _view: 'about'
8
+ # client "/blog/{_id}/edit", _view: 'blog/edit', _action: 'edit'
9
+ # client "/blog/{_id}", _view: 'blog/show', _action: 'show'
10
+ # client "/blog", _view: 'blog'
11
+ # client "/blog/new", _view: 'blog/new', _action: 'new'
12
+ # client "/cool/{_name}", _view: 'cool'
13
13
  # end
14
14
  #
15
15
  # Using the routes above, we would generate the following:
@@ -47,7 +47,13 @@ module Volt
47
47
  @indirect_routes = {}
48
48
 
49
49
  # Matcher for going from params to url
50
- @param_matches = []
50
+ @param_matches = {}
51
+
52
+ [:client, :get, :post, :put, :patch, :delete].each do |method|
53
+ @direct_routes[method] = {}
54
+ @indirect_routes[method] = {}
55
+ @param_matches[method] = []
56
+ end
51
57
  end
52
58
 
53
59
  def define(&block)
@@ -57,15 +63,29 @@ module Volt
57
63
  end
58
64
 
59
65
  # Add a route
60
- def get(path, params = {})
61
- params = params.symbolize_keys
62
- if has_binding?(path)
63
- add_indirect_path(path, params)
64
- else
65
- @direct_routes[path] = params
66
- end
66
+ def client(path, params = {})
67
+ create_route(:client, path, params)
68
+ end
69
+
70
+ # Add server side routes
71
+ def get(path, params)
72
+ create_route(:get, path, params)
73
+ end
74
+
75
+ def post(path, params)
76
+ create_route(:post, path, params)
77
+ end
78
+
79
+ def patch(path, params)
80
+ create_route(:patch, path, params)
81
+ end
82
+
83
+ def put(path, params)
84
+ create_route(:put, path, params)
85
+ end
67
86
 
68
- add_param_matcher(path, params)
87
+ def delete(path, params)
88
+ create_route(:delete, path, params)
69
89
  end
70
90
 
71
91
  # Takes in params and generates a path and the remaining params
@@ -74,18 +94,20 @@ module Volt
74
94
  #
75
95
  # returns the url and new params, or nil, nil if no match is found.
76
96
  def params_to_url(test_params)
97
+ # Extract the desired method from the params
98
+ method = test_params.delete(:method) || :client
99
+ method = method.to_sym
100
+
77
101
  # Add in underscores
78
102
  test_params = test_params.each_with_object({}) do |(k, v), obj|
79
103
  obj[k.to_sym] = v
80
104
  end
81
105
 
82
- @param_matches.each do |param_matcher|
106
+ @param_matches[method].each do |param_matcher|
83
107
  # TODO: Maybe a deep dup?
84
108
  result, new_params = check_params_match(test_params.dup, param_matcher[0])
85
109
 
86
- if result
87
- return param_matcher[1].call(new_params)
88
- end
110
+ return param_matcher[1].call(new_params) if result
89
111
  end
90
112
 
91
113
  [nil, nil]
@@ -93,19 +115,39 @@ module Volt
93
115
 
94
116
  # Takes in a path and returns the matching params.
95
117
  # returns params as a hash
96
- def url_to_params(path)
118
+ def url_to_params(*args)
119
+ if args.size < 2
120
+ path = args[0]
121
+ method = :client
122
+ else
123
+ path = args[1]
124
+ method = args[0].to_sym
125
+ end
126
+
97
127
  # First try a direct match
98
- result = @direct_routes[path]
128
+ result = @direct_routes[method][path]
99
129
  return result if result
100
130
 
101
131
  # Next, split the url and walk the sections
102
132
  parts = url_parts(path)
103
133
 
104
- match_path(parts, parts, @indirect_routes)
134
+ match_path(parts, parts, @indirect_routes[method])
105
135
  end
106
136
 
107
137
  private
108
138
 
139
+ def create_route(method, path, params)
140
+ params = params.symbolize_keys
141
+ method = method.to_sym
142
+ if has_binding?(path)
143
+ add_indirect_path(@indirect_routes[method], path, params)
144
+ else
145
+ @direct_routes[method][path] = params
146
+ end
147
+
148
+ add_param_matcher(method, path, params)
149
+ end
150
+
109
151
  # Recursively walk the @indirect_routes hash, return the params for a route, return
110
152
  # false for non-matches.
111
153
  def match_path(original_parts, remaining_parts, node)
@@ -153,9 +195,7 @@ module Volt
153
195
  # nil means a terminal, who's value will be the params.
154
196
  #
155
197
  # In the params, an integer vaule means the index of the wildcard
156
- def add_indirect_path(path, params)
157
- node = @indirect_routes
158
-
198
+ def add_indirect_path(node, path, params)
159
199
  parts = url_parts(path)
160
200
 
161
201
  parts.each_with_index do |part, index|
@@ -173,7 +213,7 @@ module Volt
173
213
  node[nil] = params
174
214
  end
175
215
 
176
- def add_param_matcher(path, params)
216
+ def add_param_matcher(method, path, params)
177
217
  params = params.dup
178
218
  parts = url_parts(path)
179
219
 
@@ -187,7 +227,7 @@ module Volt
187
227
 
188
228
  path_transformer = create_path_transformer(parts)
189
229
 
190
- @param_matches << [params, path_transformer]
230
+ @param_matches[method] << [params, path_transformer]
191
231
  end
192
232
 
193
233
  # Takes in url parts and returns a proc that takes in params and returns
data/lib/volt/server.rb CHANGED
@@ -26,10 +26,14 @@ if RUBY_PLATFORM != 'java'
26
26
  end
27
27
  require 'volt/server/rack/component_paths'
28
28
  require 'volt/server/rack/index_files'
29
+ require 'volt/server/rack/http_resource'
29
30
  require 'volt/server/rack/opal_files'
30
31
  require 'volt/server/rack/quiet_common_logger'
31
32
  require 'volt/page/page'
32
33
 
34
+ require 'volt/server/rack/http_request'
35
+ require 'volt/controllers/http_controller'
36
+
33
37
  module Rack
34
38
  # TODO: For some reason in Rack (or maybe thin), 304 headers close
35
39
  # the http connection. We might need to make this check if keep
@@ -53,6 +57,7 @@ end
53
57
 
54
58
  module Volt
55
59
  class Server
60
+
56
61
  def initialize(root_path = nil)
57
62
  root_path ||= Dir.pwd
58
63
  Volt.root = root_path
@@ -62,6 +67,8 @@ module Volt
62
67
  # Boot the volt app
63
68
  @component_paths = Volt.boot(root_path)
64
69
 
70
+ setup_router
71
+ require_http_controllers
65
72
  setup_change_listener
66
73
 
67
74
  display_welcome
@@ -71,10 +78,32 @@ module Volt
71
78
  puts File.read(File.join(File.dirname(__FILE__), 'server/banner.txt'))
72
79
  end
73
80
 
81
+ def setup_router
82
+ # Find the route file
83
+ home_path = @component_paths.component_paths('main').first
84
+ routes = File.read("#{home_path}/config/routes.rb")
85
+ @router = Routes.new.define do
86
+ eval(routes)
87
+ end
88
+ end
89
+
90
+ def require_http_controllers
91
+ @component_paths.app_folders do |app_folder|
92
+ # Sort so we get consistent load order across platforms
93
+ Dir["#{app_folder}/*/controllers/server/*.rb"].each do |ruby_file|
94
+ #path = ruby_file.gsub(/^#{app_folder}\//, '')[0..-4]
95
+ #require(path)
96
+ load ruby_file
97
+ end
98
+ end
99
+ end
100
+
74
101
  def setup_change_listener
75
102
  # Setup the listeners for file changes
76
103
  listener = Listen.to("#{@app_path}/") do |modified, added, removed|
77
104
  puts 'file changed, sending reload'
105
+ setup_router
106
+ require_http_controllers
78
107
  SocketConnectionHandler.send_message_all(nil, 'reload')
79
108
  end
80
109
  listener.start
@@ -110,6 +139,8 @@ module Volt
110
139
  # which JS/CSS files to serve.
111
140
  @app.use IndexFiles, @component_paths, opal_files
112
141
 
142
+ @app.use HttpResource, @router
143
+
113
144
  component_paths.require_in_components
114
145
 
115
146
  # Handle socks js connection
@@ -122,12 +153,12 @@ module Volt
122
153
  end
123
154
 
124
155
  @app.use Rack::Static,
125
- urls: ['/'],
126
- root: 'config/base',
127
- index: '',
128
- header_rules: [
129
- [:all, { 'Cache-Control' => 'public, max-age=86400' }]
130
- ]
156
+ urls: ['/'],
157
+ root: 'config/base',
158
+ index: '',
159
+ header_rules: [
160
+ [:all, { 'Cache-Control' => 'public, max-age=86400' }]
161
+ ]
131
162
 
132
163
  @app.run lambda { |env| [404, { 'Content-Type' => 'text/html; charset=utf-8' }, ['404 - page not found']] }
133
164
 
@@ -95,8 +95,8 @@ module Volt
95
95
  end
96
96
 
97
97
  def generate_tasks_code
98
- TaskHandler.known_handlers.map do |handler|
99
- "class #{handler.name} < Volt::TaskHandler; end"
98
+ Task.known_handlers.map do |handler|
99
+ "class #{handler.name} < Volt::Task; end"
100
100
  end.join "\n"
101
101
  end
102
102
  end
@@ -0,0 +1,50 @@
1
+ require 'volt'
2
+ require 'rack'
3
+
4
+ module Volt
5
+ # A request object for a HttpController. See Rack::Request for more details
6
+ class HttpRequest < Rack::Request
7
+ # Returns the request format
8
+ # /blub/index.html => html
9
+ # Defaults to the media_type of the request
10
+ def format
11
+ path_format || media_type
12
+ end
13
+
14
+ # Returns the path_info without the format
15
+ # /blub/index.html => /blub/index
16
+ def path
17
+ path_format ? path_info[0..path_format.size * -1 - 2] : path_info
18
+ end
19
+
20
+ # Returns the request method
21
+ # Allows requests to override the http request method by setting _method
22
+ def method
23
+ if params[:_method]
24
+ params[:_method].to_s.downcase.to_sym
25
+ else
26
+ request_method.downcase.to_sym
27
+ end
28
+ end
29
+
30
+ # The request params with symbolized keys
31
+ def params
32
+ super.symbolize_keys
33
+ end
34
+
35
+ private
36
+
37
+ # Returns the format given in the path_info
38
+ # http://example.com/test.html => html
39
+ # http://example.com/test => nil
40
+ def path_format
41
+ @path_format ||= extract_format_from_path
42
+ end
43
+
44
+ # Extract from the path
45
+ def extract_format_from_path
46
+ format = path_info.match(/\.(\w+)$/)
47
+ format.present? ? format[1] : nil
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,41 @@
1
+ require 'rack'
2
+ require 'volt'
3
+ require 'volt/router/routes'
4
+ require 'volt/server/rack/http_request'
5
+
6
+ module Volt
7
+ # Rack middleware for HttpController
8
+ class HttpResource
9
+ def initialize(app, router)
10
+ @app = app
11
+ @router = router
12
+ end
13
+
14
+ def call(env)
15
+ request = HttpRequest.new(env)
16
+ params = routes_match?(request)
17
+ if params
18
+ dispatch_to_controller(params, request)
19
+ else
20
+ @app.call env
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def routes_match?(request)
27
+ @router.url_to_params(request.method, request.path)
28
+ end
29
+
30
+ # Find the correct controller and call the correct action on it.
31
+ # The controller name and actions need to be set as params for the
32
+ # matching route
33
+ def dispatch_to_controller(params, request)
34
+ controller_name = params[:controller] + '_controller'
35
+ action = params[:action]
36
+ klass = Object.const_get(controller_name.camelize.to_sym)
37
+ controller = klass.new(params, request)
38
+ controller.perform(action)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,33 @@
1
+ require 'volt'
2
+
3
+ module Volt
4
+ # Wrapper around a Hash for easy http header creation / manipulation with
5
+ # indifferent access.
6
+ # header[:content_type] == header['Content-Type'] ==
7
+ # header['content-type'] == header ['Content_Type']
8
+ class HttpResponseHeader < Hash
9
+ def []=(key, value)
10
+ super(key.to_s.headerize, value)
11
+ end
12
+
13
+ def [](key)
14
+ super(key.to_s.headerize)
15
+ end
16
+
17
+ def delete(key)
18
+ super(key.to_s.headerize)
19
+ end
20
+
21
+ def merge(other)
22
+ dup.merge!(other)
23
+ end
24
+
25
+ def merge!(other)
26
+ new_hash = {}
27
+ other.each_with_object(new_hash) do |(key, value), hash|
28
+ self[key.to_s.headerize] = value
29
+ end
30
+ self
31
+ end
32
+ end
33
+ end