volt 0.8.27.beta6 → 0.8.27.beta7

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 (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