af-client_side_validations 3.1.4.af1

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 (115) hide show
  1. data/client_side_validations.gemspec +34 -0
  2. data/lib/client_side_validations/action_view/form_builder.rb +160 -0
  3. data/lib/client_side_validations/action_view/form_helper.rb +92 -0
  4. data/lib/client_side_validations/action_view/form_tag_helper.rb +12 -0
  5. data/lib/client_side_validations/action_view.rb +14 -0
  6. data/lib/client_side_validations/active_model/acceptance.rb +10 -0
  7. data/lib/client_side_validations/active_model/exclusion.rb +15 -0
  8. data/lib/client_side_validations/active_model/format.rb +10 -0
  9. data/lib/client_side_validations/active_model/inclusion.rb +15 -0
  10. data/lib/client_side_validations/active_model/length.rb +24 -0
  11. data/lib/client_side_validations/active_model/numericality.rb +31 -0
  12. data/lib/client_side_validations/active_model/presence.rb +10 -0
  13. data/lib/client_side_validations/active_model.rb +60 -0
  14. data/lib/client_side_validations/active_record/middleware.rb +33 -0
  15. data/lib/client_side_validations/active_record/uniqueness.rb +28 -0
  16. data/lib/client_side_validations/active_record.rb +11 -0
  17. data/lib/client_side_validations/core_ext/range.rb +10 -0
  18. data/lib/client_side_validations/core_ext/regexp.rb +14 -0
  19. data/lib/client_side_validations/core_ext.rb +3 -0
  20. data/lib/client_side_validations/engine.rb +6 -0
  21. data/lib/client_side_validations/files.rb +8 -0
  22. data/lib/client_side_validations/formtastic.rb +21 -0
  23. data/lib/client_side_validations/middleware.rb +81 -0
  24. data/lib/client_side_validations/mongo_mapper/middleware.rb +20 -0
  25. data/lib/client_side_validations/mongo_mapper/uniqueness.rb +28 -0
  26. data/lib/client_side_validations/mongo_mapper.rb +9 -0
  27. data/lib/client_side_validations/mongoid/middleware.rb +20 -0
  28. data/lib/client_side_validations/mongoid/uniqueness.rb +28 -0
  29. data/lib/client_side_validations/mongoid.rb +9 -0
  30. data/lib/client_side_validations/simple_form.rb +24 -0
  31. data/lib/client_side_validations/version.rb +3 -0
  32. data/lib/client_side_validations.rb +13 -0
  33. data/lib/generators/client_side_validations/copy_asset_generator.rb +36 -0
  34. data/lib/generators/client_side_validations/install_generator.rb +45 -0
  35. data/lib/generators/templates/client_side_validations/README.rails.3.0 +6 -0
  36. data/lib/generators/templates/client_side_validations/README.rails.3.1 +7 -0
  37. data/lib/generators/templates/client_side_validations/initializer.rb +14 -0
  38. data/test/action_view/cases/helper.rb +176 -0
  39. data/test/action_view/cases/test_helpers.rb +666 -0
  40. data/test/action_view/cases/test_legacy_helpers.rb +217 -0
  41. data/test/action_view/models/comment.rb +35 -0
  42. data/test/action_view/models/post.rb +35 -0
  43. data/test/action_view/models.rb +3 -0
  44. data/test/active_model/cases/helper.rb +4 -0
  45. data/test/active_model/cases/test_acceptance_validator.rb +16 -0
  46. data/test/active_model/cases/test_base.rb +11 -0
  47. data/test/active_model/cases/test_confirmation_validator.rb +16 -0
  48. data/test/active_model/cases/test_exclusion_validator.rb +20 -0
  49. data/test/active_model/cases/test_format_validator.rb +21 -0
  50. data/test/active_model/cases/test_inclusion_validator.rb +21 -0
  51. data/test/active_model/cases/test_length_validator.rb +61 -0
  52. data/test/active_model/cases/test_numericality_validator.rb +46 -0
  53. data/test/active_model/cases/test_presence_validator.rb +16 -0
  54. data/test/active_model/cases/test_validations.rb +175 -0
  55. data/test/active_model/models/person.rb +17 -0
  56. data/test/active_record/cases/helper.rb +12 -0
  57. data/test/active_record/cases/test_base.rb +11 -0
  58. data/test/active_record/cases/test_middleware.rb +175 -0
  59. data/test/active_record/cases/test_uniqueness_validator.rb +50 -0
  60. data/test/active_record/models/guid.rb +7 -0
  61. data/test/active_record/models/user.rb +14 -0
  62. data/test/base_helper.rb +7 -0
  63. data/test/core_ext/cases/test_core_ext.rb +46 -0
  64. data/test/formtastic/cases/helper.rb +7 -0
  65. data/test/formtastic/cases/test_form_builder.rb +11 -0
  66. data/test/formtastic/cases/test_form_helper.rb +21 -0
  67. data/test/generators/cases/test_generators.rb +70 -0
  68. data/test/javascript/config.ru +3 -0
  69. data/test/javascript/public/test/callbacks/elementAfter.js +54 -0
  70. data/test/javascript/public/test/callbacks/elementBefore.js +54 -0
  71. data/test/javascript/public/test/callbacks/elementFail.js +70 -0
  72. data/test/javascript/public/test/callbacks/elementPass.js +70 -0
  73. data/test/javascript/public/test/callbacks/formAfter.js +45 -0
  74. data/test/javascript/public/test/callbacks/formBefore.js +45 -0
  75. data/test/javascript/public/test/callbacks/formFail.js +51 -0
  76. data/test/javascript/public/test/callbacks/formPass.js +50 -0
  77. data/test/javascript/public/test/form_builders/validateForm.js +66 -0
  78. data/test/javascript/public/test/form_builders/validateFormtastic.js +54 -0
  79. data/test/javascript/public/test/form_builders/validateNestedForm.js +66 -0
  80. data/test/javascript/public/test/form_builders/validateSimpleForm.js +57 -0
  81. data/test/javascript/public/test/settings.js +15 -0
  82. data/test/javascript/public/test/validateElement.js +209 -0
  83. data/test/javascript/public/test/validators/acceptance.js +42 -0
  84. data/test/javascript/public/test/validators/confirmation.js +25 -0
  85. data/test/javascript/public/test/validators/exclusion.js +41 -0
  86. data/test/javascript/public/test/validators/format.js +27 -0
  87. data/test/javascript/public/test/validators/inclusion.js +42 -0
  88. data/test/javascript/public/test/validators/length.js +76 -0
  89. data/test/javascript/public/test/validators/numericality.js +158 -0
  90. data/test/javascript/public/test/validators/presence.js +21 -0
  91. data/test/javascript/public/test/validators/uniqueness.js +107 -0
  92. data/test/javascript/public/vendor/jquery.metadata.js +122 -0
  93. data/test/javascript/public/vendor/qunit.css +196 -0
  94. data/test/javascript/public/vendor/qunit.js +1374 -0
  95. data/test/javascript/server.rb +84 -0
  96. data/test/javascript/views/index.erb +20 -0
  97. data/test/javascript/views/layout.erb +21 -0
  98. data/test/middleware/cases/helper.rb +18 -0
  99. data/test/middleware/cases/test_middleware.rb +8 -0
  100. data/test/mongo_mapper/cases/helper.rb +9 -0
  101. data/test/mongo_mapper/cases/test_base.rb +15 -0
  102. data/test/mongo_mapper/cases/test_middleware.rb +77 -0
  103. data/test/mongo_mapper/cases/test_uniqueness_validator.rb +50 -0
  104. data/test/mongo_mapper/models/magazine.rb +11 -0
  105. data/test/mongoid/cases/helper.rb +16 -0
  106. data/test/mongoid/cases/test_base.rb +15 -0
  107. data/test/mongoid/cases/test_middleware.rb +77 -0
  108. data/test/mongoid/cases/test_uniqueness_validator.rb +49 -0
  109. data/test/mongoid/models/book.rb +12 -0
  110. data/test/simple_form/cases/helper.rb +5 -0
  111. data/test/simple_form/cases/test_form_builder.rb +14 -0
  112. data/test/simple_form/cases/test_form_helper.rb +24 -0
  113. data/test/test_loader.rb +6 -0
  114. data/vendor/assets/javascripts/rails.validations.js +418 -0
  115. metadata +436 -0
@@ -0,0 +1,34 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "client_side_validations/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "af-client_side_validations"
7
+ s.version = ClientSideValidations::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Paul Kmiec"]
10
+ s.email = ["paul.kmiec@appfolio.com"]
11
+ s.homepage = "https://github.com/appfolio/client_side_validations"
12
+ s.summary = %q{Client Side Validations}
13
+ s.description = %q{Client Side Validations}
14
+
15
+ s.files = `git ls-files -- {lib/*,vendor/*,*.gemspec}`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ s.add_development_dependency 'rails', '~> 3.1.0'
21
+ s.add_development_dependency 'sqlite3'
22
+ s.add_development_dependency 'bson_ext'
23
+ s.add_development_dependency 'mongoid', '~> 2.0.0'
24
+ s.add_development_dependency 'mongo_mapper','~> 0.9.0'
25
+ s.add_development_dependency 'mocha'
26
+ s.add_development_dependency 'simple_form', '~> 1.5.0'
27
+ s.add_development_dependency 'formtastic', '~> 2.0.0'
28
+
29
+ # For QUnit testing
30
+ s.add_development_dependency 'sinatra', '~> 1.0'
31
+ s.add_development_dependency 'shotgun'
32
+ s.add_development_dependency 'thin'
33
+ s.add_development_dependency 'json'
34
+ end
@@ -0,0 +1,160 @@
1
+ module ClientSideValidations::ActionView::Helpers
2
+ module FormBuilder
3
+
4
+ def self.included(base)
5
+ (base.field_helpers.map(&:to_s) - %w(apply_form_for_options! label check_box radio_button fields_for hidden_field)).each do |selector|
6
+ base.class_eval <<-RUBY_EVAL
7
+ def #{selector}_with_client_side_validations(method, options = {})
8
+ apply_client_side_validators(method, options)
9
+ options.delete(:validate)
10
+ #{selector}_without_client_side_validations(method, options)
11
+ end
12
+ RUBY_EVAL
13
+
14
+ base.class_eval { alias_method_chain selector, :client_side_validations }
15
+ end
16
+
17
+ base.class_eval do
18
+ alias_method_chain :initialize, :client_side_validations
19
+ alias_method_chain :fields_for, :client_side_validations
20
+ alias_method_chain :check_box, :client_side_validations
21
+ alias_method_chain :radio_button, :client_side_validations
22
+ alias_method_chain :select, :client_side_validations
23
+ alias_method_chain :collection_select, :client_side_validations
24
+ alias_method_chain :grouped_collection_select, :client_side_validations
25
+ alias_method_chain :time_zone_select, :client_side_validations
26
+
27
+ def self.client_side_form_settings(options, form_helper)
28
+ {
29
+ :type => self.to_s,
30
+ :input_tag => form_helper.class.field_error_proc.call(%{<span id="input_tag" />}, Struct.new(:error_message, :tag_id).new([], "")),
31
+ :label_tag => form_helper.class.field_error_proc.call(%{<label id="label_tag" />}, Struct.new(:error_message, :tag_id).new([], ""))
32
+ }
33
+ end
34
+ end
35
+ end
36
+
37
+ def initialize_with_client_side_validations(object_name, object, template, options, proc)
38
+ initialize_without_client_side_validations(object_name, object, template, options, proc)
39
+ @options[:validators] = {}
40
+ end
41
+
42
+ def fields_for_with_client_side_validations(record_or_name_or_array, *args, &block)
43
+ options = args.extract_options!
44
+ options[:validate] ||= @options[:validate] if @options[:validate] && !options.key?(:validate)
45
+ fields_for_without_client_side_validations(record_or_name_or_array, *(args << options), &block)
46
+ end
47
+
48
+ def check_box_with_client_side_validations(method, options = {}, checked_value = "1", unchecked_value = "0")
49
+ apply_client_side_validators(method, options)
50
+ check_box_without_client_side_validations(method, options, checked_value, unchecked_value)
51
+ end
52
+
53
+ def radio_button_with_client_side_validations(method, tag_value, options = {})
54
+ apply_client_side_validators(method, options)
55
+ radio_button_without_client_side_validations(method, tag_value, options)
56
+ end
57
+
58
+ def select_with_client_side_validations(method, choices, options = {}, html_options = {})
59
+ apply_client_side_validators(method, html_options)
60
+ select_without_client_side_validations(method, choices, options, html_options)
61
+ end
62
+
63
+ def collection_select_with_client_side_validations(method, collection, value_method, text_method, options = {}, html_options = {})
64
+ apply_client_side_validators(method, html_options)
65
+ collection_select_without_client_side_validations(method, collection, value_method, text_method, options, html_options)
66
+ end
67
+
68
+ def grouped_collection_select_with_client_side_validations(method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
69
+ apply_client_side_validators(method, html_options)
70
+ grouped_collection_select_without_client_side_validations(method, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
71
+ end
72
+
73
+ def time_zone_select_with_client_side_validations(method, priority_zones = nil, options = {}, html_options = {})
74
+ apply_client_side_validators(method, html_options)
75
+ time_zone_select_without_client_side_validations(method, priority_zones = nil, options, html_options)
76
+ end
77
+
78
+ private
79
+
80
+ def apply_client_side_validators(method, options = {})
81
+ if @options[:validate] && options[:validate] != false && validators = filter_validators(method, options[:validate])
82
+ options.merge!("data-validate" => true)
83
+ name = options[:name] || "#{@object_name}[#{method}]"
84
+ child_index = @options[:child_index] ? "(\\d+|#{Regexp.escape(@options[:child_index])})" : "\\d+"
85
+ name = name.gsub(/_attributes\]\[#{child_index}\]/, '_attributes][]')
86
+ @options[:validators].merge!("#{name}#{options[:multiple] ? "[]" : nil}" => validators)
87
+ end
88
+ end
89
+
90
+ def filter_validators(method, filters)
91
+ if validators = @object.client_side_validation_hash[method]
92
+ unfiltered_validators = validators.inject({}) do |unfiltered_validators, validator|
93
+ unfiltered_validators[validator.first] = validator.last
94
+ if has_filter_for_validator?(validator, filters)
95
+ if filter_validator?(validator, filters)
96
+ unfiltered_validators.delete(validator.first)
97
+ elsif force_validator_despite_conditional?(validator, filters) && !can_run_validator?(validator, method)
98
+ unfiltered_validators.delete(validator.first)
99
+ end
100
+ else
101
+ if (conditional = (validator.last[:if] || validator.last[:unless])) && conditional.is_a?(Symbol) && !conditional_method_is_change_method?(conditional, method)
102
+ unfiltered_validators.delete(validator.first)
103
+ end
104
+ end
105
+ unfiltered_validators[validator.first].delete(:if) if unfiltered_validators[validator.first]
106
+ unfiltered_validators[validator.first].delete(:unless) if unfiltered_validators[validator.first]
107
+ unfiltered_validators
108
+ end
109
+
110
+ unfiltered_validators.empty? ? nil : unfiltered_validators
111
+ end
112
+ end
113
+
114
+ def has_filter_for_validator?(validator, filters)
115
+ filters && (filters == true || filters.key?(validator.first))
116
+ end
117
+
118
+ def filter_validator?(validator, filters)
119
+ filters != true && filters[validator.first] == false
120
+ end
121
+
122
+ def force_validator_despite_conditional?(validator, filters)
123
+ filters == true || filters[validator.first] == true
124
+ end
125
+
126
+ def can_run_validator?(validator, method)
127
+ result = true
128
+ if_result = run_if_validator(validator.last[:if], method)
129
+ unless_result = run_unless_validator(validator.last[:unless], method)
130
+ result = result && if_result unless if_result.nil?
131
+ result = result && unless_result unless unless_result.nil?
132
+ result
133
+ end
134
+
135
+ def run_if_validator(conditional, method)
136
+ if conditional
137
+ if conditional.is_a?(Symbol)
138
+ conditional_method_is_change_method?(conditional, method) ? true : !!@object.send(conditional)
139
+ else
140
+ !!conditional.call(@object)
141
+ end
142
+ end
143
+ end
144
+
145
+ def run_unless_validator(conditional, method)
146
+ if conditional
147
+ if conditional.is_a?(Symbol)
148
+ conditional_method_is_change_method?(conditional, method) ? true : !@object.send(conditional)
149
+ else
150
+ !conditional.call(@object)
151
+ end
152
+ end
153
+ end
154
+
155
+ def conditional_method_is_change_method?(conditional, method)
156
+ conditional.to_sym == "#{method}_changed?".to_sym
157
+ end
158
+ end
159
+ end
160
+
@@ -0,0 +1,92 @@
1
+ module ClientSideValidations::ActionView::Helpers
2
+ module FormHelper
3
+ class Error < StandardError; end
4
+
5
+ def form_for(record_or_name_or_array, *args, &proc)
6
+ options = args.extract_options!
7
+ if options[:validate]
8
+
9
+ content_for_name = options[:validate] unless options[:validate] == true
10
+
11
+ # Always turn off HTML5 Validations
12
+ options[:html] ||= {}
13
+ options[:html][:novalidate] = 'novalidate'
14
+
15
+ case record_or_name_or_array
16
+ when String, Symbol
17
+ raise ClientSideValidations::ActionView::Helpers::FormHelper::Error, 'Using form_for(:name, @resource) is deprecated in Rails and is not supported with ClientSideValidations. Please use form_for(@resource, :as => :name) instead.'
18
+ when Array
19
+ object = record_or_name_or_array.last
20
+ else
21
+ object = record_or_name_or_array
22
+ end
23
+ end
24
+
25
+ @validators = {}
26
+ # Order matters here. Rails mutates the options object
27
+ script = client_side_form_settings(object, options)
28
+ form = super(record_or_name_or_array, *(args << options), &proc)
29
+ # Because of the load order requirement above this sub is necessary
30
+ # Would be nice to not do this
31
+ script = insert_validators_into_script(script)
32
+ if content_for_name
33
+ content_for(content_for_name) { script.html_safe }
34
+ script = nil
35
+ end
36
+ "#{form}#{script}".html_safe
37
+ end
38
+
39
+ def apply_form_for_options!(object_or_array, options)
40
+ super
41
+ options[:html][:validate] = true if options[:validate]
42
+ end
43
+
44
+ def fields_for(record_or_name_or_array, *args, &block)
45
+ output = super
46
+
47
+ if @validators
48
+ options = args.last || {}
49
+ if validators = options[:validators]
50
+ @validators.merge!(validators)
51
+ end
52
+ end
53
+ output
54
+ end
55
+
56
+ private
57
+
58
+ def insert_validators_into_script(script)
59
+ # There is probably a more performant way of doing this
60
+ # But using String#sub has some issues. Undocumented "features"
61
+ if script
62
+ script = script.split(/"validator_hash"/)
63
+ script = "#{script[0]}#{@validators.to_json}#{script[1]}"
64
+ end
65
+
66
+ script
67
+ end
68
+
69
+ def client_side_form_settings(object, options)
70
+ if options[:validate]
71
+ builder = options[:builder] || ActionView::Base.default_form_builder
72
+
73
+ if options[:html] && options[:html][:id]
74
+ var_name = options[:html][:id]
75
+ else
76
+ var_name = if object.respond_to?(:persisted?) && object.persisted?
77
+ options[:as] ? "#{options[:as]}_edit" : dom_id(object, :edit)
78
+ else
79
+ options[:as] ? "#{options[:as]}_new" : dom_id(object)
80
+ end
81
+ end
82
+
83
+ content_tag(:script) do
84
+ "window['#{var_name}'] = #{builder.client_side_form_settings(options, self).merge(:validators => 'validator_hash').to_json};".html_safe
85
+ end
86
+
87
+ end
88
+ end
89
+
90
+ end
91
+ end
92
+
@@ -0,0 +1,12 @@
1
+ module ClientSideValidations::ActionView::Helpers
2
+ module FormTagHelper
3
+ private
4
+ def html_options_for_form(url_for_options, options, *parameters_for_url)
5
+ options.stringify_keys!
6
+ html_options = {}
7
+ html_options['data-validate'] = options.delete('validate') if options['validate']
8
+ html_options.merge!(super(url_for_options, options, *parameters_for_url))
9
+ end
10
+ end
11
+ end
12
+
@@ -0,0 +1,14 @@
1
+ module ClientSideValidations::ActionView
2
+ module Helpers
3
+ end
4
+ end
5
+
6
+ require 'client_side_validations/core_ext'
7
+ require 'client_side_validations/action_view/form_helper'
8
+ require 'client_side_validations/action_view/form_tag_helper'
9
+ require 'client_side_validations/action_view/form_builder'
10
+
11
+ ActionView::Base.send(:include, ClientSideValidations::ActionView::Helpers::FormHelper)
12
+ ActionView::Base.send(:include, ClientSideValidations::ActionView::Helpers::FormTagHelper)
13
+ ActionView::Helpers::FormBuilder.send(:include, ClientSideValidations::ActionView::Helpers::FormBuilder)
14
+
@@ -0,0 +1,10 @@
1
+ module ClientSideValidations::ActiveModel
2
+ module Acceptance
3
+ private
4
+
5
+ def message_type
6
+ :accepted
7
+ end
8
+ end
9
+ end
10
+
@@ -0,0 +1,15 @@
1
+ module ClientSideValidations::ActiveModel
2
+ module Exclusion
3
+
4
+ def client_side_hash(model, attribute)
5
+ hash = super
6
+ if hash[:in].is_a?(Range)
7
+ hash[:range] = hash[:in]
8
+ hash.delete(:in)
9
+ end
10
+ hash
11
+ end
12
+
13
+ end
14
+ end
15
+
@@ -0,0 +1,10 @@
1
+ module ClientSideValidations::ActiveModel
2
+ module Format
3
+ private
4
+
5
+ def message_type
6
+ :invalid
7
+ end
8
+ end
9
+ end
10
+
@@ -0,0 +1,15 @@
1
+ module ClientSideValidations::ActiveModel
2
+ module Inclusion
3
+
4
+ def client_side_hash(model, attribute)
5
+ hash = super
6
+ if hash[:in].is_a?(Range)
7
+ hash[:range] = hash[:in]
8
+ hash.delete(:in)
9
+ end
10
+ hash
11
+ end
12
+
13
+ end
14
+ end
15
+
@@ -0,0 +1,24 @@
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]
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
+ hash
20
+ end
21
+
22
+ end
23
+ end
24
+
@@ -0,0 +1,31 @@
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
+ hash
27
+ end
28
+
29
+ end
30
+ end
31
+
@@ -0,0 +1,10 @@
1
+ module ClientSideValidations::ActiveModel
2
+ module Presence
3
+ private
4
+
5
+ def message_type
6
+ :blank
7
+ end
8
+ end
9
+ end
10
+
@@ -0,0 +1,60 @@
1
+ require 'client_side_validations/core_ext'
2
+
3
+ module ClientSideValidations::ActiveModel
4
+ module Validator
5
+
6
+ def client_side_hash(model, attribute)
7
+ options = self.options.dup
8
+ { :message => model.errors.generate_message(attribute, message_type, options) }.merge(options.except(*::ActiveModel::Errors::CALLBACKS_OPTIONS - [:allow_blank, :if, :unless]))
9
+ end
10
+
11
+ private
12
+
13
+ def message_type
14
+ kind
15
+ end
16
+ end
17
+
18
+ module Validations
19
+ def client_side_validation_hash
20
+ @client_side_validation_hash ||= _validators.inject({}) do |attr_hash, attr|
21
+ unless [nil, :block].include?(attr[0])
22
+
23
+ validator_hash = attr[1].inject({}) do |kind_hash, validator|
24
+ client_side_hash = validator.client_side_hash(self, attr[0])
25
+ # Yeah yeah, #new_record? is not part of ActiveModel :p
26
+ if (can_use_for_client_side_validation?(client_side_hash, validator))
27
+ kind_hash.merge!(validator.kind => client_side_hash.except(:on))
28
+ else
29
+ kind_hash.merge!({})
30
+ end
31
+ end
32
+
33
+ if validator_hash.present?
34
+ attr_hash.merge!(attr[0] => validator_hash)
35
+ else
36
+ attr_hash
37
+ end
38
+ else
39
+ attr_hash
40
+ end
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def can_use_for_client_side_validation?(client_side_hash, validator)
47
+ ((self.respond_to?(:new_record?) && validator.options[:on] == (self.new_record? ? :create : :update)) || validator.options[:on].nil?) && validator.kind != :block
48
+ end
49
+ end
50
+ end
51
+
52
+ ActiveModel::Validator.send(:include, ClientSideValidations::ActiveModel::Validator)
53
+ ActiveModel::Validations.send(:include, ClientSideValidations::ActiveModel::Validations)
54
+
55
+ %w{acceptance exclusion inclusion length format numericality presence}.each do |validator|
56
+ require "client_side_validations/active_model/#{validator}"
57
+ validator.capitalize!
58
+ eval "ActiveModel::Validations::#{validator}Validator.send(:include, ClientSideValidations::ActiveModel::#{validator})"
59
+ end
60
+
@@ -0,0 +1,33 @@
1
+ module ClientSideValidations::ActiveRecord
2
+ class Middleware
3
+
4
+ def self.is_unique?(klass, attribute, value, params)
5
+ column = klass.columns_hash[attribute.to_s]
6
+ value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s if column.text?
7
+
8
+ t = klass.arel_table
9
+
10
+ if params[:case_sensitive] == 'true'
11
+ if t.engine.connection.instance_variable_get("@config")[:adapter] == 'mysql'
12
+ relation = Arel::Nodes::SqlLiteral.new("BINARY #{t[attribute].eq(value).to_sql}")
13
+ else
14
+ relation = t[attribute].eq(value)
15
+ end
16
+ else
17
+ relation = t[attribute].matches(value)
18
+ end
19
+
20
+ if relation.is_a?(Arel::Nodes::SqlLiteral)
21
+ relation = Arel::Nodes::SqlLiteral.new("BINARY #{t[attribute].eq(value).to_sql} AND #{t.primary_key.not_eq(params[:id]).to_sql}")
22
+ else
23
+ relation = relation.and(t.primary_key.not_eq(params[:id])) if params[:id]
24
+ end
25
+
26
+ (params[:scope] || {}).each do |key, value|
27
+ relation = relation.and(t[key].eq(value))
28
+ end
29
+
30
+ !klass.where(relation).exists?
31
+ end
32
+ end
33
+ 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,11 @@
1
+ require 'client_side_validations/active_model'
2
+ require 'client_side_validations/active_record/middleware'
3
+
4
+ %w{uniqueness}.each do |validator|
5
+ require "client_side_validations/active_record/#{validator}"
6
+ validator.capitalize!
7
+ eval "ActiveRecord::Validations::#{validator}Validator.send(:include, ClientSideValidations::ActiveRecord::#{validator})"
8
+ end
9
+
10
+ ActiveRecord::Base.send(:include, ClientSideValidations::ActiveModel::Validations)
11
+
@@ -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,14 @@
1
+ class Regexp
2
+ def as_json(options = nil)
3
+ Regexp.new inspect.sub("\\A","^").sub("\\Z","$").sub("\\z","$").sub(/^\//,"").sub(/\/[a-z]*$/,""), 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,3 @@
1
+ require 'active_support/json'
2
+ require 'client_side_validations/core_ext/range'
3
+ require 'client_side_validations/core_ext/regexp'
@@ -0,0 +1,6 @@
1
+ module ClientSideValidations
2
+ class Engine < ::Rails::Engine
3
+ config.app_middleware.use ClientSideValidations::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 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,21 @@
1
+ module ClientSideValidations
2
+ module Formtastic
3
+ module FormBuilder
4
+
5
+ def self.included(base)
6
+ base.class_eval do
7
+ def self.client_side_form_settings(options, form_helper)
8
+ {
9
+ :type => self.to_s,
10
+ :inline_error_class => ::Formtastic::FormBuilder.default_inline_error_class
11
+ }
12
+ end
13
+ end
14
+ end
15
+
16
+ end
17
+ end
18
+ end
19
+
20
+ Formtastic::FormBuilder.send(:include, ClientSideValidations::Formtastic::FormBuilder)
21
+