volt 0.9.5 → 0.9.6.pre1

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 +10 -0
  3. data/app/volt/tasks/query_tasks.rb +1 -1
  4. data/app/volt/tasks/user_tasks.rb +6 -0
  5. data/docs/UPGRADE_GUIDE.md +19 -0
  6. data/lib/volt.rb +0 -1
  7. data/lib/volt/cli.rb +3 -0
  8. data/lib/volt/cli/destroy.rb +8 -0
  9. data/lib/volt/cli/generate.rb +1 -105
  10. data/lib/volt/cli/generators.rb +111 -0
  11. data/lib/volt/controllers/model_controller.rb +1 -1
  12. data/lib/volt/helpers/time.rb +5 -5
  13. data/lib/volt/models/array_model.rb +1 -1
  14. data/lib/volt/models/helpers/base.rb +28 -12
  15. data/lib/volt/models/persistors/page.rb +6 -6
  16. data/lib/volt/models/persistors/query/query_listener.rb +1 -1
  17. data/lib/volt/page/bindings/each_binding.rb +1 -1
  18. data/lib/volt/page/channel.rb +18 -7
  19. data/lib/volt/page/tasks.rb +10 -4
  20. data/lib/volt/reactive/computation.rb +20 -8
  21. data/lib/volt/reactive/dependency.rb +3 -1
  22. data/lib/volt/server/component_templates.rb +4 -3
  23. data/lib/volt/server/middleware/default_middleware_stack.rb +6 -6
  24. data/lib/volt/server/rack/index_files.rb +0 -12
  25. data/lib/volt/server/rack/opal_files.rb +1 -1
  26. data/lib/volt/server/socket_connection_handler.rb +40 -1
  27. data/lib/volt/server/template_handlers/sprockets_component_handler.rb +5 -2
  28. data/lib/volt/server/template_handlers/view_processor.rb +4 -4
  29. data/lib/volt/tasks/task.rb +2 -1
  30. data/lib/volt/utils/csso_patch.rb +1 -1
  31. data/lib/volt/version.rb +1 -1
  32. data/lib/volt/volt/app.rb +9 -0
  33. data/lib/volt/volt/server_setup/app.rb +19 -0
  34. data/lib/volt/volt/users.rb +4 -0
  35. data/spec/apps/kitchen_sink/app/main/config/routes.rb +1 -0
  36. data/spec/apps/kitchen_sink/app/main/controllers/main_controller.rb +3 -0
  37. data/spec/apps/kitchen_sink/app/main/models/user.rb +18 -0
  38. data/spec/apps/kitchen_sink/app/main/views/main/callbacks.html +7 -0
  39. data/spec/integration/bindings_spec.rb +1 -1
  40. data/spec/integration/callbacks_spec.rb +31 -0
  41. data/spec/integration/todos_spec.rb +2 -2
  42. data/spec/models/array_model_spec.rb +13 -0
  43. data/spec/models/associations_spec.rb +1 -1
  44. data/spec/models/field_helpers_spec.rb +1 -1
  45. data/spec/models/model_spec.rb +18 -0
  46. data/spec/models/permissions_spec.rb +1 -2
  47. data/spec/models/persistors/page_spec.rb +19 -0
  48. data/spec/reactive/computation_spec.rb +33 -0
  49. data/spec/server/socket_connection_handler_spec.rb +99 -0
  50. data/spec/tasks/dispatcher_spec.rb +1 -1
  51. data/spec/tasks/user_tasks_spec.rb +1 -1
  52. data/spec/utils/task_argument_filtererer_spec.rb +1 -1
  53. data/templates/project/Gemfile.tt +1 -1
  54. data/templates/project/README.md.tt +1 -1
  55. data/templates/project/config/app.rb.tt +10 -0
  56. data/templates/view/index.html.tt +1 -1
  57. metadata +14 -5
  58. data/lib/volt/utils/set_patch.rb +0 -25
@@ -47,7 +47,7 @@ module Volt
47
47
  Volt.logger.error(msg)
48
48
 
49
49
  # If we get back that the user signature is wrong, log the user out.
50
- if err.starts_with?('VoltUserError:')
50
+ if err.start_with?('VoltUserError:')
51
51
  # Delete the invalid cookie
52
52
  Volt.current_app.cookies.delete(:user_id)
53
53
  end
@@ -104,7 +104,7 @@ module Volt
104
104
  dom_section.insert_anchor_before(binding_name, @templates[position].binding_name)
105
105
  end
106
106
 
107
- # TODORW: :parent => @value may change
107
+ # TODORW: parent: @value may change
108
108
  item_context = SubContext.new({ _index_value: position, parent: @value }, @context)
109
109
  item_context.locals[@item_name.to_sym] = proc do
110
110
  # Fetch only whats there currently, no promises.
@@ -27,14 +27,25 @@ module Volt
27
27
  end
28
28
 
29
29
  def connect!
30
- `
31
- if (document.location.protocol == 'https:') {
32
- var wsProto = 'wss';
33
- } else {
34
- var wsProto = 'ws';
35
- }
30
+ # The websocket url can be overridden by config.public.websocket_url
31
+ socket_url = Volt.config.try(:public).try(:websocket_url) || begin
32
+ "#{`document.location.host`}/socket"
33
+ end
34
+
35
+ if socket_url !~ /^wss?[:]\/\//
36
+ if socket_url !~ /^[:]\/\//
37
+ # Add :// to the front
38
+ socket_url = "://#{socket_url}"
39
+ end
40
+
41
+ ws_proto = (`document.location.protocol` == 'https://') ? 'wss' : 'ws'
36
42
 
37
- this.socket = new WebSocket(wsProto + '://' + document.location.host + '/socket');
43
+ # Add wss? to the front
44
+ socket_url = "#{ws_proto}#{socket_url}"
45
+ end
46
+
47
+ `
48
+ this.socket = new WebSocket(socket_url);
38
49
 
39
50
  this.socket.onopen = function () {
40
51
  self.$opened();
@@ -73,11 +73,17 @@ module Volt
73
73
 
74
74
  def reload
75
75
  # Stash the current page value
76
- value = EJSON.stringify(Volt.current_app.page.to_h)
76
+ begin
77
+ value = EJSON.stringify(Volt.current_app.page.to_h)
77
78
 
78
- # If this browser supports session storage, store the page, so it will
79
- # be in the same state when we reload.
80
- `sessionStorage.setItem('___page', value);` if `sessionStorage`
79
+ # If this browser supports session storage, store the page, so it will
80
+ # be in the same state when we reload.
81
+ `sessionStorage.setItem('___page', value);` if `sessionStorage`
82
+ rescue EJSON::NonEjsonType => e
83
+ # Unable to serailize the page, ignore stashing it
84
+ # clear the ___page stash
85
+ `sessionStorage.removeItem('___page');`
86
+ end
81
87
 
82
88
  Volt.current_app.page._reloading = true
83
89
  `window.location.reload(false);`
@@ -21,21 +21,33 @@ module Volt
21
21
 
22
22
  # Runs the computation, called on initial run and
23
23
  # when changed!
24
- def compute!
24
+ def compute!(initial_run=false)
25
25
  @invalidated = false
26
26
 
27
27
  unless @stopped
28
28
 
29
29
  @computing = true
30
- run_in do
31
- if @computation.arity > 0
32
- # Pass in the Computation so it can be canceled from within
33
- @computation.call(self)
30
+ begin
31
+ run_in do
32
+ if @computation.arity > 0
33
+ # Pass in the Computation so it can be canceled from within
34
+ @computation.call(self)
35
+ else
36
+ @computation.call
37
+ end
38
+ end
39
+ rescue => e
40
+ if initial_run
41
+ # Re-raise if we are in the initial run
42
+ raise
34
43
  else
35
- @computation.call
44
+ msg = "Exception During Compute: " + e.inspect
45
+ msg += "\n" + e.backtrace.join("\n") if e.respond_to?(:backtrace)
46
+ Volt.logger.error(msg)
36
47
  end
48
+ ensure
49
+ @computing = false
37
50
  end
38
- @computing = false
39
51
  end
40
52
  end
41
53
 
@@ -140,7 +152,7 @@ class Proc
140
152
  computation = Volt::Computation.new(self)
141
153
 
142
154
  # Initial run
143
- computation.compute!
155
+ computation.compute!(true)
144
156
 
145
157
  # return the computation
146
158
  computation
@@ -34,7 +34,7 @@ module Volt
34
34
  # If @dependencies is nil, this Dependency has been removed
35
35
  if @dependencies
36
36
  # For set, .delete returns a boolean if it was deleted
37
- deleted = @dependencies.delete(current)
37
+ deleted = @dependencies.delete?(current)
38
38
 
39
39
  # Call on stop dep if no more deps
40
40
  @on_stop_dep.call if @on_stop_dep && deleted && @dependencies.size == 0
@@ -54,6 +54,8 @@ module Volt
54
54
 
55
55
  deps.each(&:invalidate!)
56
56
 
57
+ # Call on stop dep here because we are clearing out the @dependencies, so
58
+ # it won't trigger on the invalidates
57
59
  @on_stop_dep.call if @on_stop_dep
58
60
  end
59
61
 
@@ -66,15 +66,16 @@ module Volt
66
66
  # handle things.
67
67
  code << "\nrequire '#{require_path}'\n"
68
68
  else
69
+ valid_exts_re = exts.join('|')
69
70
  # On the sever side, we eval the compiled code
70
- path_parts = view_path.scan(/([^\/]+)\/([^\/]+)\/[^\/]+\/([^\/]+)[.](html|email)$/)
71
+ path_parts = view_path.scan(/([^\/]+)\/([^\/]+)\/[^\/]+\/([^\/]+)[.](#{valid_exts_re})$/)
71
72
  component_name, controller_name, view, _ = path_parts[0]
72
73
 
73
74
  # file extension
74
75
  format = File.extname(view_path).downcase.delete('.').to_sym
75
76
 
76
77
  # Get the path for the template, supports templates in folders
77
- template_path = view_path[views_path.size..-1].gsub(/[.](#{exts.join('|')})$/, '')
78
+ template_path = view_path[views_path.size..-1].gsub(/[.](#{valid_exts_re})$/, '')
78
79
  template_path = "#{@component_name}/#{template_path}"
79
80
 
80
81
  html = File.read(view_path)
@@ -189,4 +190,4 @@ module Volt
189
190
 
190
191
 
191
192
  end
192
- end
193
+ end
@@ -26,11 +26,11 @@ module Volt
26
26
  rack_app.use Rack::ETag
27
27
 
28
28
  rack_app.use Rack::Session::Cookie, {
29
- :key => 'rack.session',
30
- # :domain => 'localhost.com',
31
- :path => '/',
32
- :expire_after => 2592000,
33
- :secret => Volt.config.app_secret
29
+ key: 'rack.session',
30
+ # domain: 'localhost.com',
31
+ path: '/',
32
+ expire_after: 2592000,
33
+ secret: Volt.config.app_secret
34
34
  }
35
35
 
36
36
  rack_app.use QuietCommonLogger
@@ -57,7 +57,7 @@ module Volt
57
57
 
58
58
  # serve assets from public
59
59
  rack_app.use Rack::Static,
60
- urls: ['/'],
60
+ urls: [''],
61
61
  root: 'public',
62
62
  index: 'index.html',
63
63
  header_rules: [
@@ -11,18 +11,6 @@ module Volt
11
11
  @opal_files = opal_files
12
12
 
13
13
  @@router = volt_app.router
14
-
15
- @@router.define do
16
- # Load routes for each component
17
- component_paths.components.values.flatten.uniq.each do |component_path|
18
- routes_path = "#{component_path}/config/routes.rb"
19
-
20
- if File.exist?(routes_path)
21
- route_file = File.read(routes_path)
22
- instance_eval(route_file, routes_path, 0)
23
- end
24
- end
25
- end
26
14
  end
27
15
 
28
16
  def route_match?(path)
@@ -110,7 +110,7 @@ module Volt
110
110
  def add_image_compression
111
111
  if defined?(ImageOptim)
112
112
  env = @environment
113
- image_optim = ImageOptim.new({:pngout => false, :svgo => false})
113
+ image_optim = ImageOptim.new({pngout: false, svgo: false})
114
114
 
115
115
  processor = proc do |_context, data|
116
116
  image_optim.optimize_image_data(data) || data
@@ -15,6 +15,29 @@ module Volt
15
15
 
16
16
  @@channels ||= []
17
17
  @@channels << self
18
+
19
+ # Trigger a client connect event
20
+ @@dispatcher.volt_app.trigger!("client_connect")
21
+
22
+ end
23
+
24
+ def update_user_id(user_id)
25
+ if !@user_id && user_id
26
+ # If there is currently no user id associated with this channel
27
+ # and we get a new valid user_id, set it then trigger a
28
+ # user_connect event
29
+ @user_id = user_id
30
+ @@dispatcher.volt_app.trigger!("user_connect", @user_id)
31
+ elsif @user_id && !user_id
32
+ # If there is currently a user id associated with this channel
33
+ # and we get a nil user id, trigger a user_disconnect event then
34
+ # set the id to nil
35
+ @@dispatcher.volt_app.trigger!("user_disconnect", @user_id)
36
+ @user_id = user_id
37
+ else
38
+ # Otherwise, lets just set the id (should never really run)
39
+ @user_id = user_id
40
+ end
18
41
  end
19
42
 
20
43
  def self.dispatcher=(val)
@@ -22,7 +45,11 @@ module Volt
22
45
  end
23
46
 
24
47
  def self.dispatcher
25
- @@dispatcher
48
+ defined?(@@dispatcher) ? @@dispatcher : nil
49
+ end
50
+
51
+ def self.channels
52
+ @@channels
26
53
  end
27
54
 
28
55
  # Sends a message to all, optionally skipping a users channel
@@ -85,6 +112,18 @@ module Volt
85
112
 
86
113
  begin
87
114
  @@dispatcher.close_channel(self)
115
+
116
+ # Check for volt_app (@@dispatcher could be an ErrorDispatcher)
117
+ if @@dispatcher.respond_to?(:volt_app)
118
+ # Trigger a client disconnect event
119
+ @@dispatcher.volt_app.trigger!("client_disconnect")
120
+
121
+ # Trigger a user disconnect event even if the user hasn't logged out
122
+ if @user_id
123
+ @@dispatcher.volt_app.trigger!("user_disconnect", @user_id)
124
+ end
125
+ end
126
+
88
127
  rescue DRb::DRbConnError => e
89
128
  # ignore drb read of @@dispatcher error if child has closed
90
129
  end
@@ -54,8 +54,11 @@ module Sprockets
54
54
  data = env.read_file(input[:filename], input[:content_type])
55
55
  end
56
56
 
57
- dependencies = Set.new(input[:metadata][:dependencies])
58
- dependencies += [env.build_file_digest_uri(input[:filename])]
57
+ # dependencies = Set.new(input[:metadata][:dependencies])
58
+ # dependencies += [env.build_file_digest_uri(input[:filename])]
59
+
60
+ dependencies = input[:metadata][:dependencies]
61
+ # dependencies.merge(env.build_file_digest_uri(input[:filename]))
59
62
 
60
63
  { data: data, dependencies: dependencies }
61
64
  end
@@ -23,7 +23,7 @@ module Volt
23
23
  end
24
24
 
25
25
  def cache_key
26
- @cache_key ||= "#{self.class.name}:0.1".freeze
26
+ @cache_key ||= "#{self.class.name}:0.2".freeze
27
27
  end
28
28
 
29
29
  # def evaluate(context, locals, &block)
@@ -65,8 +65,6 @@ module Volt
65
65
  exts = ComponentTemplates::Preprocessors.extensions
66
66
  template_path = view_path.split('/')[-4..-1].join('/').gsub('/views/', '/').gsub(/[.](#{exts.join('|')})$/, '')
67
67
 
68
- exts = ComponentTemplates::Preprocessors.extensions
69
-
70
68
  format = File.extname(view_path).downcase.delete('.').to_sym
71
69
  code = ''
72
70
 
@@ -82,7 +80,9 @@ module Volt
82
80
  end
83
81
 
84
82
  def self.setup(sprockets=$volt_app.sprockets)
85
- sprockets.register_mime_type 'application/vtemplate', extensions: ['.html', '.email']
83
+ exts = ComponentTemplates::Preprocessors.extensions.map{ |ext| ".#{ext}" }
84
+
85
+ sprockets.register_mime_type 'application/vtemplate', extensions: exts
86
86
  sprockets.register_transformer 'application/vtemplate', 'application/javascript', Volt::ViewProcessor.new(true)
87
87
  end
88
88
  end
@@ -39,7 +39,8 @@ module Volt
39
39
 
40
40
  # Set the timeout for method calls on this task. (The default is
41
41
  # Volt.config.worker_timeout)
42
- def timeout(value)
42
+ # Set 0 to disable timeout
43
+ def self.timeout(value)
43
44
  self.__timeout = value
44
45
  end
45
46
 
@@ -31,7 +31,7 @@ module Csso
31
31
  })
32
32
  sprockets.css_compressor = :csso
33
33
  else
34
- Sprockets::Compressors.register_css_compressor(:csso, 'Csso::Compressor', :default => true)
34
+ Sprockets::Compressors.register_css_compressor(:csso, 'Csso::Compressor', default: true)
35
35
  end
36
36
  end
37
37
 
data/lib/volt/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Volt
2
2
  module Version
3
- STRING = '0.9.5'
3
+ STRING = '0.9.6.pre1'
4
4
  end
5
5
  end
data/lib/volt/volt/app.rb CHANGED
@@ -1,5 +1,10 @@
1
1
  require 'opal'
2
2
 
3
+ # On the server, setup the server env by default
4
+ unless RUBY_PLATFORM == 'opal'
5
+ ENV['SERVER'] ||= 'true'
6
+ end
7
+
3
8
  # on the client, we want to include the main volt.rb file
4
9
  require 'volt'
5
10
  require 'volt/models'
@@ -48,6 +53,8 @@ module Volt
48
53
  attr_accessor :sprockets, :opal_files
49
54
 
50
55
  def initialize(app_path=nil)
56
+ app_path ||= Dir.pwd
57
+
51
58
  if Volt.server? && !app_path
52
59
  raise "Volt::App.new requires an app path to boot"
53
60
  end
@@ -102,6 +109,8 @@ module Volt
102
109
  # Setup the middleware that we can only setup after all components boot.
103
110
  setup_postboot_middleware
104
111
 
112
+ setup_routes
113
+
105
114
  start_message_bus
106
115
  end
107
116
  end
@@ -10,6 +10,9 @@ end
10
10
  module Volt
11
11
  module ServerSetup
12
12
  module App
13
+ # Include Eventable to allow for lifecycle callbacks
14
+ include Eventable
15
+
13
16
  # The root url is where the volt app is mounted
14
17
  attr_reader :root_url
15
18
  # The app url is where the app folder (and sprockets) is mounted
@@ -37,6 +40,22 @@ module Volt
37
40
  @router = Routes.new
38
41
  end
39
42
 
43
+ def setup_routes
44
+ component_paths = @component_paths
45
+ @router.define do
46
+ # Load routes for each component
47
+ component_paths.components.values.flatten.uniq.each do |component_path|
48
+ routes_path = "#{component_path}/config/routes.rb"
49
+
50
+ if File.exist?(routes_path)
51
+ route_file = File.read(routes_path)
52
+ instance_eval(route_file, routes_path, 0)
53
+ end
54
+ end
55
+ end
56
+
57
+ end
58
+
40
59
  def setup_preboot_middleware
41
60
  @middleware = MiddlewareStack.new
42
61
  DefaultMiddlewareStack.preboot_setup(self, @middleware)
@@ -114,6 +114,10 @@ module Volt
114
114
  end
115
115
 
116
116
  def logout
117
+ # Notify the backend so we can remove the user_id from the user's channel
118
+ UserTasks.logout
119
+
120
+ # Remove the cookie so user is no longer logged in
117
121
  Volt.current_app.cookies.delete(:user_id)
118
122
  end
119
123
 
@@ -14,6 +14,7 @@ client '/missing', action: 'missing'
14
14
  client '/require_test', action: 'require_test'
15
15
  client '/images', action: 'images'
16
16
  client '/login_from_task', action: 'login_from_task'
17
+ client '/callbacks', action: 'callbacks'
17
18
 
18
19
  # Events
19
20
  client '/events', component: 'main', controller: 'events', action: 'index'