volt 0.8.24 → 0.8.26.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -2
- data/Rakefile +1 -2
- data/VERSION +1 -1
- data/app/volt/models/user.rb +1 -0
- data/lib/volt/cli.rb +4 -4
- data/lib/volt/models/array_model.rb +5 -0
- data/lib/volt/models/field_helpers.rb +37 -0
- data/lib/volt/models/model.rb +2 -0
- data/lib/volt/models/validations.rb +3 -1
- data/lib/volt/models/validators/email_validator.rb +40 -0
- data/lib/volt/models/validators/phone_number_validator.rb +40 -0
- data/lib/volt/page/bindings/each_binding.rb +13 -10
- data/lib/volt/page/tasks.rb +1 -2
- data/lib/volt/server/html_parser/each_scope.rb +9 -6
- data/lib/volt/server/html_parser/view_scope.rb +5 -3
- data/spec/apps/kitchen_sink/app/main/views/todos/index.html +2 -1
- data/spec/models/field_helpers_spec.rb +29 -0
- data/spec/models/validations_spec.rb +40 -7
- data/spec/models/validators/email_validator_spec.rb +120 -0
- data/spec/models/validators/phone_number_validator_spec.rb +146 -0
- data/spec/server/html_parser/view_parser_spec.rb +1 -1
- metadata +13 -7
- data/docs/GETTING_STARTED.md +0 -28
- data/docs/JAVASCRIPT_COMPONENTS.md +0 -2
- data/docs/WHY.md +0 -39
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d71fdd9ba18f67e9349026c51395e553bb34143e
|
4
|
+
data.tar.gz: 00ac6d296fb6a2e32d7d8326740e378ea5178545
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 774f656f5a4c365d9f37106a0204efcff36b2b72871d7f4b3bd5334b75f16ec3195b82cdecc5f23781efd76f896d130ae205a5e3f0691bd01888778cc567f12a
|
7
|
+
data.tar.gz: c50312a4099bd67fcc60684fca2353b0929c0878cce00e61bef809257ee3613e14796369ea47f18755d24aea59d648da28de32a4974c21a6b70b064079c80cba
|
data/CHANGELOG.md
CHANGED
@@ -1,9 +1,17 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
-
## 0.8.
|
3
|
+
## 0.8.25 - ...
|
4
|
+
### Added
|
5
|
+
- Added email validator
|
6
|
+
- each_with_index is now supported in views and the ```index``` value is no longer provided by default.
|
7
|
+
|
8
|
+
|
9
|
+
## 0.8.24 - 2014-12-05
|
10
|
+
### Added
|
4
11
|
- Fix bug with validation inheritance
|
12
|
+
- Fixed issue with controller loading.
|
5
13
|
|
6
|
-
## 0.8.
|
14
|
+
## 0.8.23 - 2014-11-30
|
7
15
|
### Added
|
8
16
|
- Added url_for and url_with to controllers. (See docs under Controllers)
|
9
17
|
|
data/Rakefile
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.8.
|
1
|
+
0.8.26.beta1
|
data/app/volt/models/user.rb
CHANGED
data/lib/volt/cli.rb
CHANGED
@@ -32,7 +32,8 @@ module Volt
|
|
32
32
|
end
|
33
33
|
|
34
34
|
desc 'server', 'run the server on the project in the current directory'
|
35
|
-
method_option :port, type: :string, aliases: '-p', banner: '
|
35
|
+
method_option :port, type: :string, aliases: '-p', banner: 'the port the server should run on'
|
36
|
+
method_option :bind, type: :string, aliases: '-b', banner: 'the ip the server should bind to'
|
36
37
|
|
37
38
|
def server
|
38
39
|
if RUBY_PLATFORM == 'java'
|
@@ -61,9 +62,8 @@ module Volt
|
|
61
62
|
ENV['SERVER'] = 'true'
|
62
63
|
args = ['start', '--threaded', '--max-persistent-conns', '300']
|
63
64
|
args += ['--max-conns', '400'] unless Gem.win_platform?
|
64
|
-
if options[:port]
|
65
|
-
|
66
|
-
end
|
65
|
+
args += ['-p', options[:port].to_s] if options[:port]
|
66
|
+
args += ['-b', options[:bind].to_s] if options[:bind]
|
67
67
|
|
68
68
|
Thin::Runner.new(args).run!
|
69
69
|
end
|
@@ -57,6 +57,8 @@ module Volt
|
|
57
57
|
|
58
58
|
# Make sure it gets wrapped
|
59
59
|
def <<(model)
|
60
|
+
load_data
|
61
|
+
|
60
62
|
if model.is_a?(Model)
|
61
63
|
# Set the new path
|
62
64
|
model.options = @options.merge(path: @options[:path] + [:[]])
|
@@ -121,6 +123,9 @@ module Volt
|
|
121
123
|
end
|
122
124
|
|
123
125
|
def inspect
|
126
|
+
# Just load the data on the server making it easier to work with
|
127
|
+
load_data if Volt.server?
|
128
|
+
|
124
129
|
if @persistor && @persistor.is_a?(Persistors::ArrayStore) && state == :not_loaded
|
125
130
|
# Show a special message letting users know it is not loaded yet.
|
126
131
|
"#<#{self.class}:not loaded, access with [] or size to load>"
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# Provides a method to setup a field on a model.
|
2
|
+
module FieldHelpers
|
3
|
+
class InvalidFieldClass < RuntimeError ; end
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
# field lets you declare your fields instead of using the underscore syntax.
|
7
|
+
# An optional class restriction can be passed in.
|
8
|
+
def field(name, klass=nil)
|
9
|
+
if klass && ![String, Numeric].include?(klass)
|
10
|
+
raise FieldHelpers::InvalidFieldClass, "valid field types is currently limited to String or Numeric"
|
11
|
+
end
|
12
|
+
|
13
|
+
define_method(name) do
|
14
|
+
read_attribute(name)
|
15
|
+
end
|
16
|
+
|
17
|
+
define_method(:"#{name}=") do |val|
|
18
|
+
# Check if the value assigned matches the class restriction
|
19
|
+
if klass
|
20
|
+
# Cast to the right type
|
21
|
+
if klass == String
|
22
|
+
val = val.to_s
|
23
|
+
elsif klass == Numeric
|
24
|
+
val = val.to_f
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
assign_attribute(name, val)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.included(base)
|
34
|
+
base.send :extend, ClassMethods
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
data/lib/volt/models/model.rb
CHANGED
@@ -5,6 +5,7 @@ require 'volt/models/model_hash_behaviour'
|
|
5
5
|
require 'volt/models/validations'
|
6
6
|
require 'volt/models/model_state'
|
7
7
|
require 'volt/models/buffer'
|
8
|
+
require 'volt/models/field_helpers'
|
8
9
|
require 'volt/reactive/reactive_hash'
|
9
10
|
|
10
11
|
module Volt
|
@@ -18,6 +19,7 @@ module Volt
|
|
18
19
|
include Validations
|
19
20
|
include ModelState
|
20
21
|
include Buffer
|
22
|
+
include FieldHelpers
|
21
23
|
|
22
24
|
attr_reader :attributes
|
23
25
|
attr_reader :parent, :path, :persistor, :options
|
@@ -1,8 +1,10 @@
|
|
1
1
|
# require 'volt/models/validations/errors'
|
2
|
+
require 'volt/models/validators/email_validator'
|
2
3
|
require 'volt/models/validators/length_validator'
|
4
|
+
require 'volt/models/validators/numericality_validator'
|
5
|
+
require 'volt/models/validators/phone_number_validator'
|
3
6
|
require 'volt/models/validators/presence_validator'
|
4
7
|
require 'volt/models/validators/unique_validator'
|
5
|
-
require 'volt/models/validators/numericality_validator'
|
6
8
|
|
7
9
|
module Volt
|
8
10
|
# Include in any class to get validation logic
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Volt
|
2
|
+
class EmailValidator
|
3
|
+
DEFAULT_REGEX = /^([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})$/i
|
4
|
+
ERROR_MESSAGE = 'must be an email address'
|
5
|
+
|
6
|
+
def self.validate(model, old_model, field_name, options)
|
7
|
+
new(model, field_name, options).errors
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(model, field_name, options)
|
11
|
+
@value = model.read_attribute field_name
|
12
|
+
|
13
|
+
case options
|
14
|
+
when Hash, true, false
|
15
|
+
configure options
|
16
|
+
else
|
17
|
+
fail 'arguments can only be a Boolean or a Hash'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def valid?
|
22
|
+
return false unless @value.is_a? String
|
23
|
+
|
24
|
+
!!@value.match(@custom_regex || DEFAULT_REGEX)
|
25
|
+
end
|
26
|
+
|
27
|
+
def errors
|
28
|
+
valid? ? {} : { email: [ @custom_message || ERROR_MESSAGE ] }
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def configure(options)
|
34
|
+
return unless options.is_a? Hash
|
35
|
+
|
36
|
+
@custom_message = options.fetch(:error_message) { nil }
|
37
|
+
@custom_regex = options.fetch(:with) { nil }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Volt
|
2
|
+
class PhoneNumberValidator
|
3
|
+
DEFAULT_REGEX = /^(\+?\d{1,2}[\.\-\ ]?\d{3}|\(\d{3}\)|\d{3})[\.\-\ ]?\d{3,4}[\.\-\ ]?\d{4}$/
|
4
|
+
ERROR_MESSAGE = 'must be a phone number with area or country code'
|
5
|
+
|
6
|
+
def self.validate(model, old_model, field_name, options)
|
7
|
+
new(model, field_name, options).errors
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(model, field_name, options)
|
11
|
+
@value = model.read_attribute field_name
|
12
|
+
|
13
|
+
case options
|
14
|
+
when Hash, true, false
|
15
|
+
configure options
|
16
|
+
else
|
17
|
+
fail 'arguments can only be a Boolean or a Hash'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def valid?
|
22
|
+
return false unless @value.is_a? String
|
23
|
+
|
24
|
+
!!@value.match(@custom_regex || DEFAULT_REGEX)
|
25
|
+
end
|
26
|
+
|
27
|
+
def errors
|
28
|
+
valid? ? {} : { phone_number: [ @custom_message || ERROR_MESSAGE ] }
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def configure(options)
|
34
|
+
return unless options.is_a? Hash
|
35
|
+
|
36
|
+
@custom_message = options.fetch(:error_message) { nil }
|
37
|
+
@custom_regex = options.fetch(:with) { nil }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -2,10 +2,11 @@ require 'volt/page/bindings/base_binding'
|
|
2
2
|
|
3
3
|
module Volt
|
4
4
|
class EachBinding < BaseBinding
|
5
|
-
def initialize(page, target, context, binding_name, getter, variable_name, template_name)
|
5
|
+
def initialize(page, target, context, binding_name, getter, variable_name, index_name, template_name)
|
6
6
|
super(page, target, context, binding_name)
|
7
7
|
|
8
8
|
@item_name = variable_name
|
9
|
+
@index_name = index_name
|
9
10
|
@template_name = template_name
|
10
11
|
|
11
12
|
@templates = []
|
@@ -28,7 +29,6 @@ module Volt
|
|
28
29
|
# Since we're checking things like size, we don't want this to be re-triggered on a
|
29
30
|
# size change, so we run without tracking.
|
30
31
|
Computation.run_without_tracking do
|
31
|
-
# puts "RELOAD:-------------- #{value.inspect}"
|
32
32
|
# Adjust to the new size
|
33
33
|
values = current_values(value)
|
34
34
|
@value = values
|
@@ -56,9 +56,8 @@ module Volt
|
|
56
56
|
|
57
57
|
def item_removed(position)
|
58
58
|
# Remove dependency
|
59
|
-
@templates[position].context.locals[:
|
59
|
+
@templates[position].context.locals[:_index_dependency].remove
|
60
60
|
|
61
|
-
# puts "REMOVE AT: #{position.inspect} - #{@templates[position].inspect} - #{@templates.inspect}"
|
62
61
|
@templates[position].remove_anchors
|
63
62
|
@templates[position].remove
|
64
63
|
@templates.delete_at(position)
|
@@ -84,17 +83,21 @@ module Volt
|
|
84
83
|
item_context.locals[@item_name.to_sym] = proc { @value[item_context.locals[:_index_value]]}
|
85
84
|
|
86
85
|
position_dependency = Dependency.new
|
87
|
-
item_context.locals[:
|
86
|
+
item_context.locals[:_index_dependency] = position_dependency
|
88
87
|
|
89
88
|
# Get and set index
|
90
|
-
item_context.locals[:
|
89
|
+
item_context.locals[:_index=] = proc do |val|
|
91
90
|
position_dependency.changed!
|
92
91
|
item_context.locals[:_index_value] = val
|
93
92
|
end
|
94
93
|
|
95
|
-
|
96
|
-
|
97
|
-
|
94
|
+
# If the user provides an each_with_index, we can assign the lookup for the index
|
95
|
+
# variable here.
|
96
|
+
if @index_name
|
97
|
+
item_context.locals[@index_name.to_sym] = proc do
|
98
|
+
position_dependency.depend
|
99
|
+
item_context.locals[:_index_value]
|
100
|
+
end
|
98
101
|
end
|
99
102
|
|
100
103
|
item_template = TemplateRenderer.new(@page, @target, item_context, binding_name, @template_name)
|
@@ -109,7 +112,7 @@ module Volt
|
|
109
112
|
size = @templates.size
|
110
113
|
if size > 0
|
111
114
|
start_index.upto(size - 1) do |index|
|
112
|
-
@templates[index].context.locals[:
|
115
|
+
@templates[index].context.locals[:_index=].call(index)
|
113
116
|
end
|
114
117
|
end
|
115
118
|
end
|
data/lib/volt/page/tasks.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Volt
|
2
2
|
# The tasks class provides an interface to call tasks on
|
3
|
-
# the backend server.
|
3
|
+
# the backend server. This class is setup as page.task (as a singleton)
|
4
4
|
class Tasks
|
5
5
|
def initialize(page)
|
6
6
|
@page = page
|
@@ -21,7 +21,6 @@ module Volt
|
|
21
21
|
@promises[promise_id] = promise
|
22
22
|
|
23
23
|
# TODO: Timeout on these callbacks
|
24
|
-
|
25
24
|
@page.channel.send_message([promise_id, class_name, method_name, meta_data, *args])
|
26
25
|
|
27
26
|
promise
|
@@ -1,12 +1,15 @@
|
|
1
1
|
module Volt
|
2
2
|
class EachScope < ViewScope
|
3
|
-
def initialize(handler, path, content)
|
3
|
+
def initialize(handler, path, content, with_index)
|
4
4
|
super(handler, path)
|
5
|
-
# @content, @variable_name = content.strip.split(/ as /)
|
6
5
|
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
if with_index
|
7
|
+
@content, @variable_name = content.split(/.each_with_index\s+do\s+\|/)
|
8
|
+
@variable_name, @index_name = @variable_name.gsub(/\|/, '').split(/\s*,\s*/)
|
9
|
+
else
|
10
|
+
@content, @variable_name = content.split(/.each\s+do\s+\|/)
|
11
|
+
@variable_name = @variable_name.gsub(/\|/, '')
|
12
|
+
end
|
10
13
|
end
|
11
14
|
|
12
15
|
def close_scope
|
@@ -17,7 +20,7 @@ module Volt
|
|
17
20
|
super
|
18
21
|
|
19
22
|
@handler.html << "<!-- $#{binding_number} --><!-- $/#{binding_number} -->"
|
20
|
-
@handler.scope.last.save_binding(binding_number, "lambda { |__p, __t, __c, __id| Volt::EachBinding.new(__p, __t, __c, __id, Proc.new { #{@content} }, #{@variable_name.inspect}, #{@path.inspect}) }")
|
23
|
+
@handler.scope.last.save_binding(binding_number, "lambda { |__p, __t, __c, __id| Volt::EachBinding.new(__p, __t, __c, __id, Proc.new { #{@content} }, #{@variable_name.inspect}, #{@index_name.inspect}, #{@path.inspect}) }")
|
21
24
|
end
|
22
25
|
end
|
23
26
|
end
|
@@ -42,7 +42,9 @@ module Volt
|
|
42
42
|
add_template(args)
|
43
43
|
else
|
44
44
|
if content =~ /.each\s+do\s+\|/
|
45
|
-
add_each(content)
|
45
|
+
add_each(content, false)
|
46
|
+
elsif content =~ /.each_with_index\s+do\s+\|/
|
47
|
+
add_each(content, true)
|
46
48
|
else
|
47
49
|
add_content_binding(content)
|
48
50
|
end
|
@@ -76,8 +78,8 @@ module Volt
|
|
76
78
|
fail '#else can only be added inside of an if block'
|
77
79
|
end
|
78
80
|
|
79
|
-
def add_each(content)
|
80
|
-
@handler.scope << EachScope.new(@handler, @path + "/__each#{@binding_number}", content)
|
81
|
+
def add_each(content, with_index)
|
82
|
+
@handler.scope << EachScope.new(@handler, @path + "/__each#{@binding_number}", content, with_index)
|
81
83
|
end
|
82
84
|
|
83
85
|
def add_template(content)
|
@@ -7,8 +7,9 @@
|
|
7
7
|
<div id="count">{{ completed }} of {{ _todos.size }}</div>
|
8
8
|
|
9
9
|
<table id="todos-table" class="table">
|
10
|
-
{{ _todos.
|
10
|
+
{{ _todos.each_with_index do |todo, idx| }}
|
11
11
|
<tr>
|
12
|
+
<td>{{ idx+1 }}.</td>
|
12
13
|
<td><input type="checkbox" checked="{{ todo._completed }}"></td>
|
13
14
|
<td class="name {{ if todo._completed }}complete{{ end }}">{{todo._name}}</td>
|
14
15
|
<td><button e-click="remove_todo(todo)">X</button></td>
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'volt/models'
|
3
|
+
|
4
|
+
class ExampleModelWithField < Volt::Model
|
5
|
+
field :name
|
6
|
+
field :value, Numeric
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "field helpers" do
|
10
|
+
it 'should allow a user to setup a field that can be written to and read' do
|
11
|
+
model = ExampleModelWithField.new
|
12
|
+
|
13
|
+
expect(model.name).to eq(nil)
|
14
|
+
model.name = 'jimmy'
|
15
|
+
expect(model.name).to eq('jimmy')
|
16
|
+
|
17
|
+
expect(model.value).to eq(nil)
|
18
|
+
model.value = '20.5'
|
19
|
+
|
20
|
+
# Should be cast to float
|
21
|
+
expect(model.value).to eq(20.5)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should raise an error when an invalid cast type is provided' do
|
25
|
+
expect do
|
26
|
+
ExampleModelWithField.field :awesome, Array
|
27
|
+
end.to raise_error(FieldHelpers::InvalidFieldClass)
|
28
|
+
end
|
29
|
+
end
|
@@ -1,19 +1,23 @@
|
|
1
|
-
require '
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
3
|
class TestModel < Volt::Model
|
4
|
-
validate :
|
4
|
+
validate :count, numericality: { min: 5, max: 10 }
|
5
5
|
validate :description, length: { message: 'needs to be longer', length: 50 }
|
6
|
+
validate :email, email: true
|
7
|
+
validate :name, length: 4
|
8
|
+
validate :phone_number, phone_number: true
|
6
9
|
validate :username, presence: true
|
7
|
-
validate :count, numericality: { min: 5, max: 10 }
|
8
10
|
end
|
9
11
|
|
10
12
|
describe Volt::Model do
|
11
13
|
it 'should validate the name' do
|
12
14
|
expect(TestModel.new.errors).to eq(
|
13
|
-
|
15
|
+
count: ['must be a number'],
|
14
16
|
description: ['needs to be longer'],
|
15
|
-
|
16
|
-
|
17
|
+
email: ['must be an email address'],
|
18
|
+
name: ['must be at least 4 characters'],
|
19
|
+
phone_number: ['must be a phone number with area or country code'],
|
20
|
+
username: ['must be specified']
|
17
21
|
)
|
18
22
|
end
|
19
23
|
|
@@ -36,7 +40,9 @@ describe Volt::Model do
|
|
36
40
|
|
37
41
|
model.save!
|
38
42
|
|
39
|
-
expect(model.marked_errors.keys).to eq(
|
43
|
+
expect(model.marked_errors.keys).to eq(
|
44
|
+
[:count, :description, :email, :name, :phone_number, :username]
|
45
|
+
)
|
40
46
|
end
|
41
47
|
|
42
48
|
describe 'length' do
|
@@ -81,4 +87,31 @@ describe Volt::Model do
|
|
81
87
|
end
|
82
88
|
end
|
83
89
|
|
90
|
+
describe 'email' do
|
91
|
+
it 'should validate email' do
|
92
|
+
model = TestModel.new
|
93
|
+
|
94
|
+
expect(model.marked_errors).to eq({})
|
95
|
+
|
96
|
+
model.mark_field!(:email)
|
97
|
+
|
98
|
+
expect(model.marked_errors).to eq(
|
99
|
+
email: ['must be an email address']
|
100
|
+
)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe 'phone_number' do
|
105
|
+
it 'should validate phone number' do
|
106
|
+
model = TestModel.new
|
107
|
+
|
108
|
+
expect(model.marked_errors).to eq({})
|
109
|
+
|
110
|
+
model.mark_field!(:phone_number)
|
111
|
+
|
112
|
+
expect(model.marked_errors).to eq(
|
113
|
+
phone_number: ['must be a phone number with area or country code']
|
114
|
+
)
|
115
|
+
end
|
116
|
+
end
|
84
117
|
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Volt::EmailValidator do
|
4
|
+
subject { Volt::EmailValidator.new(*params) }
|
5
|
+
let(:params) { [ model, field_name, options ] }
|
6
|
+
|
7
|
+
let(:model) { Volt::Model.new email: email }
|
8
|
+
let(:field_name) { :email }
|
9
|
+
let(:options) { true }
|
10
|
+
|
11
|
+
let(:valid_email) { 'test@example.com' }
|
12
|
+
let(:invalid_email) { 'test@example-com' }
|
13
|
+
let(:email) { valid_email }
|
14
|
+
|
15
|
+
describe '.validate' do
|
16
|
+
let(:result) { described_class.validate(*params.dup.insert(1, nil)) }
|
17
|
+
|
18
|
+
before do
|
19
|
+
allow(described_class).to receive(:new).and_return subject
|
20
|
+
allow(subject).to receive(:errors).and_call_original
|
21
|
+
|
22
|
+
result
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'initializes an email validator with the provided arguments' do
|
26
|
+
expect(described_class).to have_received(:new).with(*params)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'calls errors on the email validator' do
|
30
|
+
expect(subject).to have_received :errors
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'returns the result of calling errors on the validator' do
|
34
|
+
expect(subject.errors).to eq result
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe '#valid?' do
|
39
|
+
context 'when using the default regex' do
|
40
|
+
let(:options) { true }
|
41
|
+
|
42
|
+
context 'when the email is valid' do
|
43
|
+
let(:email) { valid_email }
|
44
|
+
|
45
|
+
specify { expect(subject.valid?).to eq true }
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'when the email is missing a TLD' do
|
49
|
+
let(:email) { 'test@example' }
|
50
|
+
|
51
|
+
specify { expect(subject.valid?).to eq false }
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'when the email TLD is only one character' do
|
55
|
+
let(:email) { 'test@example.c' }
|
56
|
+
|
57
|
+
specify { expect(subject.valid?).to eq false }
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'when the email is missing an username' do
|
61
|
+
let(:email) { '@example.com' }
|
62
|
+
|
63
|
+
specify { expect(subject.valid?).to eq false }
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'when the email is missing the @ symbol' do
|
67
|
+
let(:email) { 'test.example.com' }
|
68
|
+
|
69
|
+
specify { expect(subject.valid?).to eq false }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'when using a custom regex' do
|
74
|
+
let(:options) { { with: /.+\@.+/ } }
|
75
|
+
|
76
|
+
context 'and the email qualifies' do
|
77
|
+
let(:email) { 'test@example' }
|
78
|
+
|
79
|
+
specify { expect(subject.valid?).to eq true }
|
80
|
+
end
|
81
|
+
|
82
|
+
context 'and the email does not qualify' do
|
83
|
+
let(:email) { 'test$example' }
|
84
|
+
|
85
|
+
specify { expect(subject.valid?).to eq false }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe '#errors' do
|
91
|
+
context 'when the model has a valid email' do
|
92
|
+
let(:email) { valid_email }
|
93
|
+
|
94
|
+
it 'returns an empty error hash' do
|
95
|
+
expect(subject.errors).to eq({})
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context 'when the model has an invalid email' do
|
100
|
+
let(:email) { invalid_email }
|
101
|
+
|
102
|
+
it 'returns an array of errors for email' do
|
103
|
+
expect(subject.errors).to eq(email: ['must be an email address'])
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context 'when provided a custom error message' do
|
108
|
+
let(:options) { { error_message: custom_message } }
|
109
|
+
let(:custom_message) { 'this is a custom message' }
|
110
|
+
|
111
|
+
context 'and the email is invalid' do
|
112
|
+
let(:email) { invalid_email }
|
113
|
+
|
114
|
+
it 'returns errors with the custom message' do
|
115
|
+
expect(subject.errors).to eq(email: [ custom_message ])
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Volt::PhoneNumberValidator do
|
4
|
+
subject { Volt::PhoneNumberValidator.new(*params) }
|
5
|
+
let(:params) { [ model, field_name, options ] }
|
6
|
+
|
7
|
+
let(:model) { Volt::Model.new phone_number: phone_number }
|
8
|
+
let(:field_name) { :phone_number }
|
9
|
+
let(:options) { true }
|
10
|
+
|
11
|
+
let(:valid_us_number) { '(123)-123-1234' }
|
12
|
+
let(:valid_intl_number) { '+12 123 123 1234' }
|
13
|
+
let(:invalid_number) { '1234-123-123456' }
|
14
|
+
let(:phone_number) { valid_us_number }
|
15
|
+
|
16
|
+
describe '.validate' do
|
17
|
+
let(:result) { described_class.validate(*params.dup.insert(1, nil)) }
|
18
|
+
|
19
|
+
before do
|
20
|
+
allow(described_class).to receive(:new).and_return subject
|
21
|
+
allow(subject).to receive(:errors).and_call_original
|
22
|
+
|
23
|
+
result
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'initializes a phone number validator with the provided arguments' do
|
27
|
+
expect(described_class).to have_received(:new).with(*params)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'calls errors on the phone number validator' do
|
31
|
+
expect(subject).to have_received :errors
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'returns the result of calling errors on the validator' do
|
35
|
+
expect(subject.errors).to eq result
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe '#valid?' do
|
40
|
+
context 'when using the default regex' do
|
41
|
+
let(:options) { true }
|
42
|
+
|
43
|
+
context 'when the phone number is a valid US number' do
|
44
|
+
let(:phone_number) { valid_us_number }
|
45
|
+
|
46
|
+
specify { expect(subject.valid?).to eq true }
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'when the phone number is a valid international number' do
|
50
|
+
let(:phone_number) { valid_intl_number }
|
51
|
+
|
52
|
+
specify { expect(subject.valid?).to eq true }
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'when the phone number uses dashes' do
|
56
|
+
let(:phone_number) { '123-123-1234' }
|
57
|
+
|
58
|
+
specify { expect(subject.valid?).to eq true }
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'when the phone number uses periods' do
|
62
|
+
let(:phone_number) { '123.123.1234' }
|
63
|
+
|
64
|
+
specify { expect(subject.valid?).to eq true }
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'when the phone number uses spaces' do
|
68
|
+
let(:phone_number) { '123 123 1234' }
|
69
|
+
|
70
|
+
specify { expect(subject.valid?).to eq true }
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'when the phone number uses parentheses and a space' do
|
74
|
+
let(:phone_number) { '(123) 123.1234' }
|
75
|
+
|
76
|
+
specify { expect(subject.valid?).to eq true }
|
77
|
+
end
|
78
|
+
|
79
|
+
context 'when an international number uses a plus' do
|
80
|
+
let(:phone_number) { '+12 123 123 1234' }
|
81
|
+
|
82
|
+
specify { expect(subject.valid?).to eq true }
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'when an international number does not use a plus' do
|
86
|
+
let(:phone_number) { '12 123 123 1234' }
|
87
|
+
|
88
|
+
specify { expect(subject.valid?).to eq true }
|
89
|
+
end
|
90
|
+
|
91
|
+
context 'when an international number is from the UK' do
|
92
|
+
let(:phone_number) { '+12 123 1234 1234' }
|
93
|
+
|
94
|
+
specify { expect(subject.valid?).to eq true }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context 'when using a custom regex' do
|
99
|
+
let(:options) { { with: /\d{10}/ } }
|
100
|
+
|
101
|
+
context 'and the phone number qualifies' do
|
102
|
+
let(:phone_number) { '1231231234' }
|
103
|
+
|
104
|
+
specify { expect(subject.valid?).to eq true }
|
105
|
+
end
|
106
|
+
|
107
|
+
context 'and the phone number does not qualify' do
|
108
|
+
let(:phone_number) { '123-123-1234' }
|
109
|
+
|
110
|
+
specify { expect(subject.valid?).to eq false }
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe '#errors' do
|
116
|
+
context 'when the model has a valid phone number' do
|
117
|
+
let(:phone_number) { valid_us_number }
|
118
|
+
|
119
|
+
it 'returns an empty error hash' do
|
120
|
+
expect(subject.errors).to eq({})
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
context 'when the model has an invalid phone number' do
|
125
|
+
let(:phone_number) { invalid_number }
|
126
|
+
|
127
|
+
it 'returns an array of errors for phone number' do
|
128
|
+
expect(subject.errors).to eq(
|
129
|
+
phone_number: ['must be a phone number with area or country code'])
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context 'when provided a custom error message' do
|
134
|
+
let(:options) { { error_message: custom_message } }
|
135
|
+
let(:custom_message) { 'this is a custom message' }
|
136
|
+
|
137
|
+
context 'and the phone number is invalid' do
|
138
|
+
let(:phone_number) { invalid_number }
|
139
|
+
|
140
|
+
it 'returns errors with the custom message' do
|
141
|
+
expect(subject.errors).to eq(phone_number: [ custom_message ])
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -117,7 +117,7 @@ describe Volt::ViewParser do
|
|
117
117
|
'html' => " <div class=\"main\">\n <!-- $0 --><!-- $/0 -->\n </div>\n",
|
118
118
|
'bindings' => {
|
119
119
|
0 => [
|
120
|
-
"lambda { |__p, __t, __c, __id| Volt::EachBinding.new(__p, __t, __c, __id, Proc.new { _items }, \"item\", \"main/main/main/body/__each0/__template/0\") }"
|
120
|
+
"lambda { |__p, __t, __c, __id| Volt::EachBinding.new(__p, __t, __c, __id, Proc.new { _items }, \"item\", nil, \"main/main/main/body/__each0/__template/0\") }"
|
121
121
|
]
|
122
122
|
}
|
123
123
|
})
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: volt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.
|
4
|
+
version: 0.8.26.beta1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Stout
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-12-
|
11
|
+
date: 2014-12-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -453,9 +453,6 @@ files:
|
|
453
453
|
- app/volt/views/notices/index.html
|
454
454
|
- bin/volt
|
455
455
|
- docs/FAQ.md
|
456
|
-
- docs/GETTING_STARTED.md
|
457
|
-
- docs/JAVASCRIPT_COMPONENTS.md
|
458
|
-
- docs/WHY.md
|
459
456
|
- docs/volt-logo.jpg
|
460
457
|
- lib/volt.rb
|
461
458
|
- lib/volt/assets/test.rb
|
@@ -490,6 +487,7 @@ files:
|
|
490
487
|
- lib/volt/models/array_model.rb
|
491
488
|
- lib/volt/models/buffer.rb
|
492
489
|
- lib/volt/models/cursor.rb
|
490
|
+
- lib/volt/models/field_helpers.rb
|
493
491
|
- lib/volt/models/model.rb
|
494
492
|
- lib/volt/models/model_hash_behaviour.rb
|
495
493
|
- lib/volt/models/model_helpers.rb
|
@@ -510,8 +508,10 @@ files:
|
|
510
508
|
- lib/volt/models/persistors/store_state.rb
|
511
509
|
- lib/volt/models/url.rb
|
512
510
|
- lib/volt/models/validations.rb
|
511
|
+
- lib/volt/models/validators/email_validator.rb
|
513
512
|
- lib/volt/models/validators/length_validator.rb
|
514
513
|
- lib/volt/models/validators/numericality_validator.rb
|
514
|
+
- lib/volt/models/validators/phone_number_validator.rb
|
515
515
|
- lib/volt/models/validators/presence_validator.rb
|
516
516
|
- lib/volt/models/validators/unique_validator.rb
|
517
517
|
- lib/volt/page/bindings/attribute_binding.rb
|
@@ -625,10 +625,13 @@ files:
|
|
625
625
|
- spec/integration/templates_spec.rb
|
626
626
|
- spec/integration/url_spec.rb
|
627
627
|
- spec/integration/user_spec.rb
|
628
|
+
- spec/models/field_helpers_spec.rb
|
628
629
|
- spec/models/model_spec.rb
|
629
630
|
- spec/models/persistors/params_spec.rb
|
630
631
|
- spec/models/persistors/store_spec.rb
|
631
632
|
- spec/models/validations_spec.rb
|
633
|
+
- spec/models/validators/email_validator_spec.rb
|
634
|
+
- spec/models/validators/phone_number_validator_spec.rb
|
632
635
|
- spec/page/bindings/content_binding_spec.rb
|
633
636
|
- spec/page/bindings/template_binding_spec.rb
|
634
637
|
- spec/page/sub_context_spec.rb
|
@@ -721,9 +724,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
721
724
|
version: '0'
|
722
725
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
723
726
|
requirements:
|
724
|
-
- - "
|
727
|
+
- - ">"
|
725
728
|
- !ruby/object:Gem::Version
|
726
|
-
version:
|
729
|
+
version: 1.3.1
|
727
730
|
requirements: []
|
728
731
|
rubyforge_project:
|
729
732
|
rubygems_version: 2.2.2
|
@@ -772,10 +775,13 @@ test_files:
|
|
772
775
|
- spec/integration/templates_spec.rb
|
773
776
|
- spec/integration/url_spec.rb
|
774
777
|
- spec/integration/user_spec.rb
|
778
|
+
- spec/models/field_helpers_spec.rb
|
775
779
|
- spec/models/model_spec.rb
|
776
780
|
- spec/models/persistors/params_spec.rb
|
777
781
|
- spec/models/persistors/store_spec.rb
|
778
782
|
- spec/models/validations_spec.rb
|
783
|
+
- spec/models/validators/email_validator_spec.rb
|
784
|
+
- spec/models/validators/phone_number_validator_spec.rb
|
779
785
|
- spec/page/bindings/content_binding_spec.rb
|
780
786
|
- spec/page/bindings/template_binding_spec.rb
|
781
787
|
- spec/page/sub_context_spec.rb
|
data/docs/GETTING_STARTED.md
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
# Getting Started
|
2
|
-
|
3
|
-
Volt relies on a few concepts to take make web development faster and easier. The first of these is reactive programming. Data on the front and back end is stored in models. Instead of manually updating a page when the data changes, the page is coded using a templating language which automatically updates when the data changes.
|
4
|
-
|
5
|
-
## Bindings and Models
|
6
|
-
|
7
|
-
This automaic updating is done via bindings and models. In Volt app's all data is stored in a model. From your html, you can bind things like attributes and text to a value in a model.
|
8
|
-
|
9
|
-
### Name Example
|
10
|
-
|
11
|
-
```html
|
12
|
-
<label>Name:</label>
|
13
|
-
<input type="text" value="{page._name}" />
|
14
|
-
<p>Hello {page._name}</p>
|
15
|
-
```
|
16
|
-
|
17
|
-
In the example above, our model is called page (more about page later). Any time a user changes the value of the field, page._name will be updated to the fields value. When page._name is changed, the fields value changes. Also when ```page._name``` changes, the page will show the text "Hello ..." where ... is the value of page._name. These "two-way bindings" help us eliminiate a lot of code by keeping all of our application state in our models. Data displayed in a view is always computed live from the data in the models.
|
18
|
-
|
19
|
-
### Meal Cost Splitter Example
|
20
|
-
|
21
|
-
```html
|
22
|
-
<label>Cost:</label><input type="text" value="{page._cost}" /><br />
|
23
|
-
<label>People:</label><input type="text" value="{page._people}" /><br />
|
24
|
-
<p>Cost Per Person: {page._cost.to_f / page._people.to_f}</p>
|
25
|
-
```
|
26
|
-
In this example, a user can enter a cost and a number of people. When either changes, the Cost Per Person will update.
|
27
|
-
|
28
|
-
###
|
data/docs/WHY.md
DELETED
@@ -1,39 +0,0 @@
|
|
1
|
-
# Why Volt?
|
2
|
-
|
3
|
-
Volt is a new web framework. You use Ruby for both your client and server code. Volt helps you break your code into reusable components. It handles managing all assets and dependencies for you. Volt automatically updates your pages for you when your model data changes and sync's that data to the database for you. By providing reusable structure and handling common tasks, Volt lets you build web app's really fast!
|
4
|
-
|
5
|
-
# Features
|
6
|
-
|
7
|
-
## Components
|
8
|
-
|
9
|
-
Volt projects are broken into components. Components are easy to create and simple to reuse. They are easily shared and only require one line of code to insert into your project. Volt provides many common components out of the box.
|
10
|
-
|
11
|
-
## Reactive
|
12
|
-
|
13
|
-
Data in volt is reactive by default. Changes to the data is automatically updated in the DOM.
|
14
|
-
|
15
|
-
|
16
|
-
## Data Syncing
|
17
|
-
|
18
|
-
A lot of modern web development is moving data between the front-end to the back-end. Volt eliminates all of that work. Model's on the front-end automatically sync to the back-end, and vice versa. Validations are run on both sides for security. Models on the front-end are automatically updated whenever they are changed anywhere else (another browser, a background task, etc..)
|
19
|
-
|
20
|
-
|
21
|
-
# Why Volt is Awesome
|
22
|
-
|
23
|
-
- only the relevant DOM is updated. There is no match and patch algorithm to update from strings like other frameworks, all associations are tracked through our reactive core, so we know exactly what needs to be updated without the need to generate any extra HTML. This has a few advantages, namely that things like input fields are retained, so any properties (focus, tab position, etc...) are also retained.
|
24
|
-
|
25
|
-
|
26
|
-
# Why Ruby
|
27
|
-
|
28
|
-
Isomorphic type system with javascript
|
29
|
-
|
30
|
-
In web development today, JavaScript gets to be the default language by virtue of being in the browser. JavaScript is a very good language, but it has a lot of warts. (See http://wtfjs.com/ for some great examples) Some of these can introduce bugs, others are just difficult to deal with. JavaScript was rushed to market quickly and standardized very quickly. Ruby was used by a small community for years while most of the kinks were worked out. Ruby also has some great concepts such as [uniform access](http://en.wikipedia.org/wiki/Uniform_access_principle), [mixin's](http://en.wikipedia.org/wiki/Mixin), [duck typing](http://en.wikipedia.org/wiki/Duck_typing), and [blocks](http://yehudakatz.com/2012/01/10/javascript-needs-blocks/) to name a few. While many of these features can be implemented in JavaScript in userland, few are standardardized and the solutions are seldom eloquent.
|
31
|
-
|
32
|
-
[5,10,1].sort()
|
33
|
-
// [1, 10, 5]
|
34
|
-
|
35
|
-
Uniform access and duck typing provides us with the ability to make reactive objects that have the exact same interface as a normal object. This is a big win, nothing new to learn to do reactive programming. They can also be used interchangably with regular objects.
|
36
|
-
|
37
|
-
# Why Opal
|
38
|
-
|
39
|
-
Opal is really an increadiable project, and the core team has done a great job. Ruby and JavaScript are similar in a lot of ways. This lets Opal compile to JavaScript that is very readable. This also means that Opal's performance is great. You'll find that in most cases Ruby code runs with no performance penality compared to the eqivilent JavaScript code.
|