volt 0.9.2 → 0.9.3.pre1

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