volt 0.8.27.beta3 → 0.8.27.beta4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +2 -0
- data/CHANGELOG.md +11 -0
- data/CONTRIBUTING.md +3 -2
- data/{Readme.md → README.md} +9 -12
- data/Rakefile +2 -9
- data/VERSION +1 -1
- data/app/volt/models/user.rb +8 -0
- data/app/volt/tasks/live_query/data_store.rb +13 -5
- data/app/volt/tasks/live_query/live_query.rb +45 -3
- data/app/volt/tasks/live_query/live_query_pool.rb +9 -1
- data/app/volt/tasks/query_tasks.rb +20 -2
- data/app/volt/tasks/store_tasks.rb +37 -20
- data/app/volt/tasks/user_tasks.rb +15 -13
- data/lib/volt/boot.rb +5 -3
- data/lib/volt/cli/console.rb +1 -0
- data/lib/volt/cli/generate.rb +15 -0
- data/lib/volt/cli.rb +19 -12
- data/lib/volt/config.rb +1 -1
- data/lib/volt/controllers/model_controller.rb +13 -3
- data/lib/volt/extra_core/extra_core.rb +1 -0
- data/lib/volt/extra_core/hash.rb +26 -0
- data/lib/volt/extra_core/object.rb +5 -1
- data/lib/volt/models/array_model.rb +86 -35
- data/lib/volt/models/associations.rb +53 -0
- data/lib/volt/models/buffer.rb +22 -10
- data/lib/volt/models/dirty.rb +88 -0
- data/lib/volt/models/errors.rb +21 -0
- data/lib/volt/models/field_helpers.rb +2 -2
- data/lib/volt/models/listener_tracker.rb +17 -0
- data/lib/volt/models/model.rb +213 -69
- data/lib/volt/models/model_helpers.rb +27 -17
- data/lib/volt/models/permissions.rb +246 -0
- data/lib/volt/models/persistors/array_store.rb +149 -81
- data/lib/volt/models/persistors/base.rb +16 -0
- data/lib/volt/models/persistors/cookies.rb +14 -9
- data/lib/volt/models/persistors/flash.rb +3 -0
- data/lib/volt/models/persistors/local_store.rb +0 -16
- data/lib/volt/models/persistors/model_store.rb +1 -2
- data/lib/volt/models/persistors/query/normalizer.rb +51 -0
- data/lib/volt/models/persistors/query/query_listener.rb +21 -5
- data/lib/volt/models/persistors/query/query_listener_pool.rb +0 -9
- data/lib/volt/models/persistors/store.rb +8 -0
- data/lib/volt/models/persistors/store_state.rb +4 -27
- data/lib/volt/models/state_helpers.rb +11 -0
- data/lib/volt/models/state_manager.rb +43 -0
- data/lib/volt/models/url.rb +5 -5
- data/lib/volt/models/validations.rb +38 -41
- data/lib/volt/models/validators/email_validator.rb +4 -9
- data/lib/volt/models/validators/format_validator.rb +23 -8
- data/lib/volt/models/validators/length_validator.rb +2 -2
- data/lib/volt/models/validators/numericality_validator.rb +7 -3
- data/lib/volt/models/validators/phone_number_validator.rb +4 -9
- data/lib/volt/models/validators/presence_validator.rb +2 -2
- data/lib/volt/models/validators/unique_validator.rb +2 -2
- data/lib/volt/models/validators/user_validation.rb +6 -0
- data/lib/volt/models.rb +8 -3
- data/lib/volt/page/bindings/attribute_binding.rb +10 -4
- data/lib/volt/page/bindings/content_binding.rb +9 -5
- data/lib/volt/page/bindings/if_binding.rb +25 -2
- data/lib/volt/page/bindings/template_binding.rb +19 -1
- data/lib/volt/page/bindings/yield_binding.rb +31 -0
- data/lib/volt/page/page.rb +11 -16
- data/lib/volt/reactive/class_eventable.rb +71 -0
- data/lib/volt/reactive/computation.rb +79 -10
- data/lib/volt/reactive/dependency.rb +27 -8
- data/lib/volt/reactive/eventable.rb +36 -22
- data/lib/volt/reactive/reactive_array.rb +2 -3
- data/lib/volt/reactive/reactive_hash.rb +8 -3
- data/lib/volt/router/routes.rb +2 -1
- data/lib/volt/server/component_templates.rb +0 -2
- data/lib/volt/server/html_parser/component_view_scope.rb +59 -0
- data/lib/volt/server/html_parser/view_handler.rb +3 -0
- data/lib/volt/server/html_parser/view_parser.rb +1 -0
- data/lib/volt/server/html_parser/view_scope.rb +17 -41
- data/lib/volt/server/rack/component_paths.rb +1 -10
- data/lib/volt/server/rack/index_files.rb +9 -4
- data/lib/volt/server/rack/opal_files.rb +22 -14
- data/lib/volt/server/rack/quiet_common_logger.rb +1 -1
- data/lib/volt/server/socket_connection_handler.rb +4 -0
- data/lib/volt/spec/setup.rb +26 -0
- data/lib/volt/tasks/dispatcher.rb +11 -0
- data/lib/volt/utils/event_counter.rb +29 -0
- data/lib/volt/utils/generic_pool.rb +12 -0
- data/lib/volt/utils/modes.rb +40 -0
- data/lib/volt/utils/promise_patch.rb +66 -0
- data/lib/volt/utils/timers.rb +33 -0
- data/lib/volt/volt/users.rb +48 -5
- data/lib/volt.rb +4 -0
- data/spec/apps/kitchen_sink/Gemfile +3 -1
- data/spec/apps/kitchen_sink/app/main/config/routes.rb +9 -8
- data/spec/apps/kitchen_sink/app/main/controllers/main_controller.rb +9 -0
- data/spec/apps/kitchen_sink/app/main/controllers/yield_component_controller.rb +5 -0
- data/spec/apps/kitchen_sink/app/main/views/main/cookie_test.html +1 -1
- data/spec/apps/kitchen_sink/app/main/views/main/index.html +1 -1
- data/spec/apps/kitchen_sink/app/main/views/main/main.html +2 -1
- data/spec/apps/kitchen_sink/app/main/views/main/yield.html +18 -0
- data/spec/apps/kitchen_sink/app/main/views/yield-component/index.html +4 -0
- data/spec/extra_core/logger_spec.rb +4 -2
- data/spec/integration/user_spec.rb +42 -42
- data/spec/integration/yield_spec.rb +18 -0
- data/spec/models/associations_spec.rb +37 -0
- data/spec/models/dirty_spec.rb +102 -0
- data/spec/models/model_spec.rb +64 -8
- data/spec/models/model_state_spec.rb +24 -0
- data/spec/models/permissions_spec.rb +96 -0
- data/spec/models/user_spec.rb +8 -5
- data/spec/models/user_validation_spec.rb +24 -0
- data/spec/models/validations_spec.rb +44 -5
- data/spec/models/validators/email_validator_spec.rb +109 -82
- data/spec/models/validators/format_validator_spec.rb +4 -107
- data/spec/models/validators/length_validator_spec.rb +9 -9
- data/spec/models/validators/phone_number_validator_spec.rb +60 -103
- data/spec/models/validators/shared_examples_for_validators.rb +123 -0
- data/spec/reactive/class_eventable_spec.rb +37 -0
- data/spec/reactive/computation_spec.rb +68 -3
- data/spec/reactive/dependency_spec.rb +71 -0
- data/spec/reactive/eventable_spec.rb +21 -0
- data/spec/reactive/reactive_hash_spec.rb +12 -0
- data/spec/router/routes_spec.rb +50 -50
- data/spec/server/html_parser/view_parser_spec.rb +0 -3
- data/spec/server/rack/component_paths_spec.rb +11 -0
- data/spec/server/rack/quite_common_logger_spec.rb +3 -4
- data/spec/spec_helper.rb +7 -3
- data/templates/component/config/dependencies.rb +1 -7
- data/templates/component/config/routes.rb +1 -1
- data/templates/component/controllers/main_controller.rb.tt +20 -0
- data/templates/component/views/{index → main}/index.html.tt +0 -0
- data/templates/newgem/lib/newgem.rb.tt +1 -3
- data/templates/project/app/main/config/routes.rb +3 -3
- data/templates/project/app/main/views/main/main.html.tt +4 -4
- data/templates/project/config/app.rb.tt +6 -0
- data/volt.gemspec +11 -7
- metadata +96 -42
- data/lib/volt/models/model_state.rb +0 -21
- data/templates/component/controllers/main_controller.rb +0 -18
data/lib/volt/router/routes.rb
CHANGED
@@ -76,7 +76,7 @@ module Volt
|
|
76
76
|
def params_to_url(test_params)
|
77
77
|
# Add in underscores
|
78
78
|
test_params = test_params.each_with_object({}) do |(k, v), obj|
|
79
|
-
obj[
|
79
|
+
obj[k.to_sym] = v
|
80
80
|
end
|
81
81
|
|
82
82
|
@param_matches.each do |param_matcher|
|
@@ -160,6 +160,7 @@ module Volt
|
|
160
160
|
|
161
161
|
parts.each_with_index do |part, index|
|
162
162
|
if has_binding?(part)
|
163
|
+
# Strip off {{ and }}
|
163
164
|
params[part[2...-2].strip.to_sym] = index
|
164
165
|
|
165
166
|
# Set the part to be '*' (anything matcher)
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Volt
|
2
|
+
class ComponentViewScope < ViewScope
|
3
|
+
# The path passed in is the path used to lookup view's. The path from the tag is passed in
|
4
|
+
# as tag_name
|
5
|
+
def initialize(handler, path, tag_name, attributes, unary)
|
6
|
+
super(handler, path)
|
7
|
+
|
8
|
+
@binding_in_path = path
|
9
|
+
|
10
|
+
component_name = tag_name[1..-1].tr(':', '/')
|
11
|
+
|
12
|
+
data_hash = []
|
13
|
+
attributes.each_pair do |name, value|
|
14
|
+
name = name.tr('-', '_')
|
15
|
+
parts, binding_count = binding_parts_and_count(value)
|
16
|
+
|
17
|
+
# if this attribute has bindings
|
18
|
+
if binding_count > 0
|
19
|
+
if binding_count > 1
|
20
|
+
# Multiple bindings
|
21
|
+
elsif parts.size == 1 && binding_count == 1
|
22
|
+
# A single binding
|
23
|
+
getter = value[2...-2].strip
|
24
|
+
data_hash << "#{name.inspect} => Proc.new { #{getter} }"
|
25
|
+
|
26
|
+
setter = getter_to_setter(getter)
|
27
|
+
data_hash << "#{(name + '=').inspect} => Proc.new { |val| #{setter} }"
|
28
|
+
|
29
|
+
# Add an _parent fetcher. Useful for things like volt-fields to get the parent model.
|
30
|
+
parent = parent_fetcher(getter)
|
31
|
+
|
32
|
+
# TODO: This adds some overhead, perhaps there is a way to compute this dynamically on the
|
33
|
+
# front-end.
|
34
|
+
data_hash << "#{(name + '_parent').inspect} => Proc.new { #{parent} }"
|
35
|
+
|
36
|
+
# Add a _last_method property. This is useful
|
37
|
+
data_hash << "#{(name + '_last_method').inspect} => #{last_method_name(getter).inspect}"
|
38
|
+
end
|
39
|
+
else
|
40
|
+
# String
|
41
|
+
data_hash << "#{name.inspect} => #{value.inspect}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
@arguments = "#{component_name.inspect}, { #{data_hash.join(',')} }"
|
46
|
+
end
|
47
|
+
|
48
|
+
def close_scope
|
49
|
+
binding_number = @handler.scope[-2].binding_number
|
50
|
+
@handler.scope[-2].binding_number += 1
|
51
|
+
@path += "/__template/#{binding_number}"
|
52
|
+
|
53
|
+
super
|
54
|
+
|
55
|
+
@handler.html << "<!-- $#{binding_number} --><!-- $/#{binding_number} -->"
|
56
|
+
@handler.scope.last.save_binding(binding_number, "lambda { |__p, __t, __c, __id| Volt::ComponentBinding.new(__p, __t, __c, __id, #{@binding_in_path.inspect}, Proc.new { [#{@arguments}] }, #{@path.inspect}) }")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'volt/server/html_parser/sandlebars_parser'
|
2
2
|
require 'volt/server/html_parser/view_scope'
|
3
3
|
require 'volt/server/html_parser/if_view_scope'
|
4
|
+
require 'volt/server/html_parser/component_view_scope'
|
4
5
|
require 'volt/server/html_parser/view_handler'
|
5
6
|
require 'volt/server/html_parser/each_scope'
|
6
7
|
require 'volt/server/html_parser/textarea_scope'
|
@@ -40,6 +40,8 @@ module Volt
|
|
40
40
|
end
|
41
41
|
when 'template'
|
42
42
|
add_template(args)
|
43
|
+
when 'yield'
|
44
|
+
add_yield(args)
|
43
45
|
else
|
44
46
|
if content =~ /.each\s+do\s+\|/
|
45
47
|
add_each(content, false)
|
@@ -56,6 +58,8 @@ module Volt
|
|
56
58
|
close_scope
|
57
59
|
when 'else'
|
58
60
|
add_else(nil)
|
61
|
+
when 'yield'
|
62
|
+
add_yield
|
59
63
|
else
|
60
64
|
add_content_binding(content)
|
61
65
|
end
|
@@ -92,6 +96,17 @@ module Volt
|
|
92
96
|
@binding_number += 1
|
93
97
|
end
|
94
98
|
|
99
|
+
def add_yield(content=nil)
|
100
|
+
# Strip ( and ) from the outsides
|
101
|
+
content ||= ''
|
102
|
+
content = content.strip.gsub(/^\(/, '').gsub(/\)$/, '')
|
103
|
+
|
104
|
+
@handler.html << "<!-- $#{@binding_number} --><!-- $/#{@binding_number} -->"
|
105
|
+
save_binding(@binding_number, "lambda { |__p, __t, __c, __id| Volt::YieldBinding.new(__p, __t, __c, __id, Proc.new { [#{content}] }) }")
|
106
|
+
|
107
|
+
@binding_number += 1
|
108
|
+
end
|
109
|
+
|
95
110
|
# Returns ruby code to fetch the parent. (by removing the last fetch)
|
96
111
|
# TODO: Probably want to do this with AST transforms with the parser/unparser gems
|
97
112
|
def parent_fetcher(getter)
|
@@ -109,48 +124,9 @@ module Volt
|
|
109
124
|
end
|
110
125
|
|
111
126
|
def add_component(tag_name, attributes, unary)
|
112
|
-
|
127
|
+
@handler.scope << ComponentViewScope.new(@handler, @path + "/__component#{@binding_number}", tag_name, attributes, unary)
|
113
128
|
|
114
|
-
@handler.
|
115
|
-
|
116
|
-
data_hash = []
|
117
|
-
attributes.each_pair do |name, value|
|
118
|
-
name = name.tr('-', '_')
|
119
|
-
parts, binding_count = binding_parts_and_count(value)
|
120
|
-
|
121
|
-
# if this attribute has bindings
|
122
|
-
if binding_count > 0
|
123
|
-
if binding_count > 1
|
124
|
-
# Multiple bindings
|
125
|
-
elsif parts.size == 1 && binding_count == 1
|
126
|
-
# A single binding
|
127
|
-
getter = value[2...-2].strip
|
128
|
-
data_hash << "#{name.inspect} => Proc.new { #{getter} }"
|
129
|
-
|
130
|
-
setter = getter_to_setter(getter)
|
131
|
-
data_hash << "#{(name + '=').inspect} => Proc.new { |val| #{setter} }"
|
132
|
-
|
133
|
-
# Add an _parent fetcher. Useful for things like volt-fields to get the parent model.
|
134
|
-
parent = parent_fetcher(getter)
|
135
|
-
|
136
|
-
# TODO: This adds some overhead, perhaps there is a way to compute this dynamically on the
|
137
|
-
# front-end.
|
138
|
-
data_hash << "#{(name + '_parent').inspect} => Proc.new { #{parent} }"
|
139
|
-
|
140
|
-
# Add a _last_method property. This is useful
|
141
|
-
data_hash << "#{(name + '_last_method').inspect} => #{last_method_name(getter).inspect}"
|
142
|
-
end
|
143
|
-
else
|
144
|
-
# String
|
145
|
-
data_hash << "#{name.inspect} => #{value.inspect}"
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
arguments = "#{component_name.inspect}, { #{data_hash.join(',')} }"
|
150
|
-
|
151
|
-
save_binding(@binding_number, "lambda { |__p, __t, __c, __id| Volt::ComponentBinding.new(__p, __t, __c, __id, #{@path.inspect}, Proc.new { [#{arguments}] }) }")
|
152
|
-
|
153
|
-
@binding_number += 1
|
129
|
+
@handler.last.close_scope if unary
|
154
130
|
end
|
155
131
|
|
156
132
|
def add_textarea(tag_name, attributes, unary)
|
@@ -16,7 +16,7 @@ module Volt
|
|
16
16
|
|
17
17
|
# Gem folders with volt in them
|
18
18
|
# TODO: we should probably qualify this a bit more
|
19
|
-
app_folders += Gem.loaded_specs.values.
|
19
|
+
app_folders += Gem.loaded_specs.values.reduce([]) { |paths, gem| paths << "#{gem.full_gem_path}/app" if gem.name =~ /volt/; paths }
|
20
20
|
|
21
21
|
app_folders.uniq
|
22
22
|
end
|
@@ -66,15 +66,6 @@ module Volt
|
|
66
66
|
path = ruby_file.gsub(/^#{app_folder}\//, '')[0..-4]
|
67
67
|
require(path)
|
68
68
|
end
|
69
|
-
|
70
|
-
if Volt.server?
|
71
|
-
# Add models to page
|
72
|
-
Dir["#{app_folder}/*/models/*.rb"].each do |ruby_file|
|
73
|
-
class_name = File.basename(ruby_file).gsub(/[.]rb$/, '')
|
74
|
-
|
75
|
-
$page.add_model(class_name)
|
76
|
-
end
|
77
|
-
end
|
78
69
|
end
|
79
70
|
|
80
71
|
load_views_and_routes
|
@@ -10,10 +10,15 @@ module Volt
|
|
10
10
|
@opal_files = opal_files
|
11
11
|
|
12
12
|
@@router ||= Routes.new.define do
|
13
|
-
#
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
# Load routes for each component
|
14
|
+
component_paths.components.values.flatten.uniq.each do |component_path|
|
15
|
+
routes_path = "#{component_path}/config/routes.rb"
|
16
|
+
|
17
|
+
if File.exists?(routes_path)
|
18
|
+
route_file = File.read(routes_path)
|
19
|
+
instance_eval(route_file, routes_path, 0)
|
20
|
+
end
|
21
|
+
end
|
17
22
|
end
|
18
23
|
end
|
19
24
|
|
@@ -11,13 +11,17 @@ module Volt
|
|
11
11
|
|
12
12
|
def initialize(builder, app_path, component_paths)
|
13
13
|
Opal::Processor.source_map_enabled = Volt.source_maps?
|
14
|
+
Opal::Processor.const_missing_enabled = true
|
14
15
|
|
15
16
|
# Don't run arity checks in production
|
16
17
|
# Opal::Processor.arity_check_enabled = !Volt.env.production?
|
17
18
|
# Opal::Processor.dynamic_require_severity = :raise
|
18
19
|
|
20
|
+
server = Opal::Server.new(prefix: '/')
|
21
|
+
|
19
22
|
@component_paths = component_paths
|
20
|
-
@environment = Opal::Environment.new
|
23
|
+
# @environment = Opal::Environment.new
|
24
|
+
@environment = server.sprockets
|
21
25
|
|
22
26
|
# Since the scope changes in builder blocks, we need to capture
|
23
27
|
# environment in closure
|
@@ -31,33 +35,37 @@ module Volt
|
|
31
35
|
environment.css_compressor = Sprockets::YUICompressor
|
32
36
|
end
|
33
37
|
|
34
|
-
|
38
|
+
server.append_path(app_path)
|
35
39
|
|
36
40
|
volt_gem_lib_path = File.expand_path(File.join(File.dirname(__FILE__), '../../..'))
|
37
|
-
|
41
|
+
server.append_path(volt_gem_lib_path)
|
38
42
|
|
39
|
-
add_asset_folders(
|
43
|
+
add_asset_folders(server)
|
40
44
|
|
41
45
|
# Add the opal load paths
|
42
46
|
Opal.paths.each do |path|
|
43
|
-
|
47
|
+
server.append_path(path)
|
44
48
|
end
|
45
49
|
|
46
50
|
# opal-jquery gem
|
47
51
|
spec = Gem::Specification.find_by_name('opal-jquery')
|
48
|
-
|
52
|
+
server.append_path(spec.gem_dir + '/lib')
|
49
53
|
|
50
54
|
builder.map '/assets' do
|
51
|
-
run
|
55
|
+
run server
|
52
56
|
end
|
53
57
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
58
|
+
# map server.source_maps.prefix do
|
59
|
+
# run server.source_maps
|
60
|
+
# end
|
61
|
+
|
62
|
+
# if Volt.source_maps?
|
63
|
+
# source_maps = SourceMapServer.new(environment)
|
64
|
+
#
|
65
|
+
# builder.map(source_maps.prefix) do
|
66
|
+
# run source_maps
|
67
|
+
# end
|
68
|
+
# end
|
61
69
|
end
|
62
70
|
|
63
71
|
def add_asset_folders(environment)
|
@@ -25,7 +25,7 @@ class QuietCommonLogger < Rack::CommonLogger
|
|
25
25
|
|
26
26
|
# Because of web sockets, the initial request doesn't finish, so we
|
27
27
|
# can just trigger it now.
|
28
|
-
|
28
|
+
unless ext || path.start_with?('/channel')
|
29
29
|
log(env, status, header, began_at)
|
30
30
|
end
|
31
31
|
|
@@ -6,6 +6,10 @@ module Volt
|
|
6
6
|
class SocketConnectionHandler < SockJS::Session
|
7
7
|
# Create one instance of the dispatcher
|
8
8
|
|
9
|
+
# We track the connected user_id with the channel for use with permissions.
|
10
|
+
# This may be changed as new listeners connect, which is fine.
|
11
|
+
attr_accessor :user_id
|
12
|
+
|
9
13
|
def self.dispatcher=(val)
|
10
14
|
@@dispatcher = val
|
11
15
|
end
|
data/lib/volt/spec/setup.rb
CHANGED
@@ -16,6 +16,32 @@ module Volt
|
|
16
16
|
|
17
17
|
setup_capybara(app_path)
|
18
18
|
end
|
19
|
+
|
20
|
+
|
21
|
+
# Setup the spec collection accessors
|
22
|
+
# RSpec.shared_context "volt collections", {} do
|
23
|
+
RSpec.shared_examples_for 'volt collections', {} do
|
24
|
+
# Page conflicts with capybara's page method
|
25
|
+
# let(:page) { Model.new }
|
26
|
+
let(:store) do
|
27
|
+
@__store_accessed = true
|
28
|
+
$page ||= Page.new
|
29
|
+
$page.store
|
30
|
+
end
|
31
|
+
|
32
|
+
after do
|
33
|
+
if @__store_accessed
|
34
|
+
# Clear the database after each spec where we use store
|
35
|
+
# @@db ||= Volt::DataStore.fetch
|
36
|
+
# puts "DB CLASS: #{@@db.inspect}"
|
37
|
+
# @@db.drop_database
|
38
|
+
::DataStore.new.drop_database
|
39
|
+
|
40
|
+
$page.instance_variable_set('@store', nil)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
19
45
|
end
|
20
46
|
end
|
21
47
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# require 'ruby-prof'
|
2
|
+
|
1
3
|
module Volt
|
2
4
|
# The task dispatcher is responsible for taking incoming messages
|
3
5
|
# from the socket channel and dispatching them to the proper handler.
|
@@ -25,8 +27,17 @@ module Volt
|
|
25
27
|
promise = promise.then do
|
26
28
|
Thread.current['meta'] = meta_data
|
27
29
|
|
30
|
+
# # Profile the code
|
31
|
+
# RubyProf.start
|
32
|
+
|
28
33
|
result = klass.new(channel, self).send(method_name, *args)
|
29
34
|
|
35
|
+
# res = RubyProf.stop
|
36
|
+
#
|
37
|
+
# # Print a flat profile to text
|
38
|
+
# printer = RubyProf::FlatPrinter.new(res)
|
39
|
+
# printer.print(STDOUT)
|
40
|
+
|
30
41
|
Thread.current['meta'] = nil
|
31
42
|
|
32
43
|
result
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Volt
|
2
|
+
# EventCounter has an #add and #remove method, and when the first one is added
|
3
|
+
# will call the #start proc (passed to new), and when the last is removed will
|
4
|
+
# call #stop.
|
5
|
+
class EventCounter
|
6
|
+
attr_reader :count
|
7
|
+
|
8
|
+
def initialize(start, stop)
|
9
|
+
@start = start
|
10
|
+
@stop = stop
|
11
|
+
|
12
|
+
@count = 0
|
13
|
+
end
|
14
|
+
|
15
|
+
def add
|
16
|
+
@count += 1
|
17
|
+
|
18
|
+
@start.call if @count == 1
|
19
|
+
end
|
20
|
+
|
21
|
+
def remove
|
22
|
+
@count -= 1
|
23
|
+
|
24
|
+
raise "count below 0" if @count < 0
|
25
|
+
|
26
|
+
@stop.call if @count == 0
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -96,5 +96,17 @@ module Volt
|
|
96
96
|
end
|
97
97
|
end
|
98
98
|
end
|
99
|
+
|
100
|
+
def print
|
101
|
+
puts '--- Running Queries ---'
|
102
|
+
|
103
|
+
@pool.each_pair do |table, query_hash|
|
104
|
+
query_hash.each_key do |query|
|
105
|
+
puts "#{table.inspect}: #{query.inspect}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
puts '---------------------'
|
110
|
+
end
|
99
111
|
end
|
100
112
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
if RUBY_PLATFORM == 'opal'
|
4
|
+
# Stub thread class
|
5
|
+
class Thread
|
6
|
+
def self.current
|
7
|
+
@current ||= {}
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module Volt
|
13
|
+
# Modes provide a way to effect the state inside of a block that
|
14
|
+
# can be checked from elsewhere. This is very useful if you have
|
15
|
+
# some flag you may want to change without needing to pass all
|
16
|
+
# of the way through some other code.
|
17
|
+
module Modes
|
18
|
+
module ClassMethods
|
19
|
+
# Takes a block that when run, changes to mode inside of it
|
20
|
+
def run_in_mode(mode_name)
|
21
|
+
previous = Thread.current[mode_name]
|
22
|
+
Thread.current[mode_name] = true
|
23
|
+
begin
|
24
|
+
yield
|
25
|
+
ensure
|
26
|
+
Thread.current[mode_name] = previous
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Check to see if we are in the specified mode
|
31
|
+
def in_mode?(mode_name)
|
32
|
+
return defined?(Thread) && Thread.current[mode_name]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.included(base)
|
37
|
+
base.send :extend, ClassMethods
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# A temp patch for promises until https://github.com/opal/opal/pull/725 is released.
|
2
|
+
class Promise
|
3
|
+
def initialize(success = nil, failure = nil)
|
4
|
+
@success = success
|
5
|
+
@failure = failure
|
6
|
+
|
7
|
+
@realized = nil
|
8
|
+
@exception = false
|
9
|
+
@value = nil
|
10
|
+
@error = nil
|
11
|
+
@delayed = false
|
12
|
+
|
13
|
+
@prev = nil
|
14
|
+
@next = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def >>(promise)
|
19
|
+
@next = promise
|
20
|
+
|
21
|
+
if exception?
|
22
|
+
promise.reject(@delayed[0])
|
23
|
+
elsif resolved?
|
24
|
+
promise.resolve(@delayed ? @delayed[0] : value)
|
25
|
+
elsif rejected? && (!@failure || Promise === (@delayed ? @delayed[0] : @error))
|
26
|
+
promise.reject(@delayed ? @delayed[0] : error)
|
27
|
+
end
|
28
|
+
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def resolve!(value)
|
33
|
+
if @next
|
34
|
+
@next.resolve(value)
|
35
|
+
else
|
36
|
+
@delayed = [value]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def reject!(value)
|
41
|
+
if @next
|
42
|
+
@next.reject(value)
|
43
|
+
else
|
44
|
+
@delayed = [value]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Waits for the promise to resolve (assuming it is blocking on
|
49
|
+
# the server) and returns the result.
|
50
|
+
def sync
|
51
|
+
result = nil
|
52
|
+
error = nil
|
53
|
+
|
54
|
+
self.then do |val|
|
55
|
+
result = val
|
56
|
+
end.fail do |err|
|
57
|
+
error = err
|
58
|
+
end
|
59
|
+
|
60
|
+
if error
|
61
|
+
raise error
|
62
|
+
else
|
63
|
+
return result
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Volt
|
2
|
+
|
3
|
+
# The timers class provides useful methods for working in an asynchronus environment.
|
4
|
+
class Timers
|
5
|
+
# next tick (same as setImmediate) calls the block of code after any currently
|
6
|
+
# running code is finished.
|
7
|
+
def self.next_tick(&block)
|
8
|
+
if Volt.in_browser?
|
9
|
+
`setImmediate(function() {`
|
10
|
+
yield
|
11
|
+
`})`
|
12
|
+
else
|
13
|
+
tick_timers = (Thread.current['tick_timers'] ||= [])
|
14
|
+
tick_timers << block
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# On the server, we need to manually flush next tick timers.
|
19
|
+
# This is done automatically in the console after each enter.
|
20
|
+
def self.flush_next_tick_timers!
|
21
|
+
tick_timers = Thread.current['tick_timers']
|
22
|
+
|
23
|
+
if tick_timers
|
24
|
+
# clear
|
25
|
+
Thread.current['tick_timers'] = nil
|
26
|
+
tick_timers.each do |timer|
|
27
|
+
# Run the timer
|
28
|
+
timer.call
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/volt/volt/users.rb
CHANGED
@@ -1,7 +1,14 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
1
3
|
module Volt
|
2
4
|
class << self
|
3
5
|
# Get the user_id from the cookie
|
4
6
|
def user_id
|
7
|
+
# Check for a user_id from with_user
|
8
|
+
if (user_id = Thread.current['with_user_id'])
|
9
|
+
return user_id
|
10
|
+
end
|
11
|
+
|
5
12
|
user_id_signature = self.user_id_signature
|
6
13
|
|
7
14
|
if user_id_signature.nil?
|
@@ -14,7 +21,8 @@ module Volt
|
|
14
21
|
hash = user_id_signature[(index + 1)..-1]
|
15
22
|
|
16
23
|
# Make sure the user hash matches
|
17
|
-
|
24
|
+
# TODO: We could cache the digest generation for even faster comparisons
|
25
|
+
if hash != Digest::SHA256.hexdigest("#{Volt.config.app_secret}::#{user_id}")
|
18
26
|
# user id has been tampered with, reject
|
19
27
|
fail 'user id or hash has been tampered with'
|
20
28
|
end
|
@@ -25,6 +33,24 @@ module Volt
|
|
25
33
|
end
|
26
34
|
end
|
27
35
|
|
36
|
+
# as_user lets you run a block as another user
|
37
|
+
#
|
38
|
+
# @param user_id [Integer]
|
39
|
+
def as_user(user_id)
|
40
|
+
previous_id = Thread.current['with_user_id']
|
41
|
+
Thread.current['with_user_id'] = user_id
|
42
|
+
|
43
|
+
yield
|
44
|
+
|
45
|
+
Thread.current['with_user_id'] = previous_id
|
46
|
+
end
|
47
|
+
|
48
|
+
def skip_permissions
|
49
|
+
Volt.run_in_mode(:skip_permissions) do
|
50
|
+
yield
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
28
54
|
# True if the user is logged in and the user is loaded
|
29
55
|
def user?
|
30
56
|
!!user
|
@@ -32,11 +58,17 @@ module Volt
|
|
32
58
|
|
33
59
|
# Return the current user.
|
34
60
|
def user
|
35
|
-
|
36
|
-
|
37
|
-
|
61
|
+
# Run first on the query, or return nil
|
62
|
+
user_query.try(:first)
|
63
|
+
end
|
64
|
+
|
65
|
+
def fetch_user
|
66
|
+
u_query = user_query
|
67
|
+
if u_query
|
68
|
+
u_query.fetch_first
|
38
69
|
else
|
39
|
-
nil
|
70
|
+
# No user, resolve nil
|
71
|
+
Promise.new.resolve(nil)
|
40
72
|
end
|
41
73
|
end
|
42
74
|
|
@@ -72,5 +104,16 @@ module Volt
|
|
72
104
|
|
73
105
|
user_id_signature
|
74
106
|
end
|
107
|
+
|
108
|
+
private
|
109
|
+
# Returns a query for the current user_id or nil if there is no user_id
|
110
|
+
def user_query
|
111
|
+
user_id = self.user_id
|
112
|
+
if user_id
|
113
|
+
$page.store._users.where(_id: user_id)
|
114
|
+
else
|
115
|
+
nil
|
116
|
+
end
|
117
|
+
end
|
75
118
|
end
|
76
119
|
end
|
data/lib/volt.rb
CHANGED
@@ -2,6 +2,8 @@ require 'volt/volt/environment'
|
|
2
2
|
require 'volt/extra_core/extra_core'
|
3
3
|
require 'volt/reactive/computation'
|
4
4
|
require 'volt/reactive/dependency'
|
5
|
+
require 'volt/utils/modes'
|
6
|
+
|
5
7
|
require 'volt/config'
|
6
8
|
unless RUBY_PLATFORM == 'opal'
|
7
9
|
require 'volt/data_stores/data_store'
|
@@ -15,6 +17,8 @@ module Volt
|
|
15
17
|
false
|
16
18
|
end
|
17
19
|
|
20
|
+
include Modes
|
21
|
+
|
18
22
|
class << self
|
19
23
|
def root
|
20
24
|
@root ||= File.expand_path(Dir.pwd)
|