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.
- checksums.yaml +4 -4
- data/.travis.yml +15 -10
- data/CHANGELOG.md +8 -0
- data/Gemfile +7 -33
- data/Readme.md +2 -0
- data/VERSION +1 -1
- data/app/volt/assets/js/{setImmediate.js → volt_js_polyfills.js} +14 -0
- data/app/volt/models/user.rb +21 -26
- data/app/volt/tasks/live_query/data_store.rb +4 -0
- data/app/volt/tasks/store_tasks.rb +8 -11
- data/app/volt/tasks/user_tasks.rb +18 -17
- data/lib/volt.rb +7 -1
- data/lib/volt/cli.rb +2 -0
- data/lib/volt/config.rb +69 -29
- data/lib/volt/extra_core/object.rb +8 -0
- data/lib/volt/models/model_hash_behaviour.rb +26 -6
- data/lib/volt/models/persistors/model_store.rb +1 -2
- data/lib/volt/models/validations.rb +18 -9
- data/lib/volt/models/validators/length_validator.rb +1 -1
- data/lib/volt/models/validators/presence_validator.rb +1 -1
- data/lib/volt/models/validators/unique_validator.rb +1 -1
- data/lib/volt/page/bindings/template_binding.rb +2 -1
- data/lib/volt/page/page.rb +7 -2
- data/lib/volt/page/sub_context.rb +8 -4
- data/lib/volt/page/targets/base_section.rb +1 -1
- data/lib/volt/page/targets/dom_template.rb +1 -1
- data/lib/volt/server.rb +3 -2
- data/lib/volt/server/html_parser/view_scope.rb +1 -0
- data/lib/volt/server/rack/component_code.rb +8 -1
- data/lib/volt/server/rack/component_paths.rb +2 -1
- data/lib/volt/server/rack/quiet_common_logger.rb +34 -0
- data/lib/volt/spec/setup.rb +30 -1
- data/lib/volt/utils/generic_pool.rb +4 -0
- data/lib/volt/volt/users.rb +18 -0
- data/spec/apps/kitchen_sink/Gemfile +3 -0
- data/spec/apps/kitchen_sink/app/main/config/dependencies.rb +6 -0
- data/spec/apps/kitchen_sink/app/main/config/routes.rb +5 -1
- data/spec/apps/kitchen_sink/app/main/models/user.rb +2 -0
- data/spec/apps/kitchen_sink/app/main/views/main/main.html +1 -1
- data/spec/apps/kitchen_sink/{public → config/base}/index.html +0 -0
- data/spec/integration/bindings_spec.rb +1 -1
- data/spec/integration/cookies_spec.rb +1 -1
- data/spec/integration/flash_spec.rb +3 -2
- data/spec/integration/list_spec.rb +1 -1
- data/spec/integration/templates_spec.rb +1 -1
- data/spec/integration/url_spec.rb +1 -1
- data/spec/integration/user_spec.rb +60 -0
- data/spec/models/model_spec.rb +0 -1
- data/spec/page/bindings/template_binding_spec.rb +0 -4
- data/spec/server/rack/asset_files_spec.rb +1 -1
- data/spec/spec_helper.rb +5 -0
- data/templates/component/tasks/.empty_directory +0 -0
- data/templates/newgem/app/newgem/views/main/{main.html → index.html} +0 -0
- data/templates/project/Gemfile.tt +0 -11
- data/templates/project/app/main/config/routes.rb +5 -0
- data/templates/project/app/main/tasks/.empty_directory +0 -0
- data/templates/project/app/main/views/main/main.html.tt +1 -0
- data/templates/project/config/app.rb.tt +8 -3
- data/volt.gemspec +4 -6
- metadata +58 -12
- data/spec/apps/kitchen_sink/app/main/controllers/users_test_controller.rb +0 -27
- 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
|
-
|
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,
|
48
|
-
|
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
|
-
|
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
|
-
|
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)
|
@@ -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
|
-
|
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
|
|
data/lib/volt/page/page.rb
CHANGED
@@ -140,8 +140,13 @@ module Volt
|
|
140
140
|
attr_reader :events
|
141
141
|
|
142
142
|
def add_model(model_name)
|
143
|
-
|
144
|
-
|
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>-- < missing template #{template_name.inspect.
|
32
|
+
html = "<div>-- < missing template #{template_name.inspect.html_inspect}, make sure it's component is included in dependencies.rb > --</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>-- < missing template #{template_name.inspect.
|
19
|
+
html = "<div>-- < missing template #{template_name.inspect.html_inspect}, make sure it's component is included in dependencies.rb > --</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
|
98
|
+
@app.use QuietCommonLogger
|
98
99
|
@app.use Rack::ShowExceptions
|
99
100
|
|
100
101
|
component_paths = @component_paths
|
@@ -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
|
-
|
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
|
data/lib/volt/spec/setup.rb
CHANGED
@@ -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(
|
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
|
@@ -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
|