andrewroth-client_side_validations 3.2.0.beta.4
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/client_side_validations.gemspec +28 -0
- data/lib/client_side_validations.rb +18 -0
- data/lib/client_side_validations/action_view.rb +14 -0
- data/lib/client_side_validations/action_view/form_builder.rb +176 -0
- data/lib/client_side_validations/action_view/form_helper.rb +105 -0
- data/lib/client_side_validations/action_view/form_tag_helper.rb +12 -0
- data/lib/client_side_validations/active_model.rb +68 -0
- data/lib/client_side_validations/active_model/acceptance.rb +10 -0
- data/lib/client_side_validations/active_model/exclusion.rb +15 -0
- data/lib/client_side_validations/active_model/format.rb +10 -0
- data/lib/client_side_validations/active_model/inclusion.rb +15 -0
- data/lib/client_side_validations/active_model/length.rb +26 -0
- data/lib/client_side_validations/active_model/numericality.rb +33 -0
- data/lib/client_side_validations/active_model/presence.rb +10 -0
- data/lib/client_side_validations/active_record.rb +12 -0
- data/lib/client_side_validations/active_record/middleware.rb +50 -0
- data/lib/client_side_validations/active_record/uniqueness.rb +28 -0
- data/lib/client_side_validations/core_ext.rb +3 -0
- data/lib/client_side_validations/core_ext/range.rb +10 -0
- data/lib/client_side_validations/core_ext/regexp.rb +14 -0
- data/lib/client_side_validations/engine.rb +6 -0
- data/lib/client_side_validations/files.rb +8 -0
- data/lib/client_side_validations/generators.rb +12 -0
- data/lib/client_side_validations/generators/rails_validations.rb +15 -0
- data/lib/client_side_validations/middleware.rb +105 -0
- data/lib/client_side_validations/version.rb +3 -0
- data/lib/generators/client_side_validations/copy_assets_generator.rb +56 -0
- data/lib/generators/client_side_validations/install_generator.rb +22 -0
- data/lib/generators/templates/client_side_validations/initializer.rb +11 -0
- data/vendor/assets/javascripts/rails.validations.js +424 -0
- metadata +213 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
module ClientSideValidations::ActiveModel
|
2
|
+
module Length
|
3
|
+
|
4
|
+
def client_side_hash(model, attribute)
|
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,33 @@
|
|
1
|
+
module ClientSideValidations::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)
|
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
|
+
OPTION_MAP.each do |option, message_type|
|
20
|
+
if count = options[option]
|
21
|
+
hash[:messages][option] = model.errors.generate_message(attribute, message_type, options.merge(:count => count))
|
22
|
+
hash[option] = count
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
copy_conditional_attributes(hash, options)
|
27
|
+
|
28
|
+
hash
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'client_side_validations/active_model'
|
2
|
+
require 'client_side_validations/middleware'
|
3
|
+
require 'client_side_validations/active_record/middleware'
|
4
|
+
|
5
|
+
%w{uniqueness}.each do |validator|
|
6
|
+
require "client_side_validations/active_record/#{validator}"
|
7
|
+
validator.capitalize!
|
8
|
+
eval "ActiveRecord::Validations::#{validator}Validator.send(:include, ClientSideValidations::ActiveRecord::#{validator})"
|
9
|
+
end
|
10
|
+
|
11
|
+
ActiveRecord::Base.send(:include, ClientSideValidations::ActiveModel::Validations)
|
12
|
+
ClientSideValidations::Middleware::Uniqueness.register_orm(ClientSideValidations::ActiveRecord::Middleware)
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module ClientSideValidations::ActiveRecord
|
2
|
+
class Middleware
|
3
|
+
|
4
|
+
def self.is_class?(klass)
|
5
|
+
klass < ::ActiveRecord::Base
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.is_unique?(klass, attribute, value, params)
|
9
|
+
value = type_cast_value(klass, attribute, value)
|
10
|
+
column = klass.columns_hash[attribute.to_s]
|
11
|
+
value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s if column.text?
|
12
|
+
|
13
|
+
t = klass.arel_table
|
14
|
+
|
15
|
+
if params[:case_sensitive] == 'true'
|
16
|
+
if t.engine.connection.instance_variable_get("@config")[:adapter] == 'mysql'
|
17
|
+
relation = Arel::Nodes::SqlLiteral.new("BINARY #{t[attribute].eq(value).to_sql}")
|
18
|
+
else
|
19
|
+
relation = t[attribute].eq(value)
|
20
|
+
end
|
21
|
+
else
|
22
|
+
relation = t[attribute].matches(value)
|
23
|
+
end
|
24
|
+
|
25
|
+
if relation.is_a?(Arel::Nodes::SqlLiteral)
|
26
|
+
relation = Arel::Nodes::SqlLiteral.new("BINARY #{t[attribute].eq(value).to_sql} AND #{t.primary_key.not_eq(params[:id]).to_sql}")
|
27
|
+
else
|
28
|
+
relation = relation.and(t.primary_key.not_eq(params[:id])) if params[:id]
|
29
|
+
end
|
30
|
+
|
31
|
+
(params[:scope] || {}).each do |attribute, value|
|
32
|
+
value = type_cast_value(klass, attribute, value)
|
33
|
+
if relation.is_a?(Arel::Nodes::SqlLiteral)
|
34
|
+
relation = Arel::Nodes::SqlLiteral.new("#{relation} AND #{t[attribute].eq(value).to_sql}")
|
35
|
+
else
|
36
|
+
relation = relation.and(t[attribute].eq(value))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
!klass.where(relation).exists?
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def self.type_cast_value(klass, attribute, value)
|
46
|
+
klass.columns_hash[attribute].type_cast(value)
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module ClientSideValidations::ActiveRecord
|
2
|
+
module Uniqueness
|
3
|
+
def client_side_hash(model, attribute)
|
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
|
+
if options.key?(:scope) && options[:scope].present?
|
9
|
+
hash[:scope] = Array.wrap(options[:scope]).inject({}) do |scope_hash, scope_item|
|
10
|
+
scope_hash.merge!(scope_item => model.send(scope_item))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
unless model.class.name.demodulize == model.class.name
|
15
|
+
hash[:class] = model.class.name.underscore
|
16
|
+
end
|
17
|
+
|
18
|
+
hash
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def message_type
|
24
|
+
:taken
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,14 @@
|
|
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+:/,'('), self.options
|
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
|
14
|
+
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# This is only used by dependant libraries that need to find the files
|
2
|
+
|
3
|
+
module ClientSideValidations
|
4
|
+
module Files
|
5
|
+
Initializer = File.expand_path(File.dirname(__FILE__) + '/../generators/templates/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 ClientSideValidations
|
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,105 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'client_side_validations/core_ext'
|
4
|
+
|
5
|
+
module ClientSideValidations
|
6
|
+
|
7
|
+
module Middleware
|
8
|
+
class Validators
|
9
|
+
def initialize(app)
|
10
|
+
@app = app
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
matches = /^\/validators\/(\w+)$/.match(env['PATH_INFO'])
|
15
|
+
if !matches || (matches[1] == 'uniqueness' && Config.uniqueness_validator_disabled)
|
16
|
+
@app.call(env)
|
17
|
+
else
|
18
|
+
"::ClientSideValidations::Middleware::#{matches[1].camelize}".constantize.new(env).response
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Base
|
24
|
+
attr_accessor :request, :body, :status
|
25
|
+
|
26
|
+
def initialize(env)
|
27
|
+
self.body = ''
|
28
|
+
self.status = 200
|
29
|
+
self.request = ActionDispatch::Request.new(env)
|
30
|
+
end
|
31
|
+
|
32
|
+
def response
|
33
|
+
[status, {'Content-Type' => content_type, 'Content-Length' => body.length.to_s}, [body]]
|
34
|
+
end
|
35
|
+
|
36
|
+
def content_type
|
37
|
+
'application/json'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Uniqueness < Base
|
42
|
+
IGNORE_PARAMS = %w{case_sensitive id scope}
|
43
|
+
REGISTERED_ORMS = []
|
44
|
+
|
45
|
+
def response
|
46
|
+
if is_unique?
|
47
|
+
self.status = 404
|
48
|
+
self.body = 'true'
|
49
|
+
else
|
50
|
+
self.status = 200
|
51
|
+
self.body = 'false'
|
52
|
+
end
|
53
|
+
super
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.register_orm(orm)
|
57
|
+
registered_orms << orm
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.registered_orms
|
61
|
+
REGISTERED_ORMS
|
62
|
+
end
|
63
|
+
|
64
|
+
def registered_orms
|
65
|
+
self.class.registered_orms
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def is_unique?
|
71
|
+
convert_scope_value_from_null_to_nil
|
72
|
+
resource = extract_resource
|
73
|
+
klass = resource.classify.constantize
|
74
|
+
attribute = request.params[resource].keys.first
|
75
|
+
value = request.params[resource][attribute]
|
76
|
+
middleware_class = nil
|
77
|
+
|
78
|
+
registered_orms.each do |orm|
|
79
|
+
if orm.is_class?(klass)
|
80
|
+
middleware_class = orm
|
81
|
+
break
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
middleware_class.is_unique?(klass, attribute, value, request.params)
|
86
|
+
end
|
87
|
+
|
88
|
+
def convert_scope_value_from_null_to_nil
|
89
|
+
if request.params['scope']
|
90
|
+
request.params['scope'].each do |key, value|
|
91
|
+
if value == 'null'
|
92
|
+
request.params['scope'][key] = nil
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def extract_resource
|
99
|
+
parent_key = (request.params.keys - IGNORE_PARAMS).first
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module ClientSideValidations
|
2
|
+
module Generators
|
3
|
+
class CopyAssetsGenerator < Rails::Generators::Base
|
4
|
+
|
5
|
+
def copy_javascript_asset
|
6
|
+
if self.class == CopyAssetsGenerator || !asset_pipeline_enabled?
|
7
|
+
assets.each do |asset|
|
8
|
+
source_paths << asset[:path]
|
9
|
+
copy_file asset[:file], "#{asset_directory}/#{asset[:file]}"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def self.asset_directory
|
17
|
+
if asset_pipeline_enabled?
|
18
|
+
"app#{Rails.configuration.assets.prefix}/javascripts"
|
19
|
+
else
|
20
|
+
'public/javascripts'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def asset_directory
|
25
|
+
CopyAssetsGenerator.asset_directory
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.assets
|
29
|
+
Assets
|
30
|
+
end
|
31
|
+
|
32
|
+
def assets
|
33
|
+
CopyAssetsGenerator.assets
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.asset_file_names
|
37
|
+
assets.map { |asset| asset[:file] }.join(', ')
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.asset_pipeline_enabled?
|
41
|
+
(Rails.configuration.respond_to?(:assets) ? (Rails.configuration.assets || {}) : {})[:enabled]
|
42
|
+
end
|
43
|
+
|
44
|
+
def asset_pipeline_enabled?
|
45
|
+
self.class.asset_pipeline_enabled?
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.installation_message
|
49
|
+
"Copies #{asset_file_names} to #{asset_directory}"
|
50
|
+
end
|
51
|
+
|
52
|
+
desc installation_message
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|