volt 0.8.21 → 0.8.22.beta1

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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +15 -10
  3. data/CHANGELOG.md +8 -0
  4. data/Gemfile +7 -33
  5. data/Readme.md +2 -0
  6. data/VERSION +1 -1
  7. data/app/volt/assets/js/{setImmediate.js → volt_js_polyfills.js} +14 -0
  8. data/app/volt/models/user.rb +21 -26
  9. data/app/volt/tasks/live_query/data_store.rb +4 -0
  10. data/app/volt/tasks/store_tasks.rb +8 -11
  11. data/app/volt/tasks/user_tasks.rb +18 -17
  12. data/lib/volt.rb +7 -1
  13. data/lib/volt/cli.rb +2 -0
  14. data/lib/volt/config.rb +69 -29
  15. data/lib/volt/extra_core/object.rb +8 -0
  16. data/lib/volt/models/model_hash_behaviour.rb +26 -6
  17. data/lib/volt/models/persistors/model_store.rb +1 -2
  18. data/lib/volt/models/validations.rb +18 -9
  19. data/lib/volt/models/validators/length_validator.rb +1 -1
  20. data/lib/volt/models/validators/presence_validator.rb +1 -1
  21. data/lib/volt/models/validators/unique_validator.rb +1 -1
  22. data/lib/volt/page/bindings/template_binding.rb +2 -1
  23. data/lib/volt/page/page.rb +7 -2
  24. data/lib/volt/page/sub_context.rb +8 -4
  25. data/lib/volt/page/targets/base_section.rb +1 -1
  26. data/lib/volt/page/targets/dom_template.rb +1 -1
  27. data/lib/volt/server.rb +3 -2
  28. data/lib/volt/server/html_parser/view_scope.rb +1 -0
  29. data/lib/volt/server/rack/component_code.rb +8 -1
  30. data/lib/volt/server/rack/component_paths.rb +2 -1
  31. data/lib/volt/server/rack/quiet_common_logger.rb +34 -0
  32. data/lib/volt/spec/setup.rb +30 -1
  33. data/lib/volt/utils/generic_pool.rb +4 -0
  34. data/lib/volt/volt/users.rb +18 -0
  35. data/spec/apps/kitchen_sink/Gemfile +3 -0
  36. data/spec/apps/kitchen_sink/app/main/config/dependencies.rb +6 -0
  37. data/spec/apps/kitchen_sink/app/main/config/routes.rb +5 -1
  38. data/spec/apps/kitchen_sink/app/main/models/user.rb +2 -0
  39. data/spec/apps/kitchen_sink/app/main/views/main/main.html +1 -1
  40. data/spec/apps/kitchen_sink/{public → config/base}/index.html +0 -0
  41. data/spec/integration/bindings_spec.rb +1 -1
  42. data/spec/integration/cookies_spec.rb +1 -1
  43. data/spec/integration/flash_spec.rb +3 -2
  44. data/spec/integration/list_spec.rb +1 -1
  45. data/spec/integration/templates_spec.rb +1 -1
  46. data/spec/integration/url_spec.rb +1 -1
  47. data/spec/integration/user_spec.rb +60 -0
  48. data/spec/models/model_spec.rb +0 -1
  49. data/spec/page/bindings/template_binding_spec.rb +0 -4
  50. data/spec/server/rack/asset_files_spec.rb +1 -1
  51. data/spec/spec_helper.rb +5 -0
  52. data/templates/component/tasks/.empty_directory +0 -0
  53. data/templates/newgem/app/newgem/views/main/{main.html → index.html} +0 -0
  54. data/templates/project/Gemfile.tt +0 -11
  55. data/templates/project/app/main/config/routes.rb +5 -0
  56. data/templates/project/app/main/tasks/.empty_directory +0 -0
  57. data/templates/project/app/main/views/main/main.html.tt +1 -0
  58. data/templates/project/config/app.rb.tt +8 -3
  59. data/volt.gemspec +4 -6
  60. metadata +58 -12
  61. data/spec/apps/kitchen_sink/app/main/controllers/users_test_controller.rb +0 -27
  62. data/spec/apps/kitchen_sink/app/main/views/users_test/index.html +0 -22
@@ -21,9 +21,19 @@ module Volt
21
21
  @attributes.size
22
22
  end
23
23
 
24
+ # Returns all of the keys, skipping over nil models
25
+ # TODO: We should store nil-models elsewhere so we don't have
26
+ # to skip.
24
27
  def keys
25
28
  @size_dep.depend
26
- @attributes.keys
29
+
30
+ keys = []
31
+
32
+ each_pair do |k,v|
33
+ keys << k
34
+ end
35
+
36
+ keys
27
37
  end
28
38
 
29
39
  def nil?
@@ -44,20 +54,30 @@ module Volt
44
54
  end
45
55
 
46
56
  def clear
47
- @attributes.each_pair do |key, value|
48
- @deps.changed!(key)
57
+ @attributes.each_pair do |key, _|
58
+ delete(key)
49
59
  end
50
60
 
51
- @attributes.clear
61
+ # @attributes.clear
52
62
  @size_dep.changed!
53
-
54
- @persistor.removed(nil) if @persistor
63
+ #
64
+ # @persistor.removed(nil) if @persistor
55
65
  end
56
66
 
57
67
  def each_with_object(*args, &block)
58
68
  (@attributes || {}).each_with_object(*args, &block)
59
69
  end
60
70
 
71
+ def each_pair
72
+ @attributes.each_pair do |k,v|
73
+ yield(k,v) unless v.is_a?(Model) && v.nil?
74
+ end
75
+ end
76
+
77
+ def key?(key)
78
+ @attributes && @attributes.key?(key)
79
+ end
80
+
61
81
  # Convert the model to a hash all of the way down.
62
82
  def to_h
63
83
  @size_dep.depend
@@ -91,7 +91,6 @@ module Volt
91
91
 
92
92
  queue_client_save
93
93
  else
94
- puts "Save to DB"
95
94
  errors = save_to_db!(self_attributes)
96
95
  if errors.size == 0
97
96
  promise.resolve(nil)
@@ -204,7 +203,7 @@ module Volt
204
203
  end
205
204
  end
206
205
 
207
- puts "Update Collection: #{collection.inspect} - #{values.inspect} -- #{Thread.current['in_channel'].inspect}"
206
+ # puts "Update Collection: #{collection.inspect} - #{values.inspect} -- #{Thread.current['in_channel'].inspect}"
208
207
  QueryTasks.live_query_pool.updated_collection(collection.to_s, Thread.current['in_channel'])
209
208
  return {}
210
209
  end
@@ -84,14 +84,22 @@ module Volt
84
84
  end
85
85
  end
86
86
 
87
- errors = run_validations(errors, merge, marked_only)
87
+ # Get the previous model from the buffer
88
+ save_to = options[:save_to]
89
+ if save_to && save_to.is_a?(Volt::Model)
90
+ old_model = save_to
91
+ else
92
+ old_model = nil
93
+ end
94
+
95
+ errors = run_validations(errors, merge, marked_only, old_model)
88
96
 
89
97
  # See if any server errors are in place and merge them in if they are
90
98
  if Volt.client?
91
99
  errors = merge.call(server_errors.to_h)
92
100
  end
93
101
 
94
- errors = run_custom_validations(errors, merge)
102
+ errors = run_custom_validations(errors, merge, old_model)
95
103
 
96
104
  errors
97
105
  end
@@ -99,7 +107,7 @@ module Volt
99
107
  private
100
108
 
101
109
  # Runs through each of the normal validations.
102
- def run_validations(errors, merge, marked_only)
110
+ def run_validations(errors, merge, marked_only, old_model)
103
111
  validations = self.class.validations
104
112
  if validations
105
113
 
@@ -114,7 +122,7 @@ module Volt
114
122
  klass = validation_class(validation, args)
115
123
 
116
124
  if klass
117
- validate_with(merge, klass, field_name, args)
125
+ validate_with(merge, klass, old_model, field_name, args)
118
126
  else
119
127
  fail "validation type #{validation} is not specified."
120
128
  end
@@ -125,13 +133,14 @@ module Volt
125
133
  return errors
126
134
  end
127
135
 
128
- def run_custom_validations(errors, merge)
136
+ def run_custom_validations(errors, merge, old_model)
129
137
  # Call all of the custom validations
130
138
  custom_validations = self.class.custom_validations
131
139
  if custom_validations
132
140
  custom_validations.each do |custom_validation|
133
- # Run the validator in the context of the model
134
- result = instance_eval(&custom_validation)
141
+ # Run the validator in the context of the model, passes in
142
+ # the old_model as an argument
143
+ result = instance_eval(old_model, &custom_validation)
135
144
  if result
136
145
  errors = merge.call(result)
137
146
  end
@@ -143,8 +152,8 @@ module Volt
143
152
 
144
153
 
145
154
  # calls the validate method on the class, passing the right arguments.
146
- def validate_with(merge, klass, field_name, args)
147
- merge.call(klass.validate(self, field_name, args))
155
+ def validate_with(merge, klass, old_model, field_name, args)
156
+ merge.call(klass.validate(self, old_model, field_name, args))
148
157
  end
149
158
 
150
159
  def validation_class(validation, args)
@@ -1,6 +1,6 @@
1
1
  module Volt
2
2
  class LengthValidator
3
- def self.validate(model, field_name, args)
3
+ def self.validate(model, old_model, field_name, args)
4
4
  errors = {}
5
5
  value = model.read_attribute(field_name)
6
6
 
@@ -1,6 +1,6 @@
1
1
  module Volt
2
2
  class PresenceValidator
3
- def self.validate(model, field_name, args)
3
+ def self.validate(model, old_model, field_name, args)
4
4
  errors = {}
5
5
  value = model.read_attribute(field_name)
6
6
  if !value || value.blank?
@@ -1,6 +1,6 @@
1
1
  module Volt
2
2
  class UniqueValidator
3
- def self.validate(model, field_name, args)
3
+ def self.validate(model, old_model, field_name, args)
4
4
  errors = {}
5
5
 
6
6
  if RUBY_PLATFORM != 'opal'
@@ -161,7 +161,8 @@ module Volt
161
161
 
162
162
  # The context for templates can be either a controller, or the original context.
163
163
  def render_template(full_path, controller_path)
164
- args = @arguments ? [SubContext.new(@arguments, nil, true)] : []
164
+ # If arguments is nil, then an blank SubContext will be created
165
+ args = [SubContext.new(@arguments, nil, true)]
165
166
 
166
167
  @controller = nil
167
168
 
@@ -140,8 +140,13 @@ module Volt
140
140
  attr_reader :events
141
141
 
142
142
  def add_model(model_name)
143
- model_name = model_name.camelize.to_sym
144
- @model_classes[model_name] = Object.const_get(model_name)
143
+ begin
144
+ model_name = model_name.camelize.to_sym
145
+ @model_classes[model_name] = Object.const_get(model_name)
146
+ rescue NameError => e
147
+ # Handle if the model is user (Volt's provided user model is scoped under Volt::)
148
+ raise unless model_name == :User
149
+ end
145
150
  end
146
151
 
147
152
  def add_template(name, template, bindings)
@@ -8,19 +8,23 @@ module Volt
8
8
  class SubContext
9
9
  attr_reader :locals
10
10
 
11
- def initialize(locals, context = nil, return_nils = false)
12
- @locals = locals.stringify_keys
11
+ def initialize(locals=nil, context = nil, return_nils = false)
12
+ @locals = locals.stringify_keys if locals
13
13
  @context = context
14
14
  @return_nils = return_nils
15
15
  end
16
16
 
17
17
  def respond_to?(method_name)
18
- !!(@locals[method_name.to_s] || (@context && @context.respond_to?(method_name)))
18
+ !!((@locals && @locals[method_name.to_s]) || (@context && @context.respond_to?(method_name)))
19
+ end
20
+
21
+ def inspect
22
+ "#<SubContext #{@locals.inspect} context:#{@context.inspect}>"
19
23
  end
20
24
 
21
25
  def method_missing(method_name, *args, &block)
22
26
  method_name = method_name.to_s
23
- if @locals.key?(method_name)
27
+ if @locals && @locals.key?(method_name)
24
28
  obj = @locals[method_name]
25
29
 
26
30
  # TODORW: Might get a normal proc, flag internal procs
@@ -29,7 +29,7 @@ module Volt
29
29
  html = template['html']
30
30
  bindings = template['bindings']
31
31
  else
32
- html = "<div>-- &lt; missing template #{template_name.inspect.gsub('<', '&lt;').gsub('>', '&gt;')} &gt; --</div>"
32
+ html = "<div>-- &lt; missing template #{template_name.inspect.html_inspect}, make sure it's component is included in dependencies.rb &gt; --</div>"
33
33
  bindings = {}
34
34
  end
35
35
 
@@ -16,7 +16,7 @@ module Volt
16
16
  html = template['html']
17
17
  @bindings = template['bindings']
18
18
  else
19
- html = "<div>-- &lt; missing template #{template_name.inspect.gsub('<', '&lt;').gsub('>', '&gt;')} &gt; --</div>"
19
+ html = "<div>-- &lt; missing template #{template_name.inspect.html_inspect}, make sure it's component is included in dependencies.rb &gt; --</div>"
20
20
  @bindings = {}
21
21
  end
22
22
 
data/lib/volt/server.rb CHANGED
@@ -8,11 +8,11 @@ else
8
8
  end
9
9
 
10
10
  require 'rack'
11
+ require 'sass'
11
12
  if RUBY_PLATFORM != 'java'
12
13
  require 'rack/sockjs'
13
14
  require 'eventmachine'
14
15
  end
15
- require 'sass'
16
16
  require 'sprockets-sass'
17
17
  require 'listen'
18
18
 
@@ -27,6 +27,7 @@ end
27
27
  require 'volt/server/rack/component_paths'
28
28
  require 'volt/server/rack/index_files'
29
29
  require 'volt/server/rack/opal_files'
30
+ require 'volt/server/rack/quiet_common_logger'
30
31
  require 'volt/page/page'
31
32
 
32
33
  module Rack
@@ -94,7 +95,7 @@ module Volt
94
95
  @app.use Rack::ConditionalGet
95
96
  @app.use Rack::ETag
96
97
 
97
- @app.use Rack::CommonLogger
98
+ @app.use QuietCommonLogger
98
99
  @app.use Rack::ShowExceptions
99
100
 
100
101
  component_paths = @component_paths
@@ -113,6 +113,7 @@ module Volt
113
113
 
114
114
  data_hash = []
115
115
  attributes.each_pair do |name, value|
116
+ name = name.tr('-', '_')
116
117
  parts, binding_count = binding_parts_and_count(value)
117
118
 
118
119
  # if this attribute has bindings
@@ -14,7 +14,8 @@ module Volt
14
14
 
15
15
  # The client argument is for if this code is being generated for the client
16
16
  def code(client=true)
17
- code = ''
17
+ # Start with config code
18
+ code = generate_config_code
18
19
 
19
20
  asset_files = AssetFiles.new(@component_name, @component_paths)
20
21
  asset_files.component_paths.each do |component_path, component_name|
@@ -24,5 +25,11 @@ module Volt
24
25
 
25
26
  code
26
27
  end
28
+
29
+
30
+ def generate_config_code
31
+ "\nVolt.setup_client_config(#{Volt.config.public.to_h.inspect})\n"
32
+ end
33
+
27
34
  end
28
35
  end
@@ -18,7 +18,7 @@ module Volt
18
18
  # TODO: we should probably qualify this a bit more
19
19
  app_folders += Gem.loaded_specs.values.map(&:full_gem_path).reject { |g| g !~ /volt/ }.map { |f| f + '/app' }
20
20
 
21
- app_folders
21
+ app_folders.uniq
22
22
  end
23
23
 
24
24
  # Yield each app folder and return a flattened array with
@@ -71,6 +71,7 @@ module Volt
71
71
  # Add models to page
72
72
  Dir["#{app_folder}/*/models/*.rb"].each do |ruby_file|
73
73
  class_name = File.basename(ruby_file).gsub(/[.]rb$/, '')
74
+
74
75
  $page.add_model(class_name)
75
76
  end
76
77
  end
@@ -0,0 +1,34 @@
1
+ class QuietCommonLogger < Rack::CommonLogger
2
+ include Rack
3
+
4
+ @@ignore_extensions = %w(png jpg jpeg ico gif woff tff svg eot css js)
5
+
6
+ def call(env)
7
+ path = env['REQUEST_PATH']
8
+ began_at = Time.now
9
+ status, header, body = @app.call(env)
10
+ header = Utils::HeaderHash.new(header)
11
+ base = ::File.basename(path)
12
+ if base.index('.')
13
+ ext = base.split('.').last
14
+ else
15
+ ext = nil
16
+ end
17
+
18
+ body = BodyProxy.new(body) do
19
+
20
+ # Don't log on ignored extensions
21
+ unless @@ignore_extensions.include?(ext)
22
+ log(env, status, header, began_at)
23
+ end
24
+ end
25
+
26
+ # Because of web sockets, the initial request doesn't finish, so we
27
+ # can just trigger it now.
28
+ if !ext && !path.start_with?('/channel')
29
+ log(env, status, header, began_at)
30
+ end
31
+
32
+ [status, header, body]
33
+ end
34
+ end
@@ -4,6 +4,7 @@ module Volt
4
4
  require 'volt'
5
5
  else
6
6
  ENV['SERVER'] = 'true'
7
+ ENV['VOLT_ENV'] = 'test'
7
8
 
8
9
  if ENV['BROWSER']
9
10
  require 'capybara'
@@ -16,7 +17,7 @@ module Volt
16
17
  require 'volt/boot'
17
18
 
18
19
  # Require in app
19
- Volt.boot(Dir.pwd)
20
+ Volt.boot(app_path)
20
21
 
21
22
  if ENV['BROWSER']
22
23
  require 'volt/server'
@@ -53,6 +54,34 @@ module Volt
53
54
  Capybara::Selenium::Driver.new(app, browser: :safari)
54
55
  end
55
56
  Capybara.default_driver = :safari
57
+ elsif ENV['BROWSER'] == 'sauce'
58
+ require "sauce"
59
+ require "sauce/capybara"
60
+
61
+ Sauce.config do |c|
62
+ if ENV['OS']
63
+ # Use a specifc OS, BROWSER, VERSION combo (for travis)
64
+ c[:browsers] = [
65
+ [ENV['OS'], ENV['USE_BROWSER'], ENV['VERSION']]
66
+ ]
67
+ else
68
+ # Run all
69
+ c[:browsers] = [
70
+ # ["Windows 7", "Chrome", "30"],
71
+ # ["Windows 8", "Firefox", "28"],
72
+ ["Windows 8.1", "Internet Explorer", "11"],
73
+ ["Windows 8.0", "Internet Explorer", "10"],
74
+ ["Windows 7.0", "Internet Explorer", "9"],
75
+ # ["OSX 10.9", "iPhone", "8.1"],
76
+ # ["OSX 10.8", "Safari", "6"],
77
+ # ["Linux", "Chrome", "26"]
78
+ ]
79
+ end
80
+ c[:start_local_application] = false
81
+ end
82
+
83
+ Capybara.default_driver = :sauce
84
+ Capybara.javascript_driver = :sauce
56
85
  end
57
86
  end
58
87
  end
@@ -16,6 +16,10 @@ module Volt
16
16
  @pool = {}
17
17
  end
18
18
 
19
+ def clear
20
+ @pool = {}
21
+ end
22
+
19
23
  def lookup(*args, &block)
20
24
  section = @pool
21
25
 
@@ -0,0 +1,18 @@
1
+ module Volt
2
+
3
+ # Login the user, return a promise for success
4
+ def self.login(username, password)
5
+ UserTasks.login(username, password).then do |result|
6
+
7
+ # Assign the user_id cookie for the user
8
+ $page.cookies._user_id = result
9
+
10
+ # Pass nil back
11
+ nil
12
+ end
13
+ end
14
+
15
+ def self.logout
16
+ $page.cookies.delete(:user_id)
17
+ end
18
+ end