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.
Files changed (33) hide show
  1. data/README.md +1 -0
  2. data/lib/generators/rails4_client_side_validations/copy_assets_generator.rb +58 -0
  3. data/lib/generators/rails4_client_side_validations/install_generator.rb +22 -0
  4. data/lib/generators/templates/client_side_validations/initializer.rb +20 -0
  5. data/lib/rails4_client_side_validations.rb +13 -0
  6. data/lib/rails4_client_side_validations/action_view.rb +14 -0
  7. data/lib/rails4_client_side_validations/action_view/form_builder.rb +105 -0
  8. data/lib/rails4_client_side_validations/action_view/form_helper.rb +139 -0
  9. data/lib/rails4_client_side_validations/action_view/form_tag_helper.rb +12 -0
  10. data/lib/rails4_client_side_validations/active_model.rb +143 -0
  11. data/lib/rails4_client_side_validations/active_model/absence.rb +10 -0
  12. data/lib/rails4_client_side_validations/active_model/acceptance.rb +10 -0
  13. data/lib/rails4_client_side_validations/active_model/exclusion.rb +27 -0
  14. data/lib/rails4_client_side_validations/active_model/format.rb +31 -0
  15. data/lib/rails4_client_side_validations/active_model/inclusion.rb +26 -0
  16. data/lib/rails4_client_side_validations/active_model/length.rb +26 -0
  17. data/lib/rails4_client_side_validations/active_model/numericality.rb +42 -0
  18. data/lib/rails4_client_side_validations/active_model/presence.rb +10 -0
  19. data/lib/rails4_client_side_validations/active_record.rb +12 -0
  20. data/lib/rails4_client_side_validations/active_record/middleware.rb +59 -0
  21. data/lib/rails4_client_side_validations/active_record/uniqueness.rb +29 -0
  22. data/lib/rails4_client_side_validations/config.rb +13 -0
  23. data/lib/rails4_client_side_validations/core_ext.rb +3 -0
  24. data/lib/rails4_client_side_validations/core_ext/range.rb +10 -0
  25. data/lib/rails4_client_side_validations/core_ext/regexp.rb +13 -0
  26. data/lib/rails4_client_side_validations/engine.rb +6 -0
  27. data/lib/rails4_client_side_validations/files.rb +8 -0
  28. data/lib/rails4_client_side_validations/generators.rb +12 -0
  29. data/lib/rails4_client_side_validations/generators/rails_validations.rb +15 -0
  30. data/lib/rails4_client_side_validations/middleware.rb +120 -0
  31. data/lib/rails4_client_side_validations/version.rb +3 -0
  32. data/vendor/assets/javascripts/rails.validations.js +639 -0
  33. metadata +40 -9
@@ -0,0 +1,10 @@
1
+ module Rails4ClientSideValidations::ActiveModel
2
+ module Absence
3
+ private
4
+
5
+ def message_type
6
+ :present
7
+ end
8
+ end
9
+ end
10
+
@@ -0,0 +1,10 @@
1
+ module Rails4ClientSideValidations::ActiveModel
2
+ module Acceptance
3
+ private
4
+
5
+ def message_type
6
+ :accepted
7
+ end
8
+ end
9
+ end
10
+
@@ -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,10 @@
1
+ module Rails4ClientSideValidations::ActiveModel
2
+ module Presence
3
+ private
4
+
5
+ def message_type
6
+ :blank
7
+ end
8
+ end
9
+ end
10
+
@@ -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,3 @@
1
+ require 'active_support/json'
2
+ require 'rails4_client_side_validations/core_ext/range'
3
+ require 'rails4_client_side_validations/core_ext/regexp'
@@ -0,0 +1,10 @@
1
+ class Range
2
+ def as_json(options = nil)
3
+ [first, last]
4
+ end
5
+
6
+ def to_json(options = nil)
7
+ as_json(options).inspect
8
+ end
9
+ end
10
+
@@ -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,6 @@
1
+ module Rails4ClientSideValidations
2
+ class Engine < ::Rails::Engine
3
+ config.app_middleware.use Rails4ClientSideValidations::Middleware::Validators
4
+ end
5
+ end
6
+
@@ -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,12 @@
1
+ module Rails4ClientSideValidations
2
+ module Generators
3
+ Assets = []
4
+
5
+ def self.register_assets(klass)
6
+ Assets.push(*klass.assets)
7
+ end
8
+ end
9
+ end
10
+
11
+ require 'rails4_client_side_validations/generators/rails_validations'
12
+
@@ -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
+