volt 0.9.5.pre9 → 0.9.5.pre11

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -1
  3. data/docs/UPGRADE_GUIDE.md +8 -0
  4. data/lib/volt/cli.rb +8 -2
  5. data/lib/volt/cli/new_gem.rb +1 -1
  6. data/lib/volt/controllers/http_controller.rb +3 -0
  7. data/lib/volt/controllers/login_as_helper.rb +12 -0
  8. data/lib/volt/models.rb +1 -2
  9. data/lib/volt/models/persistors/query/query_listener.rb +1 -1
  10. data/lib/volt/models/url.rb +4 -0
  11. data/lib/volt/page/bindings/attribute_binding.rb +46 -20
  12. data/lib/volt/page/tasks.rb +10 -2
  13. data/lib/volt/server/component_templates.rb +14 -8
  14. data/lib/volt/server/message_bus/message_encoder.rb +1 -1
  15. data/lib/volt/server/rack/asset_files.rb +14 -4
  16. data/lib/volt/server/rack/component_code.rb +8 -6
  17. data/lib/volt/server/rack/component_paths.rb +9 -2
  18. data/lib/volt/server/rack/sprockets_helpers_setup.rb +3 -2
  19. data/lib/volt/server/socket_connection_handler.rb +10 -1
  20. data/lib/volt/server/template_handlers/view_processor.rb +5 -1
  21. data/lib/volt/tasks/dispatcher.rb +13 -6
  22. data/lib/volt/tasks/task.rb +13 -0
  23. data/lib/volt/utils/ejson.rb +10 -5
  24. data/lib/volt/version.rb +1 -1
  25. data/lib/volt/volt/app.rb +2 -0
  26. data/lib/volt/volt/server_setup/app.rb +4 -0
  27. data/spec/apps/kitchen_sink/app/main/assets/css/app.scss +11 -0
  28. data/spec/apps/kitchen_sink/app/main/assets/images/volt-logo.jpg +0 -0
  29. data/spec/apps/kitchen_sink/app/main/config/initializers/sample_model_extend.rb +7 -0
  30. data/spec/apps/kitchen_sink/app/main/config/routes.rb +2 -0
  31. data/spec/apps/kitchen_sink/app/main/controllers/main_controller.rb +5 -1
  32. data/spec/apps/kitchen_sink/app/main/tasks/login_tasks.rb +7 -0
  33. data/spec/apps/kitchen_sink/app/main/views/main/images.html +10 -0
  34. data/spec/apps/kitchen_sink/app/main/views/main/login_from_task.html +7 -0
  35. data/spec/integration/images_spec.rb +10 -0
  36. data/spec/integration/user_spec.rb +8 -0
  37. data/spec/tasks/dispatcher_spec.rb +19 -4
  38. data/spec/utils/ejson_spec.rb +6 -0
  39. data/templates/project/config/app.rb.tt +6 -0
  40. data/templates/project/config/initializers/boot.rb +4 -0
  41. data/volt.gemspec +1 -1
  42. metadata +26 -7
  43. data/templates/project/config/initializers/.empty_directory +0 -0
  44. data/templates/project/config/initializers/client/.empty_directory +0 -0
  45. data/templates/project/config/initializers/server/.empty_directory +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bd442a1d9cbb5e408187f2d1f5c880ac29019f4b
4
- data.tar.gz: 94af6240b96dfb2f1521d86552772a2fd46a303b
3
+ metadata.gz: 2c855e92da3bc6bcee176d539ae365747bfe848e
4
+ data.tar.gz: ca339e18a47c685787f2e55eed17bd49f2c5de13
5
5
  SHA512:
6
- metadata.gz: f2711239ae33a5b2e46c6edff9cfa3ab5eb4265614480d95a971a60025588776186d10f4e7555bea679847673e8d42abd260fba50d67a557185281b2b8f1cf25
7
- data.tar.gz: c302d959c1e5492777213245304ba0a0d469aa73d252cc87db81f4271de689324af571879fa5603596638c8a73c219c74cd58a271254638ce258fb25c928f859
6
+ metadata.gz: 7ec01b186962830327f074a0c061ed6a41c4735ac12516175a666d1175a016da1a649b0dbf3be8275de78f8467df593d3d7d35dcfdf3453e5ebfa16dabf59257
7
+ data.tar.gz: ae01a3abe872c61cadd82426be2e940242d533bbd66a7cc342187a8a3b24ad139af02b3e5bcc036183af9c785234f62b6757cbca3fa82e68a2b017089938b210
data/CHANGELOG.md CHANGED
@@ -8,7 +8,7 @@
8
8
  - You can now disable auto-import of JS/CSS with ```disable_auto_import``` in a dependencies.rb file
9
9
  - Opal was upgraded to 0.8, which brings sourcemaps back (yah!)
10
10
  - Page load performance was improved, and more of sprockets was used for component loading.
11
- - You can now return promises in permissions blocks. Also, can_read?, can_create?, and .can_delete?
11
+ - You can now return promises in permissions blocks. Also, can_read?, can_create?, and .can_delete? now return promises.
12
12
  - Anything in /public is now served via Rack::Static in the default middleware stack. (So you can put user uploaded images in there)
13
13
  - You can now use _ or - in volt tag names and attributes. (We're moving to using dash ( - ) as the standard in html)
14
14
  - You can now trigger events on controllers rendered as tags. The events will bubble up through the DOM and can be caught by any e- bindings. See the docs for more information.
@@ -21,6 +21,10 @@
21
21
  ### Changed
22
22
  - fix issue with ```raw``` and promises (#275)
23
23
  - fix issue with .length on store (#269)
24
+ - The {root}/config/initializers directory is now only for server side code.
25
+ - Redid the initializer load order so all initializers run before any controllers/models/views are loaded.
26
+ - Added error message for when an unserializable object is returned from a Task
27
+ - Fixed issue with disable_encryption option
24
28
 
25
29
  ## 0.9.4
26
30
  ### Lingo Change
@@ -1,3 +1,11 @@
1
+ # 0.9.4 to 0.9.5
2
+
3
+ CSS url's now should be referenced either 1) as relative paths from the css file, or 2) using the full path from inside of app (eg: main/assets/images/background.jpg)
4
+
5
+ On models, .can_delete?, .can_read?, and .can_create? now return promises.
6
+
7
+ Check the CHANGELOG for more info.
8
+
1
9
  # 0.9.3 to 0.9.4
2
10
 
3
11
  We moved logic out of Volt::User and into the generated user file, so it is easier to customize. Add the following to your app/main/models/user.rb:
data/lib/volt/cli.rb CHANGED
@@ -120,11 +120,17 @@ module Volt
120
120
  no_tasks do
121
121
  # The logic for creating a new project. We want to be able to invoke this
122
122
  # inside of a method so we can run it with Dir.chdir
123
- def new_project(name, skip_gemfile = false)
123
+ def new_project(name, skip_gemfile = false, disable_encryption = false)
124
124
  require 'securerandom'
125
125
 
126
126
  # Grab the current volt version
127
- directory('project', name, version: Volt::Version::STRING, name: name, domain: name.dasherize.downcase, app_name: name.capitalize)
127
+ directory('project', name, {
128
+ version: Volt::Version::STRING,
129
+ name: name,
130
+ domain: name.dasherize.downcase,
131
+ app_name: name.capitalize,
132
+ disable_encryption: disable_encryption
133
+ })
128
134
 
129
135
  unless skip_gemfile
130
136
  # Move into the directory
@@ -31,7 +31,7 @@ class NewGem
31
31
  @thor.say 'Generating dummy project for integration specs', :green
32
32
  cli = Volt::CLI.new
33
33
  cli.shell.mute do
34
- cli.new_project('dummy', true)
34
+ cli.new_project('dummy', true, true)
35
35
  end
36
36
 
37
37
  # Remove gemfile
@@ -1,12 +1,15 @@
1
1
  require 'volt/server/rack/http_response_header'
2
2
  require 'volt/server/rack/http_response_renderer'
3
3
  require 'volt/controllers/http_controller/http_cookie_persistor'
4
+ require 'volt/controllers/login_as_helper'
4
5
  require 'volt/utils/lifecycle_callbacks'
5
6
 
6
7
  module Volt
7
8
  # Allow you to create controllers that act as http endpoints
8
9
  class HttpController
9
10
  include LifecycleCallbacks
11
+ include LoginAsHelper
12
+
10
13
  # Setup before_action and after_action
11
14
  setup_action_helpers_in_class(:before_action, :after_action)
12
15
 
@@ -0,0 +1,12 @@
1
+ module Volt
2
+ module LoginAsHelper
3
+ def login_as(user)
4
+ unless user.is_a?(Volt::User)
5
+ raise "login_as must be passed a user instance, you passed a #{user.class.to_s}"
6
+ end
7
+
8
+ # Assign the user_id cookie to the signature for the user id
9
+ cookies._user_id = Volt.user_login_signature(user)
10
+ end
11
+ end
12
+ end
data/lib/volt/models.rb CHANGED
@@ -16,9 +16,8 @@ require 'volt/models/root_models/root_models'
16
16
  if RUBY_PLATFORM == 'opal'
17
17
  require 'promise'
18
18
  else
19
- # Opal doesn't expose its promise library directly
20
19
  require 'opal'
21
-
20
+ # Opal doesn't expose its promise library directly
22
21
  gem_dir = File.join(Opal.gem_dir, '..')
23
22
  require(gem_dir + '/stdlib/promise')
24
23
  end
@@ -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.to_s.start_with?('user id or hash is incorrectly signed')
50
+ if err.starts_with?('VoltUserError:')
51
51
  # Delete the invalid cookie
52
52
  Volt.current_app.cookies.delete(:user_id)
53
53
  end
@@ -72,6 +72,10 @@ module Volt
72
72
 
73
73
  path, params = @router.params_to_url(params)
74
74
 
75
+ if path == nil
76
+ raise "No route matched, make sure you have the base route defined last: `client '/', {}`"
77
+ end
78
+
75
79
  new_url = "#{scheme}://#{host_with_port}#{path.chomp('/')}"
76
80
 
77
81
  # Add query params
@@ -14,6 +14,39 @@ module Volt
14
14
  end
15
15
 
16
16
  def setup
17
+ if `#{element}.is('select')`
18
+ @is_select = true
19
+ elsif `#{element}.is('[type=hidden]')`
20
+ @is_hidden = true
21
+ elsif `#{element}.is('[type=radio]')`
22
+ @is_radio = true
23
+ @selected_value = `#{element}.attr('value') || ''`
24
+ elsif `#{element}.is('option')`
25
+ @is_option = true
26
+ end
27
+
28
+ if @is_option
29
+ else
30
+ # Bind so when this value updates, we update
31
+ case @attribute_name
32
+ when 'value'
33
+ changed_event = Proc.new { changed }
34
+ if @is_select
35
+ `#{element}.on('change.attrbind', #{changed_event})`
36
+
37
+ invalidate_proc = Proc.new { invalidate }
38
+ `#{element}.on('invalidate', #{invalidate_proc})`
39
+ elsif @is_hidden
40
+ `#{element}.watch('value', #{changed_event})`
41
+ else
42
+ `#{element}.on('input.attrbind', #{changed_event})`
43
+ end
44
+ when 'checked'
45
+ changed_event = proc { |event| changed(event) }
46
+ `#{element}.on('change.attrbind', #{changed_event})`
47
+ end
48
+ end
49
+
17
50
  # Listen for changes
18
51
  @computation = lambda do
19
52
  begin
@@ -27,26 +60,6 @@ module Volt
27
60
  method(:getter_fail)
28
61
  )
29
62
 
30
- @is_select = `#{element}.is('select')`
31
- @is_hidden = `#{element}.is('[type=hidden]')`
32
- @is_radio = `#{element}.is('[type=radio]')`
33
- @selected_value = `#{element}.attr('value') || ''` if @is_radio
34
-
35
- # Bind so when this value updates, we update
36
- case @attribute_name
37
- when 'value'
38
- changed_event = Proc.new { changed }
39
- if @is_select
40
- `#{element}.on('change.attrbind', #{changed_event})`
41
- elsif @is_hidden
42
- `#{element}.watch('value', #{changed_event})`
43
- else
44
- `#{element}.on('input.attrbind', #{changed_event})`
45
- end
46
- when 'checked'
47
- changed_event = proc { |event| changed(event) }
48
- `#{element}.on('change.attrbind', #{changed_event})`
49
- end
50
63
  end
51
64
 
52
65
  def changed(event = nil)
@@ -99,6 +112,12 @@ module Volt
99
112
  def value=(val)
100
113
  case @attribute_name
101
114
  when 'value'
115
+ if @is_option
116
+ # When a new option is added, we trigger the invalidate event on the
117
+ # parent select so it will re-run update on the next tick and set
118
+ # the correct option.
119
+ `#{element}.parent('select').trigger('invalidate');`
120
+ end
102
121
  # TODO: only update if its not the same, this keeps it from moving the
103
122
  # cursor in text fields.
104
123
  `#{element}.val(#{val})` if val != `(#{element}.val() || '')`
@@ -116,6 +135,12 @@ module Volt
116
135
  end
117
136
  end
118
137
 
138
+ # On select boxes, when an option is added/changed, we want to run update
139
+ # again. By calling invalidate, it will run at most once on the next tick.
140
+ def invalidate
141
+ @computation.invalidate!
142
+ end
143
+
119
144
  def update_checked(value)
120
145
  value = false if value.is_a?(NilMethodCall) || value.nil?
121
146
 
@@ -131,6 +156,7 @@ module Volt
131
156
  when 'value'
132
157
  if @is_select
133
158
  `#{element}.off('change.attrbind')`
159
+ `#{element}.off('invalidate')`
134
160
  elsif @is_hidden
135
161
  `#{element}.unwatch('value')`
136
162
  else
@@ -41,14 +41,22 @@ module Volt
41
41
 
42
42
  # When a request is sent to the backend, it can attach a callback,
43
43
  # this is called from the backend to pass to the callback.
44
- def response(promise_id, result, error)
44
+ def response(promise_id, result, error, cookies)
45
+ # Set the cookies
46
+ if cookies
47
+ cookies.each do |key, value|
48
+ @volt_app.cookies.set(key, value)
49
+ end
50
+ end
51
+
45
52
  promise = @promises.delete(promise_id)
46
53
 
47
54
  if promise
48
55
  if error
49
56
  # TODO: full error handling
50
57
  Volt.logger.error('Task Response:')
51
- Volt.logger.error(error.inspect)
58
+ Volt.logger.error(error)
59
+
52
60
  promise.reject(error)
53
61
  else
54
62
  promise.resolve(result)
@@ -22,12 +22,22 @@ module Volt
22
22
  @client = client
23
23
  end
24
24
 
25
- def code
26
- code = generate_routes_code + generate_view_code
25
+ def initializer_code
26
+ if @client
27
+ generate_initializers_code
28
+ else
29
+ ''
30
+ end
31
+ end
32
+
33
+ def component_code
34
+ code = ''
35
+
36
+ code << generate_routes_code + generate_view_code
27
37
  if @client
28
38
  # On the backend, we just need the views
29
39
  code << generate_controller_code + generate_model_code +
30
- generate_tasks_code + generate_initializers_code
40
+ generate_tasks_code
31
41
  end
32
42
 
33
43
  code
@@ -161,16 +171,12 @@ module Volt
161
171
  end
162
172
 
163
173
  def generate_initializers_code
164
- # Include the root initializers
165
- paths = Dir["#{Volt.root}/config/initializers/*.rb"]
166
- paths += Dir["#{Volt.root}/config/initializers/client/*.rb"]
167
-
168
174
  paths = Dir["#{@component_path}/config/initializers/*.rb"]
169
175
  paths += Dir["#{@component_path}/config/initializers/client/*.rb"]
170
176
 
171
177
  code = "\n" + paths.map { |path| "require '#{localize_path(path)}'" }.join("\n")
172
178
 
173
- code
179
+ code + "\n\n"
174
180
  end
175
181
 
176
182
  private
@@ -13,7 +13,7 @@ module Volt
13
13
  end
14
14
 
15
15
  # Message bus is encrypted by default
16
- disable = Volt.config.message_bus.try(:disable_encryption)
16
+ disable = (msg_bus = Volt.config.message_bus) && msg_bus.disable_encryption
17
17
  @encrypted = !windows && (disable != true)
18
18
 
19
19
  if @encrypted
@@ -130,9 +130,11 @@ module Volt
130
130
  case type
131
131
  when :folder
132
132
  # for a folder, we search for all .js files and return a tag for them
133
+ base_path = base(path)
133
134
  javascript_files += Dir["#{path}/**/*.js"].sort.map do |folder|
134
135
  # Grab the component folder/assets/js/file.js
135
- @app_url + '/' + folder.split('/')[-4..-1].join('/')
136
+ local_path = folder[path.size..-1]
137
+ @app_url + '/' + base_path + local_path
136
138
  end
137
139
  when :javascript_file
138
140
  # javascript_file is a cdn path to a JS file
@@ -175,9 +177,10 @@ module Volt
175
177
  # Don't import any css/scss files that start with an underscore, so scss partials
176
178
  # aren't imported by default:
177
179
  # http://sass-lang.com/guide
178
- css_files += Dir["#{path}/**/[^_]*.{css,scss}"].sort.map do |folder|
179
- last4 = folder.split('/')[-4..-1].join('/').gsub(/[.]scss$/, '')
180
- css_path = @app_url + '/' + last4
180
+ base_path = base(path)
181
+ css_files += Dir["#{path}/**/[^_]*.{css,scss,sass}"].sort.map do |folder|
182
+ local_path = folder[path.size..-1].gsub(/[.](scss|sass)$/, '')
183
+ css_path = @app_url + '/' + base_path + local_path
181
184
  css_path += '.css' unless css_path =~ /[.]css$/
182
185
  css_path
183
186
  end
@@ -207,5 +210,12 @@ module Volt
207
210
  end
208
211
  end
209
212
  end
213
+
214
+ private
215
+ def base(path)
216
+ path.split('/')[-2..-1].join('/')
217
+ end
218
+
219
+
210
220
  end
211
221
  end
@@ -2,8 +2,8 @@ require 'volt/server/html_parser/view_parser'
2
2
  require 'volt/server/component_templates'
3
3
  require 'volt/server/rack/asset_files'
4
4
 
5
- # Takes in the name and all component paths and has a .code
6
- # method that returns all of the ruby setup code for the component.
5
+ # Takes in the name and all component paths returns all of the ruby code for the
6
+ # component and its dependencies.
7
7
  module Volt
8
8
  class ComponentCode
9
9
  def initialize(volt_app, component_name, component_paths, client = true)
@@ -16,15 +16,17 @@ module Volt
16
16
  # The client argument is for if this code is being generated for the client
17
17
  def code
18
18
  # Start with config code
19
- code = @client ? generate_config_code : ''
19
+ initializer_code = @client ? generate_config_code : ''
20
+ component_code = ''
20
21
 
21
22
  asset_files = AssetFiles.from_cache(@volt_app.app_url, @component_name, @component_paths)
22
23
  asset_files.component_paths.each do |component_path, component_name|
23
- code << ComponentTemplates.new(component_path, component_name, @client).code
24
- code << "\n\n"
24
+ comp_template = ComponentTemplates.new(component_path, component_name, @client)
25
+ initializer_code << comp_template.initializer_code + "\n\n"
26
+ component_code << comp_template.component_code + "\n\n"
25
27
  end
26
28
 
27
- code
29
+ initializer_code + component_code
28
30
  end
29
31
 
30
32
  def generate_config_code
@@ -54,13 +54,20 @@ module Volt
54
54
  @components
55
55
  end
56
56
 
57
+ # Setup load path for components
58
+ def setup_load_paths
59
+ unless RUBY_PLATFORM == 'opal'
60
+ app_folders do |app_folder|
61
+ $LOAD_PATH.unshift(app_folder)
62
+ end
63
+ end
64
+ end
65
+
57
66
  # Makes each components classes available on the load path, require classes.
58
67
  def require_in_components(volt_app)
59
68
  if RUBY_PLATFORM == 'opal'
60
69
  else
61
70
  app_folders do |app_folder|
62
- $LOAD_PATH.unshift(app_folder)
63
-
64
71
  # Sort so we get consistent load order across platforms
65
72
  Dir["#{app_folder}/*/{controllers,models,tasks}/*.rb"].each do |ruby_file|
66
73
  path = ruby_file.gsub(/^#{app_folder}\//, '')[0..-4]
@@ -33,10 +33,11 @@ module Volt
33
33
  end
34
34
 
35
35
  def add_linking_in_asset_path
36
+ app_path = @volt_app.app_path
36
37
  @env.context_class.class_eval do
37
38
  # We "freedom-patch" sprockets-helpers asset_path method to
38
39
  # automatically link assets.
39
- def asset_path(source, options = {})
40
+ define_method(:asset_path) do |source, options = {}|
40
41
  relative_path = source =~ /^[.][.]\//
41
42
  if relative_path
42
43
  component_root = logical_path.gsub(/\/[^\/]+$/, '')
@@ -47,7 +48,7 @@ module Volt
47
48
  if relative_path
48
49
  link_path = source
49
50
  else
50
- link_path = source.gsub(/^#{@volt_app.app_path}\//, '')
51
+ link_path = source.gsub(/^#{app_path}\//, '')
51
52
  end
52
53
 
53
54
  # Return for absolute urls (one's off site)
@@ -54,9 +54,19 @@ module Volt
54
54
  end
55
55
  end
56
56
 
57
+ # Used when the message is already encoded
58
+ def send_string_message(str)
59
+ send_raw_message(str)
60
+ end
61
+
57
62
  def send_message(*args)
63
+ # Encode as EJSON
58
64
  str = EJSON.stringify([*args])
59
65
 
66
+ send_raw_message(str)
67
+ end
68
+
69
+ def send_raw_message(str)
60
70
  @session.send(str)
61
71
 
62
72
  if RUNNING_SERVER == 'thin'
@@ -65,7 +75,6 @@ module Volt
65
75
  # TODO: Figure out the cause of the issue and submit a fix upstream.
66
76
  EM.next_tick {}
67
77
  end
68
-
69
78
  end
70
79
 
71
80
  def closed
@@ -33,19 +33,23 @@ module Volt
33
33
  # end
34
34
 
35
35
  def call(input)
36
+ context = input[:environment].context_class.new(input)
37
+ # context.link_asset('main/assets/images/lombard.jpg')
36
38
  # pp input
37
39
  data = input[:data]
38
40
 
39
41
  # input[:accept] = 'application/javascript'
40
42
  # input[:content_type] = 'application/javascript'
41
43
  # input[:environment].content_type = 'application/javascript'
42
- input[:cache].fetch([self.cache_key, data]) do
44
+ data = input[:cache].fetch([self.cache_key, data]) do
43
45
  filename = input[:filename]
44
46
  # puts input[:data].inspect
45
47
  # Remove all semicolons from source
46
48
  # input[:content_type] = 'application/javascript'
47
49
  compile(filename, input[:data])
48
50
  end
51
+
52
+ context.metadata.merge(data: data.to_str)
49
53
  end
50
54
 
51
55
  def compile(view_path, html)
@@ -22,9 +22,9 @@ module Volt
22
22
  @worker_pool = Concurrent::ImmediateExecutor.new
23
23
  else
24
24
  @worker_pool = Concurrent::ThreadPoolExecutor.new(
25
- min_threads: Volt.config.min_worker_threads,
26
- max_threads: Volt.config.max_worker_threads
27
- )
25
+ min_threads: Volt.config.min_worker_threads,
26
+ max_threads: Volt.config.max_worker_threads
27
+ )
28
28
  end
29
29
 
30
30
  @worker_timeout = Volt.config.worker_timeout || 60
@@ -102,6 +102,7 @@ module Volt
102
102
  klass = Object.send(:const_get, class_name)
103
103
 
104
104
  promise = Promise.new
105
+ cookies = nil
105
106
 
106
107
  start_time = Time.now.to_f
107
108
 
@@ -116,7 +117,9 @@ module Volt
116
117
  Timeout.timeout(klass.__timeout || @worker_timeout) do
117
118
  Thread.current['meta'] = meta_data
118
119
  begin
119
- result = klass.new(@volt_app, channel, self).send(method_name, *args)
120
+ klass_inst = klass.new(@volt_app, channel, self)
121
+ result = klass_inst.send(method_name, *args)
122
+ cookies = klass_inst.fetch_cookies
120
123
  ensure
121
124
  Thread.current['meta'] = nil
122
125
  end
@@ -143,12 +146,16 @@ module Volt
143
146
 
144
147
  # Run the promise and pass the return value/error back to the client
145
148
  promise.then do |result|
146
- channel.send_message('response', callback_id, result, nil)
149
+ reply = EJSON.stringify(['response', callback_id, result, nil, cookies])
150
+ channel.send_string_message(reply)
147
151
 
148
152
  finish.call
149
153
  end.fail do |error|
150
154
  finish.call(error)
151
- channel.send_message('response', callback_id, nil, error)
155
+ # Convert the error into a string so it can be serialized.
156
+ error_str = "#{error.class.to_s}: #{error.to_s}"
157
+
158
+ channel.send_message('response', callback_id, nil, error_str, cookies)
152
159
  end
153
160
 
154
161
  end
@@ -1,4 +1,5 @@
1
1
  require 'volt/controllers/collection_helpers'
2
+ require 'volt/controllers/login_as_helper'
2
3
 
3
4
  module Volt
4
5
  class Task
@@ -17,6 +18,7 @@ module Volt
17
18
  end
18
19
  else
19
20
  include CollectionHelpers
21
+ include LoginAsHelper
20
22
 
21
23
  class_attribute :__timeout
22
24
 
@@ -41,6 +43,17 @@ module Volt
41
43
  self.__timeout = value
42
44
  end
43
45
 
46
+ def cookies
47
+ @cookies ||= Model.new
48
+ end
49
+
50
+ # Get the cookies that got set
51
+ def fetch_cookies
52
+ if @cookies
53
+ @cookies.to_h.reject {|k,v| k == :id }
54
+ end
55
+ end
56
+
44
57
  # On the backend, we proxy all class methods like we would
45
58
  # on the front-end. This returns a promise, even if the
46
59
  # original code did not.
@@ -2,6 +2,10 @@ require 'json'
2
2
 
3
3
  module Volt
4
4
  class EJSON
5
+ class NonEjsonType < Exception ; end
6
+
7
+ OTHER_VALID_CLASSES = [String, Symbol, TrueClass, FalseClass, Numeric, NilClass]
8
+
5
9
  def self.stringify(obj)
6
10
  encode(obj).to_json
7
11
  end
@@ -48,12 +52,13 @@ module Volt
48
52
 
49
53
  [key, value]
50
54
  end.to_h
55
+ elsif Time === obj
56
+ {'$date' => obj.to_i * 1_000}
57
+ elsif OTHER_VALID_CLASSES.any? {|klass| obj.is_a?(klass) }
58
+ obj
51
59
  else
52
- if obj.is_a?(Time)
53
- {'$date' => obj.to_i * 1_000}
54
- else
55
- obj
56
- end
60
+ # Not a valid class for serializing, raise an exception
61
+ raise NonEjsonType, "Unable to serialize #{obj.inspect} to EJSON"
57
62
  end
58
63
  end
59
64
  end
data/lib/volt/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Volt
2
2
  module Version
3
- STRING = '0.9.5.pre9'
3
+ STRING = '0.9.5.pre11'
4
4
  end
5
5
  end
data/lib/volt/volt/app.rb CHANGED
@@ -80,6 +80,8 @@ module Volt
80
80
  # Require in app and initializers
81
81
  run_app_and_initializers unless RUBY_PLATFORM == 'opal'
82
82
 
83
+ require_components
84
+
83
85
  # abort_on_exception is a useful debugging tool, and in my opinion something
84
86
  # you probbaly want on. That said you can disable it if you need.
85
87
  unless RUBY_PLATFORM == 'opal'
@@ -18,6 +18,10 @@ module Volt
18
18
  def setup_paths
19
19
  # Load component paths
20
20
  @component_paths = ComponentPaths.new(@app_path)
21
+ @component_paths.setup_load_paths
22
+ end
23
+
24
+ def require_components
21
25
  @component_paths.require_in_components(self)
22
26
  end
23
27
 
@@ -0,0 +1,11 @@
1
+ .logo1 {
2
+ width: 129px;
3
+ height: 50px;
4
+ background: asset-url('../images/volt-logo.jpg') no-repeat center center;
5
+ }
6
+
7
+ .logo2 {
8
+ width: 129px;
9
+ height: 50px;
10
+ background: asset-url('main/assets/images/volt-logo.jpg') no-repeat center center;
11
+ }
@@ -0,0 +1,7 @@
1
+ module Volt
2
+ class Model
3
+ def self.acts_awesome
4
+ true
5
+ end
6
+ end
7
+ end
@@ -12,6 +12,8 @@ client '/todos', controller: 'todos'
12
12
  client '/html_safe', action: 'html_safe'
13
13
  client '/missing', action: 'missing'
14
14
  client '/require_test', action: 'require_test'
15
+ client '/images', action: 'images'
16
+ client '/login_from_task', action: 'login_from_task'
15
17
 
16
18
  # Events
17
19
  client '/events', component: 'main', controller: 'events', action: 'index'
@@ -5,7 +5,7 @@ module Main
5
5
  class MainController < Volt::ModelController
6
6
  model :page
7
7
 
8
- reactive_accessor :blur_count, :focus_count
8
+ reactive_accessor :blur_count, :focus_count, :image_loaded
9
9
 
10
10
  def index
11
11
  a = {}
@@ -88,6 +88,10 @@ module Main
88
88
  self.focus_count += 1
89
89
  end
90
90
 
91
+ def do_login_from_task
92
+ LoginTasks.login_first_user
93
+ end
94
+
91
95
  private
92
96
 
93
97
  # the main template contains a #template binding that shows another
@@ -0,0 +1,7 @@
1
+ class LoginTasks < Volt::Task
2
+ def login_first_user
3
+ store.users.first.then do |first_user|
4
+ login_as(first_user)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ <:Title>
2
+ Images
3
+
4
+ <:Body>
5
+ <h1>Images</h1>
6
+
7
+ <div class="logo1"></div>
8
+ <div class="logo2"></div>
9
+
10
+ <img src="/app/main/assets/images/volt-logo.jpg" />
@@ -0,0 +1,7 @@
1
+ <:Title>
2
+ Login From Task
3
+
4
+ <:Body>
5
+ <h1>Login From Task</h1>
6
+
7
+ <button e-click="do_login_from_task">Login First User</button>
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'image loading', type: :feature, sauce: true do
4
+ it 'should load images in assets' do
5
+ visit '/images'
6
+
7
+ loaded = page.evaluate_script("$('img').get(0).complete")
8
+ expect(loaded).to eq(true)
9
+ end
10
+ end
@@ -88,6 +88,14 @@ describe 'user accounts', type: :feature, sauce: true do
88
88
 
89
89
  expect(page).to have_content('Password did not match')
90
90
  end
91
+
92
+ it 'should let you login from a task' do
93
+ visit '/login_from_task'
94
+
95
+ click_button 'Login First User'
96
+
97
+ expect(page).to have_content('Test Account 9550')
98
+ end
91
99
  end
92
100
 
93
101
  end
@@ -5,6 +5,10 @@ if RUBY_PLATFORM != 'opal'
5
5
  def allowed_method(arg1, arg2)
6
6
  'yes' + arg1 + arg2
7
7
  end
8
+
9
+ def set_cookie
10
+ cookies._something = 'awesome'
11
+ end
8
12
  end
9
13
 
10
14
  class WorkerPoolStub
@@ -29,7 +33,9 @@ if RUBY_PLATFORM != 'opal'
29
33
  it 'should only allow method calls on Task or above in the inheritance chain' do
30
34
  channel = double('channel')
31
35
 
32
- expect(channel).to receive(:send_message).with('response', 0, 'yes it works', nil)
36
+ # Tasks handle their own conversion to EJSON
37
+ msg = Volt::EJSON.stringify(['response', 0, 'yes it works', nil, nil])
38
+ expect(channel).to receive(:send_string_message).with(msg)
33
39
 
34
40
  dispatcher.dispatch(channel, [0, 'TestTask', :allowed_method, {}, ' it', ' works'])
35
41
  end
@@ -37,7 +43,7 @@ if RUBY_PLATFORM != 'opal'
37
43
  it 'should not allow eval' do
38
44
  channel = double('channel')
39
45
 
40
- expect(channel).to receive(:send_message).with('response', 0, nil, RuntimeError.new('unsafe method: eval'))
46
+ expect(channel).to receive(:send_message).with('response', 0, nil, "RuntimeError: unsafe method: eval", nil)
41
47
 
42
48
  dispatcher.dispatch(channel, [0, 'TestTask', :eval, '5 + 10'])
43
49
  end
@@ -45,7 +51,7 @@ if RUBY_PLATFORM != 'opal'
45
51
  it 'should not allow instance_eval' do
46
52
  channel = double('channel')
47
53
 
48
- expect(channel).to receive(:send_message).with('response', 0, nil, RuntimeError.new('unsafe method: instance_eval'))
54
+ expect(channel).to receive(:send_message).with('response', 0, nil, 'RuntimeError: unsafe method: instance_eval', nil)
49
55
 
50
56
  dispatcher.dispatch(channel, [0, 'TestTask', :instance_eval, '5 + 10'])
51
57
  end
@@ -53,7 +59,7 @@ if RUBY_PLATFORM != 'opal'
53
59
  it 'should not allow #methods' do
54
60
  channel = double('channel')
55
61
 
56
- expect(channel).to receive(:send_message).with('response', 0, nil, RuntimeError.new('unsafe method: methods'))
62
+ expect(channel).to receive(:send_message).with('response', 0, nil, 'RuntimeError: unsafe method: methods', nil)
57
63
 
58
64
  dispatcher.dispatch(channel, [0, 'TestTask', :methods])
59
65
  end
@@ -67,6 +73,15 @@ if RUBY_PLATFORM != 'opal'
67
73
  dispatcher.dispatch(channel, [0, 'TestTask', :allowed_method, {}, ' it', ' works'])
68
74
  end
69
75
 
76
+ it 'should let you set a cookie' do
77
+ channel = double('channel')
78
+
79
+ allow(channel).to receive(:send_message).with('response', 0, 'yes it works', {:something=>"awesome"})
80
+ expect(Volt.logger).to receive(:log_dispatch)
81
+
82
+ dispatcher.dispatch(channel, [0, 'TestTask', :set_cookie, {}])
83
+ end
84
+
70
85
  it 'closes the channel' do
71
86
  disp = dispatcher
72
87
  channel = Volt::ChannelStub.new
@@ -92,6 +92,12 @@ describe Volt::EJSON, '.stringify' do
92
92
  )
93
93
  end
94
94
 
95
+ it 'should convert symbols to strings' do
96
+ stringified = subject.stringify({something: :awesome})
97
+
98
+ expect(stringified).to eq('{"something":"awesome"}')
99
+ end
100
+
95
101
  it 'escapes reserved key when type is incorrect' do
96
102
  stringified = subject.stringify '$date' => 'something'
97
103
 
@@ -102,7 +102,13 @@ Volt.configure do |config|
102
102
  #
103
103
  # Encrypt message bus - messages on the message bus are encrypted by default
104
104
  # using rbnacl.
105
+ <% if config[:disable_encryption] %>
106
+ #
107
+ # For dummy apps, we disable_encryption, to simplify the gem requirements.
108
+ config.message_bus.disable_encryption = true
109
+ <% else %>
105
110
  # config.message_bus.disable_encryption = true
111
+ <% end %>
106
112
  #
107
113
  # ## MessageBus Server -- the message bus binds to a port and ip which the
108
114
  # other volt instances need to be able to connect to. You can customize
@@ -0,0 +1,4 @@
1
+ # Any ./config/initializers/*.rb files will when the app starts up on the server.
2
+ # To load code on the client (or client and server), you can use the
3
+ # config/initializers folder in a component in the app directory. This folder
4
+ # is only for things that are server only. (Usually for things like config)
data/volt.gemspec CHANGED
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
26
26
  spec.add_dependency 'sass', '~> 3.4.15'
27
27
  spec.add_dependency 'listen', '~> 3.0.1'
28
28
  spec.add_dependency 'configurations', '~> 2.0.0.pre'
29
- spec.add_dependency 'opal', '~> 0.8.0'
29
+ spec.add_dependency 'opal', ['>= 0.8.0', '< 0.9']
30
30
  spec.add_dependency 'bundler', '>= 1.5'
31
31
  spec.add_dependency 'faye-websocket', '~> 0.10.0'
32
32
  spec.add_dependency 'sprockets-helpers', '~> 1.2.1'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: volt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.5.pre9
4
+ version: 0.9.5.pre11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Stout
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-08-25 00:00:00.000000000 Z
11
+ date: 2015-08-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -98,16 +98,22 @@ dependencies:
98
98
  name: opal
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - "~>"
101
+ - - ">="
102
102
  - !ruby/object:Gem::Version
103
103
  version: 0.8.0
104
+ - - "<"
105
+ - !ruby/object:Gem::Version
106
+ version: '0.9'
104
107
  type: :runtime
105
108
  prerelease: false
106
109
  version_requirements: !ruby/object:Gem::Requirement
107
110
  requirements:
108
- - - "~>"
111
+ - - ">="
109
112
  - !ruby/object:Gem::Version
110
113
  version: 0.8.0
114
+ - - "<"
115
+ - !ruby/object:Gem::Version
116
+ version: '0.9'
111
117
  - !ruby/object:Gem::Dependency
112
118
  name: bundler
113
119
  requirement: !ruby/object:Gem::Requirement
@@ -416,6 +422,7 @@ files:
416
422
  - lib/volt/controllers/collection_helpers.rb
417
423
  - lib/volt/controllers/http_controller.rb
418
424
  - lib/volt/controllers/http_controller/http_cookie_persistor.rb
425
+ - lib/volt/controllers/login_as_helper.rb
419
426
  - lib/volt/controllers/model_controller.rb
420
427
  - lib/volt/controllers/template_helpers.rb
421
428
  - lib/volt/data_stores/base_adaptor_client.rb
@@ -621,8 +628,11 @@ files:
621
628
  - spec/apps/file_loading/app/slideshow/assets/js/test3.js
622
629
  - spec/apps/kitchen_sink/.gitignore
623
630
  - spec/apps/kitchen_sink/Gemfile
631
+ - spec/apps/kitchen_sink/app/main/assets/css/app.scss
624
632
  - spec/apps/kitchen_sink/app/main/assets/css/todos.css
633
+ - spec/apps/kitchen_sink/app/main/assets/images/volt-logo.jpg
625
634
  - spec/apps/kitchen_sink/app/main/config/dependencies.rb
635
+ - spec/apps/kitchen_sink/app/main/config/initializers/sample_model_extend.rb
626
636
  - spec/apps/kitchen_sink/app/main/config/routes.rb
627
637
  - spec/apps/kitchen_sink/app/main/controllers/events_controller.rb
628
638
  - spec/apps/kitchen_sink/app/main/controllers/main_controller.rb
@@ -631,6 +641,7 @@ files:
631
641
  - spec/apps/kitchen_sink/app/main/controllers/upload_controller.rb
632
642
  - spec/apps/kitchen_sink/app/main/controllers/yield_component_controller.rb
633
643
  - spec/apps/kitchen_sink/app/main/models/user.rb
644
+ - spec/apps/kitchen_sink/app/main/tasks/login_tasks.rb
634
645
  - spec/apps/kitchen_sink/app/main/views/events/index.html
635
646
  - spec/apps/kitchen_sink/app/main/views/mailers/welcome.email
636
647
  - spec/apps/kitchen_sink/app/main/views/main/bindings.html
@@ -639,7 +650,9 @@ files:
639
650
  - spec/apps/kitchen_sink/app/main/views/main/flash.html
640
651
  - spec/apps/kitchen_sink/app/main/views/main/form.html
641
652
  - spec/apps/kitchen_sink/app/main/views/main/html_safe.html
653
+ - spec/apps/kitchen_sink/app/main/views/main/images.html
642
654
  - spec/apps/kitchen_sink/app/main/views/main/index.html
655
+ - spec/apps/kitchen_sink/app/main/views/main/login_from_task.html
643
656
  - spec/apps/kitchen_sink/app/main/views/main/main.html
644
657
  - spec/apps/kitchen_sink/app/main/views/main/missing.html
645
658
  - spec/apps/kitchen_sink/app/main/views/main/require_test.html
@@ -671,6 +684,7 @@ files:
671
684
  - spec/integration/first_last_spec.rb
672
685
  - spec/integration/flash_spec.rb
673
686
  - spec/integration/http_endpoints_spec.rb
687
+ - spec/integration/images_spec.rb
674
688
  - spec/integration/list_spec.rb
675
689
  - spec/integration/missing_spec.rb
676
690
  - spec/integration/raw_html_binding.rb
@@ -834,9 +848,7 @@ files:
834
848
  - templates/project/config.ru
835
849
  - templates/project/config/app.rb.tt
836
850
  - templates/project/config/base/index.html
837
- - templates/project/config/initializers/.empty_directory
838
- - templates/project/config/initializers/client/.empty_directory
839
- - templates/project/config/initializers/server/.empty_directory
851
+ - templates/project/config/initializers/boot.rb
840
852
  - templates/project/spec/app/main/controllers/server/sample_http_controller_spec.rb
841
853
  - templates/project/spec/app/main/integration/sample_integration_spec.rb
842
854
  - templates/project/spec/app/main/models/sample_model_spec.rb
@@ -886,8 +898,11 @@ test_files:
886
898
  - spec/apps/file_loading/app/slideshow/assets/js/test3.js
887
899
  - spec/apps/kitchen_sink/.gitignore
888
900
  - spec/apps/kitchen_sink/Gemfile
901
+ - spec/apps/kitchen_sink/app/main/assets/css/app.scss
889
902
  - spec/apps/kitchen_sink/app/main/assets/css/todos.css
903
+ - spec/apps/kitchen_sink/app/main/assets/images/volt-logo.jpg
890
904
  - spec/apps/kitchen_sink/app/main/config/dependencies.rb
905
+ - spec/apps/kitchen_sink/app/main/config/initializers/sample_model_extend.rb
891
906
  - spec/apps/kitchen_sink/app/main/config/routes.rb
892
907
  - spec/apps/kitchen_sink/app/main/controllers/events_controller.rb
893
908
  - spec/apps/kitchen_sink/app/main/controllers/main_controller.rb
@@ -896,6 +911,7 @@ test_files:
896
911
  - spec/apps/kitchen_sink/app/main/controllers/upload_controller.rb
897
912
  - spec/apps/kitchen_sink/app/main/controllers/yield_component_controller.rb
898
913
  - spec/apps/kitchen_sink/app/main/models/user.rb
914
+ - spec/apps/kitchen_sink/app/main/tasks/login_tasks.rb
899
915
  - spec/apps/kitchen_sink/app/main/views/events/index.html
900
916
  - spec/apps/kitchen_sink/app/main/views/mailers/welcome.email
901
917
  - spec/apps/kitchen_sink/app/main/views/main/bindings.html
@@ -904,7 +920,9 @@ test_files:
904
920
  - spec/apps/kitchen_sink/app/main/views/main/flash.html
905
921
  - spec/apps/kitchen_sink/app/main/views/main/form.html
906
922
  - spec/apps/kitchen_sink/app/main/views/main/html_safe.html
923
+ - spec/apps/kitchen_sink/app/main/views/main/images.html
907
924
  - spec/apps/kitchen_sink/app/main/views/main/index.html
925
+ - spec/apps/kitchen_sink/app/main/views/main/login_from_task.html
908
926
  - spec/apps/kitchen_sink/app/main/views/main/main.html
909
927
  - spec/apps/kitchen_sink/app/main/views/main/missing.html
910
928
  - spec/apps/kitchen_sink/app/main/views/main/require_test.html
@@ -936,6 +954,7 @@ test_files:
936
954
  - spec/integration/first_last_spec.rb
937
955
  - spec/integration/flash_spec.rb
938
956
  - spec/integration/http_endpoints_spec.rb
957
+ - spec/integration/images_spec.rb
939
958
  - spec/integration/list_spec.rb
940
959
  - spec/integration/missing_spec.rb
941
960
  - spec/integration/raw_html_binding.rb