rails4_client_side_validations 0.0.2 → 0.0.3
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.
- data/README.md +1 -0
- data/lib/generators/rails4_client_side_validations/copy_assets_generator.rb +58 -0
- data/lib/generators/rails4_client_side_validations/install_generator.rb +22 -0
- data/lib/generators/templates/client_side_validations/initializer.rb +20 -0
- data/lib/rails4_client_side_validations.rb +13 -0
- data/lib/rails4_client_side_validations/action_view.rb +14 -0
- data/lib/rails4_client_side_validations/action_view/form_builder.rb +105 -0
- data/lib/rails4_client_side_validations/action_view/form_helper.rb +139 -0
- data/lib/rails4_client_side_validations/action_view/form_tag_helper.rb +12 -0
- data/lib/rails4_client_side_validations/active_model.rb +143 -0
- data/lib/rails4_client_side_validations/active_model/absence.rb +10 -0
- data/lib/rails4_client_side_validations/active_model/acceptance.rb +10 -0
- data/lib/rails4_client_side_validations/active_model/exclusion.rb +27 -0
- data/lib/rails4_client_side_validations/active_model/format.rb +31 -0
- data/lib/rails4_client_side_validations/active_model/inclusion.rb +26 -0
- data/lib/rails4_client_side_validations/active_model/length.rb +26 -0
- data/lib/rails4_client_side_validations/active_model/numericality.rb +42 -0
- data/lib/rails4_client_side_validations/active_model/presence.rb +10 -0
- data/lib/rails4_client_side_validations/active_record.rb +12 -0
- data/lib/rails4_client_side_validations/active_record/middleware.rb +59 -0
- data/lib/rails4_client_side_validations/active_record/uniqueness.rb +29 -0
- data/lib/rails4_client_side_validations/config.rb +13 -0
- data/lib/rails4_client_side_validations/core_ext.rb +3 -0
- data/lib/rails4_client_side_validations/core_ext/range.rb +10 -0
- data/lib/rails4_client_side_validations/core_ext/regexp.rb +13 -0
- data/lib/rails4_client_side_validations/engine.rb +6 -0
- data/lib/rails4_client_side_validations/files.rb +8 -0
- data/lib/rails4_client_side_validations/generators.rb +12 -0
- data/lib/rails4_client_side_validations/generators/rails_validations.rb +15 -0
- data/lib/rails4_client_side_validations/middleware.rb +120 -0
- data/lib/rails4_client_side_validations/version.rb +3 -0
- data/vendor/assets/javascripts/rails.validations.js +639 -0
- metadata +40 -9
@@ -0,0 +1,27 @@
|
|
1
|
+
module Rails4ClientSideValidations::ActiveModel
|
2
|
+
module Exclusion
|
3
|
+
|
4
|
+
def client_side_hash(model, attribute, force = nil)
|
5
|
+
if options[:in].respond_to?(:call)
|
6
|
+
if force
|
7
|
+
options = self.options.dup
|
8
|
+
options[:in] = options[:in].call(model)
|
9
|
+
hash = build_client_side_hash(model, attribute, options)
|
10
|
+
else
|
11
|
+
return
|
12
|
+
end
|
13
|
+
else
|
14
|
+
hash = build_client_side_hash(model, attribute, self.options.dup)
|
15
|
+
end
|
16
|
+
|
17
|
+
if hash[:in].is_a?(Range)
|
18
|
+
hash[:range] = hash[:in]
|
19
|
+
hash.delete(:in)
|
20
|
+
end
|
21
|
+
|
22
|
+
hash
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Rails4ClientSideValidations::ActiveModel
|
2
|
+
module Format
|
3
|
+
def client_side_hash(model, attribute, force = nil)
|
4
|
+
options = self.options.dup
|
5
|
+
if options[:with].respond_to?(:call)
|
6
|
+
if force
|
7
|
+
options[:with] = options[:with].call(model)
|
8
|
+
build_client_side_hash(model, attribute, options)
|
9
|
+
else
|
10
|
+
return
|
11
|
+
end
|
12
|
+
elsif options[:without].respond_to?(:call)
|
13
|
+
if force
|
14
|
+
options[:without] = options[:without].call(model)
|
15
|
+
build_client_side_hash(model, attribute, options)
|
16
|
+
else
|
17
|
+
return
|
18
|
+
end
|
19
|
+
else
|
20
|
+
super
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def message_type
|
27
|
+
:invalid
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Rails4ClientSideValidations::ActiveModel
|
2
|
+
module Inclusion
|
3
|
+
|
4
|
+
def client_side_hash(model, attribute, force = nil)
|
5
|
+
if options[:in].respond_to?(:call)
|
6
|
+
if force
|
7
|
+
options = self.options.dup
|
8
|
+
options[:in] = options[:in].call(model)
|
9
|
+
hash = build_client_side_hash(model, attribute, options)
|
10
|
+
else
|
11
|
+
return
|
12
|
+
end
|
13
|
+
else
|
14
|
+
hash = build_client_side_hash(model, attribute, self.options.dup)
|
15
|
+
end
|
16
|
+
|
17
|
+
if hash[:in].is_a?(Range)
|
18
|
+
hash[:range] = hash[:in]
|
19
|
+
hash.delete(:in)
|
20
|
+
end
|
21
|
+
hash
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Rails4ClientSideValidations::ActiveModel
|
2
|
+
module Length
|
3
|
+
|
4
|
+
def client_side_hash(model, attribute, force = nil)
|
5
|
+
options = self.options.dup
|
6
|
+
hash = { :messages => {} }
|
7
|
+
hash[:js_tokenizer] = options[:js_tokenizer] if options[:js_tokenizer]
|
8
|
+
hash[:allow_blank] = true if options[:allow_blank]
|
9
|
+
|
10
|
+
self.class::MESSAGES.each do |option, message_type|
|
11
|
+
if count = options[option]
|
12
|
+
options[:message] = options[message_type] if options[message_type].present?
|
13
|
+
options.delete(:message) if options[:message].nil?
|
14
|
+
hash[:messages][option] = model.errors.generate_message(attribute, message_type, options.merge(:count => count))
|
15
|
+
hash[option] = count
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
copy_conditional_attributes(hash, options)
|
20
|
+
|
21
|
+
hash
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Rails4ClientSideValidations::ActiveModel
|
2
|
+
module Numericality
|
3
|
+
|
4
|
+
OPTION_MAP = {}
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
OPTION_MAP.merge!(base::CHECKS.keys.inject({}) { |hash, key| hash.merge!(key => key) })
|
8
|
+
end
|
9
|
+
|
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) } }
|
13
|
+
|
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
|
18
|
+
|
19
|
+
hash[:allow_blank] = true if options[:allow_nil] || options[:allow_blank]
|
20
|
+
|
21
|
+
OPTION_MAP.each do |option, message_type|
|
22
|
+
if count = options[option]
|
23
|
+
if count.respond_to?(:call)
|
24
|
+
if force
|
25
|
+
count = count.call(model)
|
26
|
+
else
|
27
|
+
next
|
28
|
+
end
|
29
|
+
end
|
30
|
+
hash[:messages][option] = model.errors.generate_message(attribute, message_type, options.merge(:count => count))
|
31
|
+
hash[option] = count
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
copy_conditional_attributes(hash, options)
|
36
|
+
|
37
|
+
hash
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'rails4_client_side_validations/active_model'
|
2
|
+
require 'rails4_client_side_validations/middleware'
|
3
|
+
require 'rails4_client_side_validations/active_record/middleware'
|
4
|
+
|
5
|
+
%w{uniqueness}.each do |validator|
|
6
|
+
require "rails4_client_side_validations/active_record/#{validator}"
|
7
|
+
validator.capitalize!
|
8
|
+
eval "ActiveRecord::Validations::#{validator}Validator.send(:include, Rails4ClientSideValidations::ActiveRecord::#{validator})"
|
9
|
+
end
|
10
|
+
|
11
|
+
ActiveRecord::Base.send(:include, Rails4ClientSideValidations::ActiveModel::Validations)
|
12
|
+
Rails4ClientSideValidations::Middleware::Uniqueness.register_orm(Rails4ClientSideValidations::ActiveRecord::Middleware)
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Rails4ClientSideValidations::ActiveRecord
|
2
|
+
class Middleware
|
3
|
+
|
4
|
+
def self.is_class?(klass)
|
5
|
+
klass.abstract_class.blank? && klass < ::ActiveRecord::Base
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.is_unique?(klass, attribute, value, params)
|
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
|
+
if params[:case_sensitive] == 'true'
|
17
|
+
if t.engine.connection.adapter_name =~ /^mysql/i
|
18
|
+
relation = Arel::Nodes::SqlLiteral.new("BINARY #{t[attribute].eq(value).to_sql}")
|
19
|
+
else
|
20
|
+
relation = t[attribute].eq(value)
|
21
|
+
end
|
22
|
+
else
|
23
|
+
relation = t[attribute].matches(value)
|
24
|
+
end
|
25
|
+
|
26
|
+
if relation.is_a?(Arel::Nodes::SqlLiteral)
|
27
|
+
relation = Arel::Nodes::SqlLiteral.new("BINARY #{t[attribute].eq(value).to_sql} AND #{t[klass.primary_key].not_eq(params[:id]).to_sql}")
|
28
|
+
else
|
29
|
+
relation = relation.and(t[klass.primary_key].not_eq(params[:id])) if params[:id]
|
30
|
+
end
|
31
|
+
|
32
|
+
(params[:scope] || {}).each do |attribute, value|
|
33
|
+
value = type_cast_value(klass, attribute, value)
|
34
|
+
if relation.is_a?(Arel::Nodes::SqlLiteral)
|
35
|
+
relation = Arel::Nodes::SqlLiteral.new("#{relation} AND #{t[attribute].eq(value).to_sql}")
|
36
|
+
else
|
37
|
+
relation = relation.and(t[attribute].eq(value))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
!klass.where(relation).exists?
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def self.type_cast_value(klass, attribute, value)
|
47
|
+
klass.columns_hash[attribute].type_cast(value)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.find_topmost_superclass(klass)
|
51
|
+
if is_class?(klass.superclass)
|
52
|
+
find_topmost_superclass(klass.superclass)
|
53
|
+
else
|
54
|
+
klass
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Rails4ClientSideValidations::ActiveRecord
|
2
|
+
module Uniqueness
|
3
|
+
def client_side_hash(model, attribute, force = nil)
|
4
|
+
hash = {}
|
5
|
+
hash[:message] = model.errors.generate_message(attribute, message_type, options.except(:scope))
|
6
|
+
hash[:case_sensitive] = options[:case_sensitive]
|
7
|
+
hash[:id] = model.id unless model.new_record?
|
8
|
+
hash[:allow_blank] = true if options[:allow_blank]
|
9
|
+
if options.key?(:scope) && options[:scope].present?
|
10
|
+
hash[:scope] = Array.wrap(options[:scope]).inject({}) do |scope_hash, scope_item|
|
11
|
+
scope_hash.merge!(scope_item => model.send(scope_item))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
unless model.class.name.demodulize == model.class.name
|
16
|
+
hash[:class] = model.class.name.underscore
|
17
|
+
end
|
18
|
+
|
19
|
+
hash
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def message_type
|
25
|
+
:taken
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Rails4ClientSideValidations
|
2
|
+
module Config
|
3
|
+
class << self
|
4
|
+
attr_accessor :disabled_validators
|
5
|
+
attr_accessor :number_format_with_locale
|
6
|
+
attr_accessor :root_path
|
7
|
+
end
|
8
|
+
|
9
|
+
self.disabled_validators = []
|
10
|
+
self.number_format_with_locale = false
|
11
|
+
self.root_path = nil
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Regexp
|
2
|
+
def as_json(options = nil)
|
3
|
+
Regexp.new inspect.sub('\\A','^').sub('\\Z','$').sub('\\z','$').sub(/^\//,'').sub(/\/[a-z]*$/,'').gsub(/\(\?#.+\)/, '').gsub(/\(\?-\w+:/,'(').gsub(/\s/,''), self.options & 5
|
4
|
+
end
|
5
|
+
|
6
|
+
def to_json(options = nil)
|
7
|
+
as_json(options).inspect
|
8
|
+
end
|
9
|
+
|
10
|
+
def encode_json(encoder)
|
11
|
+
inspect
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# This is only used by dependant libraries that need to find the files
|
2
|
+
|
3
|
+
module Rails4ClientSideValidations
|
4
|
+
module Files
|
5
|
+
Initializer = File.expand_path(File.dirname(__FILE__) + '/../generators/templates/rails4_client_side_validations/initializer.rb')
|
6
|
+
Javascript = File.expand_path(File.dirname(__FILE__) + '/../../vendor/assets/javascripts/rails.validations.js')
|
7
|
+
end
|
8
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Rails4ClientSideValidations
|
2
|
+
module Generators
|
3
|
+
class RailsValidations
|
4
|
+
def self.assets
|
5
|
+
[{
|
6
|
+
:path => File.expand_path('../../../../vendor/assets/javascripts', __FILE__),
|
7
|
+
:file => 'rails.validations.js'
|
8
|
+
}]
|
9
|
+
end
|
10
|
+
|
11
|
+
Generators.register_assets(self)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rails4_client_side_validations/core_ext'
|
4
|
+
|
5
|
+
module Rails4ClientSideValidations
|
6
|
+
|
7
|
+
module Middleware
|
8
|
+
class Validators
|
9
|
+
def initialize(app)
|
10
|
+
@app = app
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
if matches = /^\/validators\/(\w+)$/.match(env['PATH_INFO'])
|
15
|
+
if Rails4ClientSideValidations::Config.disabled_validators.include?(matches[1].to_sym)
|
16
|
+
[500, {'Content-Type' => 'application/json', 'Content-Length' => '0'}, ['']]
|
17
|
+
else
|
18
|
+
"::Rails4ClientSideValidations::Middleware::#{matches[1].camelize}".constantize.new(env).response
|
19
|
+
end
|
20
|
+
else
|
21
|
+
@app.call(env)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Base
|
27
|
+
attr_accessor :request, :body, :status
|
28
|
+
|
29
|
+
def initialize(env)
|
30
|
+
# Filter out cache buster
|
31
|
+
env['QUERY_STRING'] = env['QUERY_STRING'].split('&').select { |p| !p.match(/^_=/) }.join('&')
|
32
|
+
self.body = ''
|
33
|
+
self.status = 200
|
34
|
+
self.request = ActionDispatch::Request.new(env)
|
35
|
+
end
|
36
|
+
|
37
|
+
def response
|
38
|
+
[status, {'Content-Type' => content_type, 'Content-Length' => body.length.to_s}, [body]]
|
39
|
+
end
|
40
|
+
|
41
|
+
def content_type
|
42
|
+
'application/json'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class Uniqueness < Base
|
47
|
+
IGNORE_PARAMS = %w{case_sensitive id scope}
|
48
|
+
REGISTERED_ORMS = []
|
49
|
+
class NotValidatable < StandardError; end
|
50
|
+
|
51
|
+
def response
|
52
|
+
begin
|
53
|
+
if is_unique?
|
54
|
+
self.status = 404
|
55
|
+
self.body = 'true'
|
56
|
+
else
|
57
|
+
self.status = 200
|
58
|
+
self.body = 'false'
|
59
|
+
end
|
60
|
+
rescue NotValidatable
|
61
|
+
self.status = 500
|
62
|
+
self.body = ''
|
63
|
+
end
|
64
|
+
super
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.register_orm(orm)
|
68
|
+
registered_orms << orm
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.registered_orms
|
72
|
+
REGISTERED_ORMS
|
73
|
+
end
|
74
|
+
|
75
|
+
def registered_orms
|
76
|
+
self.class.registered_orms
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def is_unique?
|
82
|
+
convert_scope_value_from_null_to_nil
|
83
|
+
resource = extract_resource
|
84
|
+
klass = resource.classify.constantize
|
85
|
+
attribute = request.params[resource].keys.first
|
86
|
+
value = request.params[resource][attribute]
|
87
|
+
middleware_class = nil
|
88
|
+
|
89
|
+
unless Array.wrap(klass._validators[attribute.to_sym]).find { |v| v.kind == :uniqueness }
|
90
|
+
raise NotValidatable
|
91
|
+
end
|
92
|
+
|
93
|
+
registered_orms.each do |orm|
|
94
|
+
if orm.is_class?(klass)
|
95
|
+
middleware_class = orm
|
96
|
+
break
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
middleware_class.is_unique?(klass, attribute, value, request.params)
|
101
|
+
end
|
102
|
+
|
103
|
+
def convert_scope_value_from_null_to_nil
|
104
|
+
if request.params['scope']
|
105
|
+
request.params['scope'].each do |key, value|
|
106
|
+
if value == 'null'
|
107
|
+
request.params['scope'][key] = nil
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def extract_resource
|
114
|
+
parent_key = (request.params.keys - IGNORE_PARAMS).first
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|