volt 0.9.5 → 0.9.6.pre1

Sign up to get free protection for your applications and to get access to all the features.
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'