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.
Files changed (136) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -0
  3. data/CHANGELOG.md +11 -0
  4. data/CONTRIBUTING.md +3 -2
  5. data/{Readme.md → README.md} +9 -12
  6. data/Rakefile +2 -9
  7. data/VERSION +1 -1
  8. data/app/volt/models/user.rb +8 -0
  9. data/app/volt/tasks/live_query/data_store.rb +13 -5
  10. data/app/volt/tasks/live_query/live_query.rb +45 -3
  11. data/app/volt/tasks/live_query/live_query_pool.rb +9 -1
  12. data/app/volt/tasks/query_tasks.rb +20 -2
  13. data/app/volt/tasks/store_tasks.rb +37 -20
  14. data/app/volt/tasks/user_tasks.rb +15 -13
  15. data/lib/volt/boot.rb +5 -3
  16. data/lib/volt/cli/console.rb +1 -0
  17. data/lib/volt/cli/generate.rb +15 -0
  18. data/lib/volt/cli.rb +19 -12
  19. data/lib/volt/config.rb +1 -1
  20. data/lib/volt/controllers/model_controller.rb +13 -3
  21. data/lib/volt/extra_core/extra_core.rb +1 -0
  22. data/lib/volt/extra_core/hash.rb +26 -0
  23. data/lib/volt/extra_core/object.rb +5 -1
  24. data/lib/volt/models/array_model.rb +86 -35
  25. data/lib/volt/models/associations.rb +53 -0
  26. data/lib/volt/models/buffer.rb +22 -10
  27. data/lib/volt/models/dirty.rb +88 -0
  28. data/lib/volt/models/errors.rb +21 -0
  29. data/lib/volt/models/field_helpers.rb +2 -2
  30. data/lib/volt/models/listener_tracker.rb +17 -0
  31. data/lib/volt/models/model.rb +213 -69
  32. data/lib/volt/models/model_helpers.rb +27 -17
  33. data/lib/volt/models/permissions.rb +246 -0
  34. data/lib/volt/models/persistors/array_store.rb +149 -81
  35. data/lib/volt/models/persistors/base.rb +16 -0
  36. data/lib/volt/models/persistors/cookies.rb +14 -9
  37. data/lib/volt/models/persistors/flash.rb +3 -0
  38. data/lib/volt/models/persistors/local_store.rb +0 -16
  39. data/lib/volt/models/persistors/model_store.rb +1 -2
  40. data/lib/volt/models/persistors/query/normalizer.rb +51 -0
  41. data/lib/volt/models/persistors/query/query_listener.rb +21 -5
  42. data/lib/volt/models/persistors/query/query_listener_pool.rb +0 -9
  43. data/lib/volt/models/persistors/store.rb +8 -0
  44. data/lib/volt/models/persistors/store_state.rb +4 -27
  45. data/lib/volt/models/state_helpers.rb +11 -0
  46. data/lib/volt/models/state_manager.rb +43 -0
  47. data/lib/volt/models/url.rb +5 -5
  48. data/lib/volt/models/validations.rb +38 -41
  49. data/lib/volt/models/validators/email_validator.rb +4 -9
  50. data/lib/volt/models/validators/format_validator.rb +23 -8
  51. data/lib/volt/models/validators/length_validator.rb +2 -2
  52. data/lib/volt/models/validators/numericality_validator.rb +7 -3
  53. data/lib/volt/models/validators/phone_number_validator.rb +4 -9
  54. data/lib/volt/models/validators/presence_validator.rb +2 -2
  55. data/lib/volt/models/validators/unique_validator.rb +2 -2
  56. data/lib/volt/models/validators/user_validation.rb +6 -0
  57. data/lib/volt/models.rb +8 -3
  58. data/lib/volt/page/bindings/attribute_binding.rb +10 -4
  59. data/lib/volt/page/bindings/content_binding.rb +9 -5
  60. data/lib/volt/page/bindings/if_binding.rb +25 -2
  61. data/lib/volt/page/bindings/template_binding.rb +19 -1
  62. data/lib/volt/page/bindings/yield_binding.rb +31 -0
  63. data/lib/volt/page/page.rb +11 -16
  64. data/lib/volt/reactive/class_eventable.rb +71 -0
  65. data/lib/volt/reactive/computation.rb +79 -10
  66. data/lib/volt/reactive/dependency.rb +27 -8
  67. data/lib/volt/reactive/eventable.rb +36 -22
  68. data/lib/volt/reactive/reactive_array.rb +2 -3
  69. data/lib/volt/reactive/reactive_hash.rb +8 -3
  70. data/lib/volt/router/routes.rb +2 -1
  71. data/lib/volt/server/component_templates.rb +0 -2
  72. data/lib/volt/server/html_parser/component_view_scope.rb +59 -0
  73. data/lib/volt/server/html_parser/view_handler.rb +3 -0
  74. data/lib/volt/server/html_parser/view_parser.rb +1 -0
  75. data/lib/volt/server/html_parser/view_scope.rb +17 -41
  76. data/lib/volt/server/rack/component_paths.rb +1 -10
  77. data/lib/volt/server/rack/index_files.rb +9 -4
  78. data/lib/volt/server/rack/opal_files.rb +22 -14
  79. data/lib/volt/server/rack/quiet_common_logger.rb +1 -1
  80. data/lib/volt/server/socket_connection_handler.rb +4 -0
  81. data/lib/volt/spec/setup.rb +26 -0
  82. data/lib/volt/tasks/dispatcher.rb +11 -0
  83. data/lib/volt/utils/event_counter.rb +29 -0
  84. data/lib/volt/utils/generic_pool.rb +12 -0
  85. data/lib/volt/utils/modes.rb +40 -0
  86. data/lib/volt/utils/promise_patch.rb +66 -0
  87. data/lib/volt/utils/timers.rb +33 -0
  88. data/lib/volt/volt/users.rb +48 -5
  89. data/lib/volt.rb +4 -0
  90. data/spec/apps/kitchen_sink/Gemfile +3 -1
  91. data/spec/apps/kitchen_sink/app/main/config/routes.rb +9 -8
  92. data/spec/apps/kitchen_sink/app/main/controllers/main_controller.rb +9 -0
  93. data/spec/apps/kitchen_sink/app/main/controllers/yield_component_controller.rb +5 -0
  94. data/spec/apps/kitchen_sink/app/main/views/main/cookie_test.html +1 -1
  95. data/spec/apps/kitchen_sink/app/main/views/main/index.html +1 -1
  96. data/spec/apps/kitchen_sink/app/main/views/main/main.html +2 -1
  97. data/spec/apps/kitchen_sink/app/main/views/main/yield.html +18 -0
  98. data/spec/apps/kitchen_sink/app/main/views/yield-component/index.html +4 -0
  99. data/spec/extra_core/logger_spec.rb +4 -2
  100. data/spec/integration/user_spec.rb +42 -42
  101. data/spec/integration/yield_spec.rb +18 -0
  102. data/spec/models/associations_spec.rb +37 -0
  103. data/spec/models/dirty_spec.rb +102 -0
  104. data/spec/models/model_spec.rb +64 -8
  105. data/spec/models/model_state_spec.rb +24 -0
  106. data/spec/models/permissions_spec.rb +96 -0
  107. data/spec/models/user_spec.rb +8 -5
  108. data/spec/models/user_validation_spec.rb +24 -0
  109. data/spec/models/validations_spec.rb +44 -5
  110. data/spec/models/validators/email_validator_spec.rb +109 -82
  111. data/spec/models/validators/format_validator_spec.rb +4 -107
  112. data/spec/models/validators/length_validator_spec.rb +9 -9
  113. data/spec/models/validators/phone_number_validator_spec.rb +60 -103
  114. data/spec/models/validators/shared_examples_for_validators.rb +123 -0
  115. data/spec/reactive/class_eventable_spec.rb +37 -0
  116. data/spec/reactive/computation_spec.rb +68 -3
  117. data/spec/reactive/dependency_spec.rb +71 -0
  118. data/spec/reactive/eventable_spec.rb +21 -0
  119. data/spec/reactive/reactive_hash_spec.rb +12 -0
  120. data/spec/router/routes_spec.rb +50 -50
  121. data/spec/server/html_parser/view_parser_spec.rb +0 -3
  122. data/spec/server/rack/component_paths_spec.rb +11 -0
  123. data/spec/server/rack/quite_common_logger_spec.rb +3 -4
  124. data/spec/spec_helper.rb +7 -3
  125. data/templates/component/config/dependencies.rb +1 -7
  126. data/templates/component/config/routes.rb +1 -1
  127. data/templates/component/controllers/main_controller.rb.tt +20 -0
  128. data/templates/component/views/{index → main}/index.html.tt +0 -0
  129. data/templates/newgem/lib/newgem.rb.tt +1 -3
  130. data/templates/project/app/main/config/routes.rb +3 -3
  131. data/templates/project/app/main/views/main/main.html.tt +4 -4
  132. data/templates/project/config/app.rb.tt +6 -0
  133. data/volt.gemspec +11 -7
  134. metadata +96 -42
  135. data/lib/volt/models/model_state.rb +0 -21
  136. data/templates/component/controllers/main_controller.rb +0 -18
@@ -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[:"_#{k}"] = v
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)
@@ -76,8 +76,6 @@ module Volt
76
76
  code << File.read(model_path) + "\n\n"
77
77
 
78
78
  model_name = model_path.match(/([^\/]+)[.]rb$/)[1]
79
-
80
- code << "#{page_reference}.add_model(#{model_name.inspect})\n\n"
81
79
  end
82
80
 
83
81
  code
@@ -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
@@ -56,6 +56,9 @@ module Volt
56
56
  if @in_textarea && tag_name == 'textarea'
57
57
  last.close_scope
58
58
  @in_textarea = nil
59
+ elsif tag_name[0] == ':'
60
+ # Closing a volt tag
61
+ last.close_scope
59
62
  else
60
63
  last << "</#{tag_name}>"
61
64
  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
- component_name = tag_name[1..-1].tr(':', '/')
127
+ @handler.scope << ComponentViewScope.new(@handler, @path + "/__component#{@binding_number}", tag_name, attributes, unary)
113
128
 
114
- @handler.html << "<!-- $#{@binding_number} --><!-- $/#{@binding_number} -->"
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.map(&:full_gem_path).reject { |g| g !~ /volt/ }.map { |f| f + '/app' }
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
- # Find the route file
14
- home_path = component_paths.component_paths('main').first
15
- route_file = File.read("#{home_path}/config/routes.rb")
16
- eval(route_file)
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
- environment.append_path(app_path)
38
+ server.append_path(app_path)
35
39
 
36
40
  volt_gem_lib_path = File.expand_path(File.join(File.dirname(__FILE__), '../../..'))
37
- environment.append_path(volt_gem_lib_path)
41
+ server.append_path(volt_gem_lib_path)
38
42
 
39
- add_asset_folders(environment)
43
+ add_asset_folders(server)
40
44
 
41
45
  # Add the opal load paths
42
46
  Opal.paths.each do |path|
43
- environment.append_path(path)
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
- environment.append_path(spec.gem_dir + '/opal')
52
+ server.append_path(spec.gem_dir + '/lib')
49
53
 
50
54
  builder.map '/assets' do
51
- run environment
55
+ run server
52
56
  end
53
57
 
54
- if Volt.source_maps?
55
- source_maps = SourceMapServer.new(environment)
56
-
57
- builder.map(source_maps.prefix) do
58
- run source_maps
59
- end
60
- end
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
- if !ext && !path.start_with?('/channel')
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
@@ -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
@@ -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
- if BCrypt::Password.new(hash) != "#{Volt.config.app_secret}::#{user_id}"
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
- user_id = self.user_id
36
- if user_id
37
- $page.store._users.find_one(_id: user_id)
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)