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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -3
- data/VERSION +1 -1
- data/app/volt/models/user.rb +1 -1
- data/app/volt/tasks/query_tasks.rb +2 -2
- data/app/volt/tasks/store_tasks.rb +14 -4
- data/app/volt/tasks/user_tasks.rb +1 -1
- data/lib/volt/controllers/http_controller.rb +60 -0
- data/lib/volt/controllers/model_controller.rb +5 -1
- data/lib/volt/extra_core/string.rb +6 -0
- data/lib/volt/models/array_model.rb +6 -2
- data/lib/volt/models/associations.rb +1 -1
- data/lib/volt/models/buffer.rb +14 -3
- data/lib/volt/models/model.rb +28 -60
- data/lib/volt/models/permissions.rb +4 -4
- data/lib/volt/reactive/computation.rb +15 -15
- data/lib/volt/reactive/reactive_array.rb +1 -0
- data/lib/volt/router/routes.rb +67 -27
- data/lib/volt/server.rb +37 -6
- data/lib/volt/server/component_templates.rb +2 -2
- data/lib/volt/server/rack/http_request.rb +50 -0
- data/lib/volt/server/rack/http_resource.rb +41 -0
- data/lib/volt/server/rack/http_response_header.rb +33 -0
- data/lib/volt/server/rack/http_response_renderer.rb +41 -0
- data/lib/volt/spec/setup.rb +4 -2
- data/lib/volt/tasks/dispatcher.rb +7 -7
- data/lib/volt/tasks/task_handler.rb +1 -1
- data/lib/volt/volt/users.rb +12 -6
- data/spec/apps/kitchen_sink/app/main/config/routes.rb +18 -10
- data/spec/apps/kitchen_sink/app/main/controllers/main_controller.rb +2 -2
- data/spec/apps/kitchen_sink/app/main/controllers/server/simple_http_controller.rb +15 -0
- data/spec/apps/kitchen_sink/app/main/controllers/upload_controller.rb +22 -0
- data/spec/apps/kitchen_sink/app/main/views/main/yield.html +2 -2
- data/spec/apps/kitchen_sink/app/main/views/upload/index.html +15 -0
- data/spec/controllers/http_controller_spec.rb +130 -0
- data/spec/extra_core/string_transformation_test_cases.rb +8 -0
- data/spec/extra_core/string_transformations_spec.rb +12 -0
- data/spec/integration/http_endpoints_spec.rb +29 -0
- data/spec/integration/user_spec.rb +42 -42
- data/spec/models/associations_spec.rb +4 -4
- data/spec/models/buffer_spec.rb +15 -0
- data/spec/models/model_spec.rb +70 -25
- data/spec/models/model_state_spec.rb +1 -1
- data/spec/models/permissions_spec.rb +64 -2
- data/spec/models/persistors/params_spec.rb +8 -8
- data/spec/models/persistors/store_spec.rb +1 -1
- data/spec/models/user_validation_spec.rb +1 -1
- data/spec/router/routes_spec.rb +111 -43
- data/spec/server/rack/http_request_spec.rb +50 -0
- data/spec/server/rack/http_resource_spec.rb +59 -0
- data/spec/server/rack/http_response_header_spec.rb +34 -0
- data/spec/server/rack/http_response_renderer_spec.rb +33 -0
- data/spec/tasks/dispatcher_spec.rb +2 -2
- data/templates/component/config/routes.rb +2 -2
- data/templates/project/Gemfile.tt +3 -5
- data/templates/project/app/main/config/routes.rb +4 -4
- data/volt.gemspec +2 -2
- 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
|
-
|
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
|
-
|
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
|
126
|
-
|
127
|
-
|
128
|
-
|
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
|
data/lib/volt/router/routes.rb
CHANGED
@@ -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
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
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
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
-
|
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(
|
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
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
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
|
-
|
99
|
-
"class #{handler.name} < Volt::
|
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
|