volt 0.9.5.pre9 → 0.9.5.pre11

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