rails4_client_side_validations 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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
+