volt 0.8.21 → 0.8.22.beta1

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