client_side_validations 3.2.8 → 4.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/HISTORY.md +46 -0
- data/README.md +540 -0
- data/lib/client_side_validations.rb +0 -1
- data/lib/client_side_validations/action_view.rb +4 -3
- data/lib/client_side_validations/action_view/form_builder.rb +101 -84
- data/lib/client_side_validations/action_view/form_helper.rb +99 -110
- data/lib/client_side_validations/action_view/form_tag_helper.rb +13 -9
- data/lib/client_side_validations/active_model.rb +95 -80
- data/lib/client_side_validations/active_model/absence.rb +11 -0
- data/lib/client_side_validations/active_model/acceptance.rb +7 -6
- data/lib/client_side_validations/active_model/exclusion.rb +4 -24
- data/lib/client_side_validations/active_model/format.rb +24 -23
- data/lib/client_side_validations/active_model/inclusion.rb +4 -23
- data/lib/client_side_validations/active_model/length.rb +15 -15
- data/lib/client_side_validations/active_model/numericality.rb +23 -22
- data/lib/client_side_validations/active_model/presence.rb +7 -6
- data/lib/client_side_validations/active_record.rb +2 -2
- data/lib/client_side_validations/active_record/middleware.rb +41 -39
- data/lib/client_side_validations/active_record/uniqueness.rb +24 -23
- data/lib/client_side_validations/config.rb +1 -1
- data/lib/client_side_validations/core_ext/range.rb +1 -2
- data/lib/client_side_validations/core_ext/regexp.rb +6 -4
- data/lib/client_side_validations/engine.rb +0 -1
- data/lib/client_side_validations/generators.rb +2 -3
- data/lib/client_side_validations/generators/rails_validations.rb +2 -3
- data/lib/client_side_validations/middleware.rb +17 -24
- data/lib/client_side_validations/version.rb +1 -1
- data/lib/generators/client_side_validations/copy_assets_generator.rb +7 -11
- data/lib/generators/client_side_validations/install_generator.rb +0 -2
- data/lib/generators/templates/client_side_validations/initializer.rb +1 -2
- data/vendor/assets/javascripts/rails.validations.js +26 -20
- metadata +173 -48
- data/client_side_validations.gemspec +0 -30
@@ -1,25 +1,27 @@
|
|
1
|
-
module ClientSideValidations
|
2
|
-
module
|
1
|
+
module ClientSideValidations
|
2
|
+
module ActiveModel
|
3
|
+
module Numericality
|
4
|
+
OPTION_MAP = {}
|
3
5
|
|
4
|
-
|
6
|
+
def self.included(base)
|
7
|
+
OPTION_MAP.merge!(base::CHECKS.keys.inject({}) { |a, e| a.merge!(e => e) })
|
8
|
+
end
|
5
9
|
|
6
|
-
|
7
|
-
|
8
|
-
|
10
|
+
def client_side_hash(model, attribute, force = nil)
|
11
|
+
options = self.options.dup
|
12
|
+
hash = { messages: { numericality: model.errors.generate_message(attribute, :not_a_number, options) } }
|
9
13
|
|
10
|
-
|
11
|
-
|
12
|
-
|
14
|
+
if options[:only_integer]
|
15
|
+
hash[:messages][:only_integer] = model.errors.generate_message(attribute, :not_an_integer, options)
|
16
|
+
hash[:only_integer] = true
|
17
|
+
end
|
13
18
|
|
14
|
-
|
15
|
-
hash[:messages][:only_integer] = model.errors.generate_message(attribute, :not_an_integer, options)
|
16
|
-
hash[:only_integer] = true
|
17
|
-
end
|
19
|
+
hash[:allow_blank] = true if options[:allow_nil] || options[:allow_blank]
|
18
20
|
|
19
|
-
|
21
|
+
OPTION_MAP.each do |option, message_type|
|
22
|
+
count = options[option]
|
23
|
+
next unless count
|
20
24
|
|
21
|
-
OPTION_MAP.each do |option, message_type|
|
22
|
-
if count = options[option]
|
23
25
|
if count.respond_to?(:call)
|
24
26
|
if force
|
25
27
|
count = count.call(model)
|
@@ -27,16 +29,15 @@ module ClientSideValidations::ActiveModel
|
|
27
29
|
next
|
28
30
|
end
|
29
31
|
end
|
30
|
-
|
32
|
+
|
33
|
+
hash[:messages][option] = model.errors.generate_message(attribute, message_type, options.merge(count: count))
|
31
34
|
hash[option] = count
|
32
35
|
end
|
33
|
-
end
|
34
36
|
|
35
|
-
|
37
|
+
copy_conditional_attributes(hash, options)
|
36
38
|
|
37
|
-
|
39
|
+
hash
|
40
|
+
end
|
38
41
|
end
|
39
|
-
|
40
42
|
end
|
41
43
|
end
|
42
|
-
|
@@ -2,10 +2,10 @@ require 'client_side_validations/active_model'
|
|
2
2
|
require 'client_side_validations/middleware'
|
3
3
|
require 'client_side_validations/active_record/middleware'
|
4
4
|
|
5
|
-
%w
|
5
|
+
%w(uniqueness).each do |validator|
|
6
6
|
require "client_side_validations/active_record/#{validator}"
|
7
7
|
validator.capitalize!
|
8
|
-
|
8
|
+
ActiveRecord::Validations.const_get("#{validator}Validator").send :include, ClientSideValidations::ActiveRecord.const_get(validator)
|
9
9
|
end
|
10
10
|
|
11
11
|
ActiveRecord::Base.send(:include, ClientSideValidations::ActiveModel::Validations)
|
@@ -1,51 +1,53 @@
|
|
1
|
-
module ClientSideValidations
|
2
|
-
|
1
|
+
module ClientSideValidations
|
2
|
+
module ActiveRecord
|
3
|
+
class Middleware
|
4
|
+
def self.class?(klass)
|
5
|
+
klass.abstract_class.blank? && klass < ::ActiveRecord::Base
|
6
|
+
end
|
3
7
|
|
4
|
-
|
5
|
-
|
6
|
-
|
8
|
+
def self.unique?(klass, attribute, value, params)
|
9
|
+
klass = find_topmost_superclass(klass)
|
10
|
+
column = klass.columns_hash[attribute.to_s]
|
11
|
+
value = type_cast_value(column, value)
|
12
|
+
value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s if value.is_a?(String)
|
7
13
|
|
8
|
-
|
9
|
-
klass = find_topmost_superclass(klass)
|
10
|
-
value = type_cast_value(klass, attribute, value)
|
11
|
-
column = klass.columns_hash[attribute.to_s]
|
12
|
-
value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s if column.text?
|
13
|
-
|
14
|
-
t = klass.arel_table
|
15
|
-
|
16
|
-
sql = []
|
17
|
-
if params[:case_sensitive] == 'true'
|
18
|
-
sql << 'BINARY' if t.engine.connection.adapter_name =~ /^mysql/i
|
19
|
-
sql << t[attribute].eq(value).to_sql
|
20
|
-
else
|
21
|
-
escaped_value = value.gsub(/[%_]/, '\\\\\0')
|
22
|
-
sql << "#{t[attribute].matches(escaped_value).to_sql} ESCAPE '\\'"
|
23
|
-
end
|
14
|
+
t = klass.arel_table
|
24
15
|
|
25
|
-
|
16
|
+
sql = []
|
17
|
+
if params[:case_sensitive] == 'true'
|
18
|
+
sql << 'BINARY' if t.engine.connection.adapter_name =~ /^mysql/i
|
19
|
+
sql << t[attribute].eq(value).to_sql
|
20
|
+
else
|
21
|
+
escaped_value = value.gsub(/[%_]/, '\\\\\0')
|
22
|
+
sql << "#{t[attribute].matches(escaped_value).to_sql} ESCAPE '\\'"
|
23
|
+
end
|
26
24
|
|
27
|
-
|
28
|
-
value = type_cast_value(klass, attribute, value)
|
29
|
-
sql << "AND #{t[attribute].eq(value).to_sql}"
|
30
|
-
end
|
25
|
+
sql << "AND #{t[klass.primary_key].not_eq(params[:id]).to_sql}" if params[:id]
|
31
26
|
|
32
|
-
|
33
|
-
|
34
|
-
|
27
|
+
(params[:scope] || {}).each do |scope_attribute, scope_value|
|
28
|
+
scope_value = type_cast_value(klass.columns_hash[scope_attribute], scope_value)
|
29
|
+
sql << "AND #{t[scope_attribute].eq(scope_value).to_sql}"
|
30
|
+
end
|
35
31
|
|
36
|
-
|
32
|
+
relation = Arel::Nodes::SqlLiteral.new(sql.join(' '))
|
33
|
+
!klass.where(relation).exists?
|
34
|
+
end
|
37
35
|
|
38
|
-
|
39
|
-
|
40
|
-
|
36
|
+
def self.type_cast_value(column, value)
|
37
|
+
if column.respond_to?(:type_cast)
|
38
|
+
column.type_cast(value)
|
39
|
+
else
|
40
|
+
column.type_cast_from_database(value)
|
41
|
+
end
|
42
|
+
end
|
41
43
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
44
|
+
def self.find_topmost_superclass(klass)
|
45
|
+
if class?(klass.superclass)
|
46
|
+
find_topmost_superclass(klass.superclass)
|
47
|
+
else
|
48
|
+
klass
|
49
|
+
end
|
47
50
|
end
|
48
51
|
end
|
49
|
-
|
50
52
|
end
|
51
53
|
end
|
@@ -1,32 +1,33 @@
|
|
1
|
-
module ClientSideValidations
|
2
|
-
module
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
1
|
+
module ClientSideValidations
|
2
|
+
module ActiveRecord
|
3
|
+
module Uniqueness
|
4
|
+
def client_side_hash(model, attribute, _force = nil)
|
5
|
+
hash = {}
|
6
|
+
hash[:message] = model.errors.generate_message(attribute, message_type, options.except(:scope))
|
7
|
+
hash[:case_sensitive] = options[:case_sensitive]
|
8
|
+
hash[:id] = model.id unless model.new_record?
|
9
|
+
hash[:allow_blank] = true if options[:allow_blank]
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
if options.key?(:client_validations) && options[:client_validations].key?(:class)
|
12
|
+
hash[:class] = options[:client_validations][:class].underscore
|
13
|
+
elsif model.class.name.demodulize != model.class.name
|
14
|
+
hash[:class] = model.class.name.underscore
|
15
|
+
end
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
17
|
+
if options.key?(:scope) && options[:scope].present?
|
18
|
+
hash[:scope] = Array.wrap(options[:scope]).inject({}) do |scope_hash, scope_item|
|
19
|
+
scope_hash.merge!(scope_item => model.send(scope_item))
|
20
|
+
end
|
19
21
|
end
|
20
|
-
end
|
21
22
|
|
22
|
-
|
23
|
-
|
23
|
+
hash
|
24
|
+
end
|
24
25
|
|
25
|
-
|
26
|
+
private
|
26
27
|
|
27
|
-
|
28
|
-
|
28
|
+
def message_type
|
29
|
+
:taken
|
30
|
+
end
|
29
31
|
end
|
30
32
|
end
|
31
33
|
end
|
32
|
-
|
@@ -1,13 +1,15 @@
|
|
1
|
+
require 'js_regex'
|
2
|
+
|
1
3
|
class Regexp
|
2
|
-
def as_json(
|
3
|
-
|
4
|
+
def as_json(*)
|
5
|
+
JsRegex.new(self).to_h
|
4
6
|
end
|
5
7
|
|
6
8
|
def to_json(options = nil)
|
7
|
-
as_json(options)
|
9
|
+
as_json(options)
|
8
10
|
end
|
9
11
|
|
10
|
-
def encode_json(
|
12
|
+
def encode_json(_encoder)
|
11
13
|
inspect
|
12
14
|
end
|
13
15
|
end
|
@@ -1,12 +1,11 @@
|
|
1
1
|
module ClientSideValidations
|
2
2
|
module Generators
|
3
|
-
|
3
|
+
ASSETS = []
|
4
4
|
|
5
5
|
def self.register_assets(klass)
|
6
|
-
|
6
|
+
ASSETS.push(*klass.assets)
|
7
7
|
end
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
11
|
require 'client_side_validations/generators/rails_validations'
|
12
|
-
|
@@ -3,8 +3,8 @@ module ClientSideValidations
|
|
3
3
|
class RailsValidations
|
4
4
|
def self.assets
|
5
5
|
[{
|
6
|
-
:
|
7
|
-
:
|
6
|
+
path: File.expand_path('../../../../vendor/assets/javascripts', __FILE__),
|
7
|
+
file: 'rails.validations.js'
|
8
8
|
}]
|
9
9
|
end
|
10
10
|
|
@@ -12,4 +12,3 @@ module ClientSideValidations
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
15
|
-
|
@@ -1,9 +1,6 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
1
|
require 'client_side_validations/core_ext'
|
4
2
|
|
5
3
|
module ClientSideValidations
|
6
|
-
|
7
4
|
module Middleware
|
8
5
|
class Validators
|
9
6
|
def initialize(app)
|
@@ -11,7 +8,8 @@ module ClientSideValidations
|
|
11
8
|
end
|
12
9
|
|
13
10
|
def call(env)
|
14
|
-
|
11
|
+
matches = %r{\A/validators/(\w+)\z}.match(env['PATH_INFO'])
|
12
|
+
if matches
|
15
13
|
process_request(matches.captures.first, env)
|
16
14
|
else
|
17
15
|
@app.call(env)
|
@@ -19,23 +17,23 @@ module ClientSideValidations
|
|
19
17
|
end
|
20
18
|
|
21
19
|
def process_request(validation, env)
|
22
|
-
if disabled_validators.include?(validation
|
20
|
+
if disabled_validators.include?(validation)
|
23
21
|
error_resp
|
24
22
|
else
|
25
23
|
klass_name = validation.camelize
|
26
24
|
klass_name = "::ClientSideValidations::Middleware::#{klass_name}"
|
27
25
|
klass_name.constantize.new(env).response
|
28
26
|
end
|
29
|
-
rescue
|
27
|
+
rescue
|
30
28
|
error_resp
|
31
29
|
end
|
32
30
|
|
33
31
|
def disabled_validators
|
34
|
-
ClientSideValidations::Config.disabled_validators.map
|
32
|
+
ClientSideValidations::Config.disabled_validators.map(&:to_s)
|
35
33
|
end
|
36
34
|
|
37
35
|
def error_resp
|
38
|
-
[500, {'Content-Type' => 'application/json', 'Content-Length' => '0'}, ['']]
|
36
|
+
[500, { 'Content-Type' => 'application/json', 'Content-Length' => '0' }, ['']]
|
39
37
|
end
|
40
38
|
end
|
41
39
|
|
@@ -51,7 +49,7 @@ module ClientSideValidations
|
|
51
49
|
end
|
52
50
|
|
53
51
|
def response
|
54
|
-
[status, {'Content-Type' => content_type, 'Content-Length' => body.length.to_s}, [body]]
|
52
|
+
[status, { 'Content-Type' => content_type, 'Content-Length' => body.length.to_s }, [body]]
|
55
53
|
end
|
56
54
|
|
57
55
|
def content_type
|
@@ -60,13 +58,13 @@ module ClientSideValidations
|
|
60
58
|
end
|
61
59
|
|
62
60
|
class Uniqueness < Base
|
63
|
-
IGNORE_PARAMS = %w
|
61
|
+
IGNORE_PARAMS = %w(case_sensitive id scope)
|
64
62
|
REGISTERED_ORMS = []
|
65
63
|
class NotValidatable < StandardError; end
|
66
64
|
|
67
65
|
def response
|
68
66
|
begin
|
69
|
-
if
|
67
|
+
if unique?
|
70
68
|
self.status = 404
|
71
69
|
self.body = 'true'
|
72
70
|
else
|
@@ -94,32 +92,29 @@ module ClientSideValidations
|
|
94
92
|
|
95
93
|
private
|
96
94
|
|
97
|
-
def
|
95
|
+
def unique?
|
98
96
|
convert_scope_value_from_null_to_nil
|
99
97
|
klass, attribute, value = extract_resources
|
100
98
|
middleware_class = nil
|
101
99
|
|
102
100
|
unless Array.wrap(klass._validators[attribute.to_sym]).find { |v| v.kind == :uniqueness }
|
103
|
-
|
101
|
+
fail NotValidatable
|
104
102
|
end
|
105
103
|
|
106
104
|
registered_orms.each do |orm|
|
107
|
-
if orm.
|
105
|
+
if orm.class?(klass)
|
108
106
|
middleware_class = orm
|
109
107
|
break
|
110
108
|
end
|
111
109
|
end
|
112
110
|
|
113
|
-
middleware_class.
|
111
|
+
middleware_class.unique?(klass, attribute, value, request.params)
|
114
112
|
end
|
115
113
|
|
116
114
|
def convert_scope_value_from_null_to_nil
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
request.params['scope'][key] = nil
|
121
|
-
end
|
122
|
-
end
|
115
|
+
return unless request.params['scope']
|
116
|
+
request.params['scope'].each do |key, value|
|
117
|
+
request.params['scope'][key] = nil if value == 'null'
|
123
118
|
end
|
124
119
|
end
|
125
120
|
|
@@ -153,14 +148,12 @@ module ClientSideValidations
|
|
153
148
|
|
154
149
|
def nested?(hash = nil, levels = 0)
|
155
150
|
i = 0
|
156
|
-
|
151
|
+
while hash.respond_to? :keys
|
157
152
|
hash = hash[hash.keys.first]
|
158
153
|
i += 1
|
159
154
|
end
|
160
155
|
i > levels
|
161
156
|
end
|
162
|
-
|
163
157
|
end
|
164
158
|
end
|
165
159
|
end
|
166
|
-
|