volt 0.9.2 → 0.9.3.pre1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +7 -0
  3. data/CONTRIBUTING.md +4 -0
  4. data/Gemfile +3 -0
  5. data/app/volt/assets/js/volt_js_polyfills.js +0 -1
  6. data/app/volt/assets/js/volt_watch.js +217 -0
  7. data/app/volt/models/user.rb +7 -2
  8. data/app/volt/tasks/query_tasks.rb +6 -0
  9. data/lib/volt/boot.rb +1 -1
  10. data/lib/volt/cli/generate.rb +1 -1
  11. data/lib/volt/config.rb +12 -2
  12. data/lib/volt/controllers/model_controller.rb +1 -1
  13. data/lib/volt/data_stores/base_adaptor_client.rb +34 -0
  14. data/lib/volt/data_stores/{base.rb → base_adaptor_server.rb} +1 -1
  15. data/lib/volt/data_stores/data_store.rb +23 -7
  16. data/lib/volt/models/array_model.rb +3 -2
  17. data/lib/volt/models/model.rb +29 -91
  18. data/lib/volt/models/{dirty.rb → model_helpers/dirty.rb} +0 -0
  19. data/lib/volt/models/{listener_tracker.rb → model_helpers/listener_tracker.rb} +0 -0
  20. data/lib/volt/models/model_helpers/model_change_helpers.rb +76 -0
  21. data/lib/volt/models/{model_helpers.rb → model_helpers/model_helpers.rb} +0 -0
  22. data/lib/volt/models/persistors/array_store.rb +2 -23
  23. data/lib/volt/models/persistors/query/normalizer.rb +0 -44
  24. data/{templates/project/lib/.empty_directory → lib/volt/models/validations/errors.rb} +0 -0
  25. data/lib/volt/models/{validations.rb → validations/validations.rb} +80 -26
  26. data/lib/volt/page/bindings/attribute_binding.rb +17 -3
  27. data/lib/volt/page/page.rb +1 -0
  28. data/lib/volt/reactive/eventable.rb +1 -0
  29. data/lib/volt/server.rb +2 -1
  30. data/lib/volt/server/component_templates.rb +66 -16
  31. data/lib/volt/server/forking_server.rb +16 -14
  32. data/lib/volt/server/html_parser/sandlebars_parser.rb +2 -0
  33. data/lib/volt/server/html_parser/view_scope.rb +2 -0
  34. data/lib/volt/server/rack/component_paths.rb +4 -2
  35. data/lib/volt/server/rack/opal_files.rb +4 -2
  36. data/lib/volt/server/socket_connection_handler.rb +5 -1
  37. data/lib/volt/server/template_handlers/handlers.rb +0 -0
  38. data/lib/volt/spec/setup.rb +23 -8
  39. data/lib/volt/tasks/dispatcher.rb +4 -0
  40. data/lib/volt/utils/promise_patch.rb +3 -0
  41. data/lib/volt/version.rb +1 -1
  42. data/spec/apps/kitchen_sink/Gemfile +5 -0
  43. data/spec/apps/kitchen_sink/app/main/config/routes.rb +1 -0
  44. data/spec/apps/kitchen_sink/app/main/controllers/main_controller.rb +10 -0
  45. data/spec/apps/kitchen_sink/app/main/views/main/bindings.html +20 -0
  46. data/spec/apps/kitchen_sink/app/main/views/main/form.html +73 -0
  47. data/spec/apps/kitchen_sink/app/main/views/main/main.html +1 -0
  48. data/spec/integration/bindings_spec.rb +33 -0
  49. data/spec/integration/user_spec.rb +51 -21
  50. data/spec/models/associations_spec.rb +8 -0
  51. data/spec/models/model_spec.rb +7 -0
  52. data/spec/models/user_spec.rb +20 -0
  53. data/spec/models/validations_spec.rb +2 -1
  54. data/spec/models/validators/block_validations_spec.rb +53 -0
  55. data/spec/page/bindings/template_binding/view_lookup_for_path_spec.rb +10 -0
  56. data/spec/page/path_string_renderer_spec.rb +6 -0
  57. data/spec/reactive/eventable_spec.rb +24 -6
  58. data/spec/server/component_templates_spec.rb +21 -0
  59. data/spec/server/html_parser/sandlebars_parser_spec.rb +12 -13
  60. data/spec/server/html_parser/view_parser_spec.rb +3 -0
  61. data/spec/server/rack/asset_files_spec.rb +2 -2
  62. data/spec/server/rack/http_resource_spec.rb +10 -0
  63. data/spec/tasks/dispatcher_spec.rb +5 -0
  64. data/spec/tasks/user_tasks_spec.rb +59 -0
  65. data/spec/utils/task_argument_filtererer_spec.rb +6 -0
  66. data/templates/newgem/app/newgem/config/initializers/boot.rb +10 -0
  67. data/templates/newgem/lib/newgem.rb.tt +13 -0
  68. data/templates/project/Gemfile.tt +3 -0
  69. data/templates/project/app/main/lib/.empty_directory +0 -0
  70. data/volt.gemspec +3 -1
  71. metadata +24 -25
  72. data/lib/volt/data_stores/mongo_driver.rb +0 -69
@@ -26,14 +26,22 @@ module Volt
26
26
  update(result)
27
27
  end
28
28
 
29
+ @is_select = `#{element}.is('select')`
30
+ @is_hidden = `#{element}.is('[type=hidden]')`
29
31
  @is_radio = `#{element}.is('[type=radio]')`
30
32
  @selected_value = `#{element}.attr('value') || ''` if @is_radio
31
33
 
32
34
  # Bind so when this value updates, we update
33
35
  case @attribute_name
34
36
  when 'value'
35
- changed_event = proc { changed }
36
- `#{element}.on('input.attrbind', #{changed_event})`
37
+ changed_event = Proc.new { changed }
38
+ if @is_select
39
+ `#{element}.on('change.attrbind', #{changed_event})`
40
+ elsif @is_hidden
41
+ `#{element}.watch('value', #{changed_event})`
42
+ else
43
+ `#{element}.on('input.attrbind', #{changed_event})`
44
+ end
37
45
  when 'checked'
38
46
  changed_event = proc { |event| changed(event) }
39
47
  `#{element}.on('change.attrbind', #{changed_event})`
@@ -120,7 +128,13 @@ module Volt
120
128
  # aren't responsible for it being there.
121
129
  case @attribute_name
122
130
  when 'value'
123
- `#{element}.off('input.attrbind', #{nil})`
131
+ if @is_select
132
+ `#{element}.off('change.attrbind')`
133
+ elsif @is_hidden
134
+ `#{element}.unwatch('value')`
135
+ else
136
+ `#{element}.off('input.attrbind', #{nil})`
137
+ end
124
138
  when 'checked'
125
139
  `#{element}.off('change.attrbind', #{nil})`
126
140
  end
@@ -15,6 +15,7 @@ require 'volt/page/string_template_renderer'
15
15
  require 'volt/page/document_events'
16
16
  require 'volt/page/sub_context'
17
17
  require 'volt/page/targets/dom_target'
18
+ require 'volt/data_stores/base_adaptor_client'
18
19
 
19
20
  if RUBY_PLATFORM == 'opal'
20
21
  require 'volt/page/channel'
@@ -2,6 +2,7 @@ module Volt
2
2
  # Listeners are returned from #on on a class with Eventable included.
3
3
  # Listeners can be stopped by calling #remove
4
4
  class Listener
5
+ attr_reader :events
5
6
  def initialize(klass, events, callback)
6
7
  @klass = klass
7
8
  @events = events
data/lib/volt/server.rb CHANGED
@@ -7,6 +7,7 @@ require 'sass'
7
7
  require 'volt/utils/tilt_patch'
8
8
  require 'sprockets-sass'
9
9
 
10
+
10
11
  require 'volt'
11
12
  require 'volt/tasks/dispatcher'
12
13
  require 'volt/tasks/task_handler'
@@ -49,7 +50,7 @@ module Volt
49
50
  attr_reader :listener, :app_path
50
51
 
51
52
  # You can also optionally pass in a prebooted app
52
- def initialize(root_path = nil, app = false)
53
+ def initialize(root_path = nil, app = nil)
53
54
  @root_path = root_path || Dir.pwd
54
55
  @volt_app = app
55
56
 
@@ -4,7 +4,47 @@ require 'volt/tasks/task_handler'
4
4
  # Initialize with the path to a component and returns all the front-end
5
5
  # setup code (for controllers, models, views, and routes)
6
6
  module Volt
7
+ class BasicHandler
8
+ def call(file_contents)
9
+ file_contents
10
+ end
11
+ end
12
+
7
13
  class ComponentTemplates
14
+
15
+ module Handlers #:nodoc:
16
+ # Setup default handler on extend
17
+ def self.extended(base)
18
+ base.register_template_handler :html, BasicHandler.new
19
+ base.register_template_handler :email, BasicHandler.new
20
+ end
21
+
22
+ @@template_handlers = {}
23
+
24
+ def self.extensions
25
+ @@template_handlers.keys
26
+ end
27
+
28
+ # Register an object that knows how to handle template files with the given
29
+ # extensions. This can be used to implement new template types.
30
+ # The handler must respond to +:call+, which will be passed the template
31
+ # and should return the rendered template as a String.
32
+ def register_template_handler(extension, handler)
33
+ @@template_handlers[extension.to_sym] = handler
34
+ end
35
+
36
+ def registered_template_handler(extension)
37
+ extension && @@template_handlers[extension.to_sym]
38
+ end
39
+
40
+ def handler_for_extension(extension)
41
+ registered_template_handler(extension)
42
+ end
43
+ end
44
+
45
+ extend ComponentTemplates::Handlers
46
+
47
+
8
48
  # client is if we are generating for the client or backend
9
49
  def initialize(component_path, component_name, client = true)
10
50
  @component_path = component_path
@@ -16,7 +56,8 @@ module Volt
16
56
  code = generate_routes_code + generate_view_code
17
57
  if @client
18
58
  # On the backend, we just need the views
19
- code << generate_controller_code + generate_model_code + generate_tasks_code + generate_initializers_code
59
+ code << generate_controller_code + generate_model_code +
60
+ generate_tasks_code + generate_initializers_code
20
61
  end
21
62
 
22
63
  code
@@ -31,39 +72,47 @@ module Volt
31
72
  end
32
73
 
33
74
  def generate_view_code
34
- code = ''
75
+ code = ''
35
76
  views_path = "#{@component_path}/views/"
36
77
 
37
- exts = ['html']
38
-
39
- # Only load email templates on the server
40
- exts << 'email' unless @client
78
+ exts = Handlers.extensions
41
79
 
42
80
  # Load all templates in the folder
43
81
  Dir["#{views_path}*/*.{#{exts.join(',')}}"].sort.each do |view_path|
82
+ # file extension
83
+ format = File.extname(view_path).downcase.delete('.').to_sym
84
+
44
85
  # Get the path for the template, supports templates in folders
45
86
  template_path = view_path[views_path.size..-1].gsub(/[.](#{exts.join('|')})$/, '')
46
87
  template_path = "#{@component_name}/#{template_path}"
47
88
 
48
- all_templates = ViewParser.new(File.read(view_path), template_path)
89
+ file_contents = File.read(view_path)
49
90
 
50
- binding_initializers = []
51
- all_templates.templates.each_pair do |name, template|
52
- binding_code = []
91
+ # Process template if we have a handler for this file type
92
+ if handler = ComponentTemplates.handler_for_extension(format)
93
+ file_contents = handler.call(file_contents)
53
94
 
54
- if template['bindings']
55
- template['bindings'].each_pair do |key, value|
56
- binding_code << "#{key.inspect} => [#{value.join(', ')}]"
95
+ all_templates = ViewParser.new(file_contents, template_path)
96
+
97
+ binding_initializers = []
98
+ all_templates.templates.each_pair do |name, template|
99
+ binding_code = []
100
+
101
+ if template['bindings']
102
+ template['bindings'].each_pair do |key, value|
103
+ binding_code << "#{key.inspect} => [#{value.join(', ')}]"
104
+ end
57
105
  end
58
- end
59
106
 
60
- binding_code = "{#{binding_code.join(', ')}}"
107
+ binding_code = "{#{binding_code.join(', ')}}"
61
108
 
62
- code << "#{page_reference}.add_template(#{name.inspect}, #{template['html'].inspect}, #{binding_code})\n"
109
+ code << "#{page_reference}.add_template(#{name.inspect}, #{template['html'].inspect}, #{binding_code})\n"
110
+ end
63
111
  end
64
112
  end
65
113
 
66
114
  code
115
+
67
116
  end
68
117
 
69
118
  def generate_controller_code
@@ -125,5 +174,6 @@ module Volt
125
174
  def generate_initializers_code
126
175
  "\nrequire_tree '#{@component_path}/config/initializers/'\n"
127
176
  end
177
+
128
178
  end
129
179
  end
@@ -79,6 +79,22 @@ module Volt
79
79
  end
80
80
  end
81
81
 
82
+
83
+ def stop_child
84
+ # clear the drb object and kill the child process.
85
+ if @drb_object
86
+ begin
87
+ @drb_object = nil
88
+ DRb.stop_service
89
+ @reader.close
90
+ stop_change_listener
91
+ Process.kill(9, @child_id)
92
+ rescue => e
93
+ puts "Stop Child Error: #{e.inspect}"
94
+ end
95
+ end
96
+ end
97
+
82
98
  # In the even the parent gets killed without at_exit running,
83
99
  # we watch the pipe and close if the pipe gets closed.
84
100
  def watch_for_parent_exit
@@ -129,20 +145,6 @@ module Volt
129
145
  end
130
146
  end
131
147
 
132
- def stop_child
133
- # clear the drb object and kill the child process.
134
- if @drb_object
135
- begin
136
- @drb_object = nil
137
- DRb.stop_service
138
- @reader.close
139
- stop_change_listener
140
- Process.kill(9, @child_id)
141
- rescue => e
142
- puts "Stop Child Error: #{e.inspect}"
143
- end
144
- end
145
- end
146
148
 
147
149
  def reload(changed_files)
148
150
  # only reload the server code if a non-view file was changed
@@ -122,7 +122,9 @@ module Volt
122
122
  # or end of doc before closed binding
123
123
  raise_parse_error("unclosed binding: {#{binding.strip}")
124
124
  else
125
+ #:nocov:
125
126
  fail 'should not reach here'
127
+ #:nocov:
126
128
  end
127
129
  end
128
130
 
@@ -30,6 +30,8 @@ module Volt
30
30
  case first_symbol
31
31
  when 'if'
32
32
  add_if(args)
33
+ when 'unless'
34
+ add_if("!(#{args})")
33
35
  when 'elsif'
34
36
  add_else(args)
35
37
  when 'else'
@@ -16,7 +16,9 @@ module Volt
16
16
 
17
17
  # Gem folders with volt in them
18
18
  # TODO: we should probably qualify this a bit more
19
- app_folders += Gem.loaded_specs.values.reduce([]) { |paths, gem| paths << "#{gem.full_gem_path}/app" if gem.name =~ /volt/; paths }
19
+ app_folders += Gem.loaded_specs.values
20
+ .select {|gem| gem.name =~ /volt/ }
21
+ .map {|gem| "#{gem.full_gem_path}/app" }
20
22
 
21
23
  app_folders.uniq
22
24
  end
@@ -60,7 +62,7 @@ module Volt
60
62
  $LOAD_PATH.unshift(app_folder)
61
63
 
62
64
  # Sort so we get consistent load order across platforms
63
- Dir["#{app_folder}/*/{lib,controllers,models,tasks}/*.rb"].each do |ruby_file|
65
+ Dir["#{app_folder}/*/{controllers,models,tasks}/*.rb"].each do |ruby_file|
64
66
  path = ruby_file.gsub(/^#{app_folder}\//, '')[0..-4]
65
67
  require(path)
66
68
  end
@@ -23,9 +23,11 @@ module Volt
23
23
  Opal.append_path(Volt.root + '/lib')
24
24
 
25
25
  Gem.loaded_specs.values.each do |gem|
26
- path = gem.full_gem_path + '/app'
26
+ ['app', 'lib'].each do |folder|
27
+ path = gem.full_gem_path + "/#{folder}"
27
28
 
28
- Opal.append_path(path) if Dir.exist?(path)
29
+ Opal.append_path(path) if Dir.exist?(path)
30
+ end
29
31
  end
30
32
 
31
33
  # Don't run arity checks in production
@@ -53,7 +53,11 @@ module Volt
53
53
  # Remove ourself from the available channels
54
54
  @@channels.delete(self)
55
55
 
56
- QueryTasks.new(self).close!
56
+ begin
57
+ @@dispatcher.close_channel(self)
58
+ rescue DRb::DRbConnError => e
59
+ # ignore drb read of @@dispatcher error if child has closed
60
+ end
57
61
  else
58
62
  Volt.logger.error("Socket Error: Connection already closed\n#{inspect}")
59
63
  end
File without changes
@@ -29,6 +29,21 @@ module Volt
29
29
  RSpec.configuration.filter_run_excluding type: :feature
30
30
  end
31
31
 
32
+
33
+
34
+ cleanup_db = -> do
35
+ Volt::DataStore.fetch.drop_database
36
+
37
+ # Clear cached for a reset
38
+ $page.instance_variable_set('@store', nil)
39
+ QueryTasks.reset!
40
+ end
41
+
42
+ if RUBY_PLATFORM != 'opal'
43
+ # Call once during setup to clear if we killed the last run
44
+ cleanup_db.call
45
+ end
46
+
32
47
  # Setup the spec collection accessors
33
48
  # RSpec.shared_context "volt collections", {} do
34
49
  RSpec.shared_examples_for 'volt collections', {} do
@@ -42,23 +57,23 @@ module Volt
42
57
  $page.store
43
58
  end
44
59
 
45
- def cleanup_after
46
- Volt::DataStore.fetch.drop_database
47
-
48
- $page.instance_variable_set('@store', nil)
49
- end
50
60
 
51
61
  if RUBY_PLATFORM != 'opal'
52
62
  after do
53
63
  if @__store_accessed
54
64
  # Clear the database after each spec where we use store
55
- cleanup_after
65
+ cleanup_db.call
56
66
  end
57
67
  end
58
68
 
69
+ # Assume store is accessed in capyabara specs
70
+ before(:context, {type: :feature}) do
71
+ @__store_accessed = true
72
+ end
73
+
59
74
  # Cleanup after integration tests also.
60
- after(:example, {type: :feature}) do
61
- cleanup_after
75
+ before(:example, {type: :feature}) do
76
+ @__store_accessed = true
62
77
  end
63
78
  end
64
79
  end
@@ -89,5 +89,9 @@ module Volt
89
89
 
90
90
  false
91
91
  end
92
+
93
+ def close_channel(channel)
94
+ QueryTasks.new(channel).close!
95
+ end
92
96
  end
93
97
  end
@@ -57,6 +57,9 @@ class Promise
57
57
  end
58
58
 
59
59
  if error
60
+ err_str = "Exception in Promise at .sync: #{error.inspect}"
61
+ err_str += error.backtrace.join("\n")
62
+ Volt.logger.error(err_str)
60
63
  fail error
61
64
  else
62
65
  return result
data/lib/volt/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Volt
2
2
  module Version
3
- STRING = '0.9.2'
3
+ STRING = '0.9.3.pre1'
4
4
  end
5
5
  end
@@ -13,8 +13,13 @@ gem 'volt-bootstrap_jumbotron_theme'
13
13
  gem 'volt-fields'
14
14
  gem 'volt-user_templates'
15
15
 
16
+ # use mongo for data store while testing
17
+ gem 'volt-mongo'
18
+
16
19
  gem 'opal'
17
20
 
21
+ gem 'concurrent-ruby-ext'
22
+
18
23
  # Server for MRI
19
24
  platform :mri do
20
25
  gem 'thin', '~> 1.6.0'
@@ -2,6 +2,7 @@
2
2
 
3
3
  client '/bindings/{{ route_test }}', action: 'bindings'
4
4
  client '/bindings', action: 'bindings'
5
+ client '/form', action: 'form'
5
6
  client '/store', action: 'store'
6
7
  client '/cookie_test', action: 'cookie_test'
7
8
  client '/flash', action: 'flash'
@@ -10,6 +10,12 @@ module Main
10
10
  a[{}] = 5
11
11
  end
12
12
 
13
+ def form_ready
14
+ `$('#title').html('form_ready')`
15
+ `$('select#location').val('AL').change()` # have to trigger manually as this is not user initiaized action
16
+ `$('input#name').val('Test')`
17
+ end
18
+
13
19
  def flash_notice
14
20
  flash._notices << 'A notice message'
15
21
  end
@@ -44,6 +50,10 @@ module Main
44
50
  '<button id="examplebutton">Example Button</button>'
45
51
  end
46
52
 
53
+ def set_show(value)
54
+ page._show = value
55
+ end
56
+
47
57
  private
48
58
 
49
59
  # the main template contains a #template binding that shows another