andrewroth-client_side_validations 3.2.0.beta.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. data/client_side_validations.gemspec +28 -0
  2. data/lib/client_side_validations.rb +18 -0
  3. data/lib/client_side_validations/action_view.rb +14 -0
  4. data/lib/client_side_validations/action_view/form_builder.rb +176 -0
  5. data/lib/client_side_validations/action_view/form_helper.rb +105 -0
  6. data/lib/client_side_validations/action_view/form_tag_helper.rb +12 -0
  7. data/lib/client_side_validations/active_model.rb +68 -0
  8. data/lib/client_side_validations/active_model/acceptance.rb +10 -0
  9. data/lib/client_side_validations/active_model/exclusion.rb +15 -0
  10. data/lib/client_side_validations/active_model/format.rb +10 -0
  11. data/lib/client_side_validations/active_model/inclusion.rb +15 -0
  12. data/lib/client_side_validations/active_model/length.rb +26 -0
  13. data/lib/client_side_validations/active_model/numericality.rb +33 -0
  14. data/lib/client_side_validations/active_model/presence.rb +10 -0
  15. data/lib/client_side_validations/active_record.rb +12 -0
  16. data/lib/client_side_validations/active_record/middleware.rb +50 -0
  17. data/lib/client_side_validations/active_record/uniqueness.rb +28 -0
  18. data/lib/client_side_validations/core_ext.rb +3 -0
  19. data/lib/client_side_validations/core_ext/range.rb +10 -0
  20. data/lib/client_side_validations/core_ext/regexp.rb +14 -0
  21. data/lib/client_side_validations/engine.rb +6 -0
  22. data/lib/client_side_validations/files.rb +8 -0
  23. data/lib/client_side_validations/generators.rb +12 -0
  24. data/lib/client_side_validations/generators/rails_validations.rb +15 -0
  25. data/lib/client_side_validations/middleware.rb +105 -0
  26. data/lib/client_side_validations/version.rb +3 -0
  27. data/lib/generators/client_side_validations/copy_assets_generator.rb +56 -0
  28. data/lib/generators/client_side_validations/install_generator.rb +22 -0
  29. data/lib/generators/templates/client_side_validations/initializer.rb +11 -0
  30. data/vendor/assets/javascripts/rails.validations.js +424 -0
  31. metadata +213 -0
@@ -0,0 +1,28 @@
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 = "andrewroth-client_side_validations"
7
+ s.version = ClientSideValidations::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Brian Cardarella"]
10
+ s.email = ["bcardarella@gmail.com"]
11
+ s.homepage = "https://github.com/bcardarella/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.require_paths = ["lib"]
17
+
18
+ s.add_development_dependency 'rails', '~> 3.2.0'
19
+ s.add_development_dependency 'sqlite3'
20
+ s.add_development_dependency 'mocha'
21
+
22
+ # For QUnit testing
23
+ s.add_development_dependency 'sinatra', '~> 1.0'
24
+ s.add_development_dependency 'shotgun'
25
+ s.add_development_dependency 'thin'
26
+ s.add_development_dependency 'json'
27
+ s.add_development_dependency 'coffee-script'
28
+ end
@@ -0,0 +1,18 @@
1
+ module ClientSideValidations
2
+ module Config
3
+ class << self
4
+ attr_accessor :uniqueness_validator_disabled
5
+ @uniqueness_validator_disabled = false
6
+ end
7
+ end
8
+ end
9
+
10
+ require 'client_side_validations/active_model' if defined?(::ActiveModel)
11
+ require 'client_side_validations/active_record' if defined?(::ActiveRecord)
12
+ require 'client_side_validations/action_view' if defined?(::ActionView)
13
+ if defined?(::Rails)
14
+ require 'client_side_validations/generators'
15
+ require 'client_side_validations/middleware'
16
+ require 'client_side_validations/engine'
17
+ end
18
+
@@ -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,176 @@
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]))
102
+ result = case conditional
103
+ when Symbol
104
+ if @object.respond_to?(conditional)
105
+ @object.send(conditional)
106
+ else
107
+ raise(ArgumentError, "unknown method called '#{conditional}'")
108
+ end
109
+ when String
110
+ eval(conditional)
111
+ when Proc
112
+ conditional.call(@object)
113
+ end
114
+
115
+ # :if was specified and result is false OR :unless was specified and result was true
116
+ if (validator.last[:if] && !result) || (validator.last[:unless] && result)
117
+ unfiltered_validators.delete(validator.first)
118
+ end
119
+ end
120
+ end
121
+ unfiltered_validators[validator.first].delete(:if) if unfiltered_validators[validator.first]
122
+ unfiltered_validators[validator.first].delete(:unless) if unfiltered_validators[validator.first]
123
+ unfiltered_validators
124
+ end
125
+
126
+ unfiltered_validators.empty? ? nil : unfiltered_validators
127
+ end
128
+ end
129
+
130
+ def has_filter_for_validator?(validator, filters)
131
+ filters && (filters == true || filters.key?(validator.first))
132
+ end
133
+
134
+ def filter_validator?(validator, filters)
135
+ filters != true && filters[validator.first] == false
136
+ end
137
+
138
+ def force_validator_despite_conditional?(validator, filters)
139
+ filters == true || filters[validator.first] == true
140
+ end
141
+
142
+ def can_run_validator?(validator, method)
143
+ result = true
144
+ if_result = run_if_validator(validator.last[:if], method)
145
+ unless_result = run_unless_validator(validator.last[:unless], method)
146
+ result = result && if_result unless if_result.nil?
147
+ result = result && unless_result unless unless_result.nil?
148
+ result
149
+ end
150
+
151
+ def run_if_validator(conditional, method)
152
+ if conditional
153
+ if conditional.is_a?(Symbol)
154
+ conditional_method_is_change_method?(conditional, method) ? true : !!@object.send(conditional)
155
+ else
156
+ !!conditional.call(@object)
157
+ end
158
+ end
159
+ end
160
+
161
+ def run_unless_validator(conditional, method)
162
+ if conditional
163
+ if conditional.is_a?(Symbol)
164
+ conditional_method_is_change_method?(conditional, method) ? true : !@object.send(conditional)
165
+ else
166
+ !conditional.call(@object)
167
+ end
168
+ end
169
+ end
170
+
171
+ def conditional_method_is_change_method?(conditional, method)
172
+ conditional.to_sym == "#{method}_changed?".to_sym
173
+ end
174
+ end
175
+ end
176
+
@@ -0,0 +1,105 @@
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
+ # Always turn off HTML5 Validations
10
+ options[:html] ||= {}
11
+ options[:html][:novalidate] = 'novalidate'
12
+
13
+ case record_or_name_or_array
14
+ when String, Symbol
15
+ 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.'
16
+ when Array
17
+ object = record_or_name_or_array.last
18
+ else
19
+ object = record_or_name_or_array
20
+ end
21
+ end
22
+
23
+ @validators = {}
24
+
25
+ # Order matters here. Rails mutates the options object
26
+ script = client_side_form_settings(object, options)
27
+ form = super(record_or_name_or_array, *(args << options), &proc)
28
+
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
+
33
+ if assign_script_to_content_for(options[:validate], script)
34
+ form.html_safe
35
+ else
36
+ "#{form}#{script}".html_safe
37
+ end
38
+ end
39
+
40
+ def assign_script_to_content_for(name, script)
41
+ if name && name != true
42
+ content_for(name) { script.html_safe }
43
+ true
44
+ end
45
+ end
46
+
47
+ def apply_form_for_options!(object_or_array, options)
48
+ super
49
+ options[:html][:validate] = true if options[:validate]
50
+ end
51
+
52
+ def fields_for(record_or_name_or_array, *args, &block)
53
+ output = super
54
+ @validators.merge!(args.last[:validators]) if @validators
55
+ output
56
+ end
57
+
58
+ private
59
+
60
+ def insert_validators_into_script(script)
61
+ # There is probably a more performant way of doing this
62
+ # But using String#sub has some issues. Undocumented "features"
63
+ if script
64
+ script = script.split(/"validator_hash"/)
65
+ script = "#{script[0]}#{@validators.to_json}#{script[1]}"
66
+ end
67
+
68
+ script
69
+ end
70
+
71
+ def client_side_form_settings(object, options)
72
+ if options[:validate]
73
+ builder = options[:builder] || ActionView::Base.default_form_builder
74
+
75
+ if options[:html] && options[:html][:id]
76
+ var_name = options[:html][:id]
77
+ else
78
+ if Rails.version >= '3.2.0'
79
+ var_name = if object.respond_to?(:persisted?) && object.persisted?
80
+ options[:as] ? "edit_#{options[:as]}" : [options[:namespace], dom_id(object, :edit)].compact.join("_")
81
+ else
82
+ options[:as] ? "new_#{options[:as]}" : [options[:namespace], dom_id(object)].compact.join("_")
83
+ end
84
+ else
85
+ # This is to maintain backward compatibility with Rails 3.1
86
+ # see: https://github.com/rails/rails/commit/e29773f885fd500189ffd964550ae20061d745ba#commitcomment-948052
87
+ var_name = if object.respond_to?(:persisted?) && object.persisted?
88
+ options[:as] ? "#{options[:as]}_edit" : dom_id(object, :edit)
89
+ else
90
+ options[:as] ? "#{options[:as]}_new" : dom_id(object)
91
+ end
92
+ end
93
+
94
+
95
+ end
96
+
97
+ content_tag(:script) do
98
+ "//<![CDATA[\nwindow.ClientSideValidations.forms['#{var_name}'] = #{builder.client_side_form_settings(options, self).merge(:validators => 'validator_hash').to_json};\n//]]>".html_safe
99
+ end
100
+ end
101
+ end
102
+
103
+ end
104
+ end
105
+
@@ -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,68 @@
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
+ def copy_conditional_attributes(to, from)
12
+ [:if, :unless].each { |key| to[key] = from[key] if from[key].present? }
13
+ end
14
+
15
+ private
16
+
17
+ def message_type
18
+ kind
19
+ end
20
+ end
21
+
22
+ module Validations
23
+ def client_side_validation_hash
24
+ @client_side_validation_hash ||= _validators.inject({}) do |attr_hash, attr|
25
+ unless [nil, :block].include?(attr[0])
26
+
27
+ validator_hash = attr[1].inject({}) do |kind_hash, validator|
28
+ client_side_hash = validator.client_side_hash(self, attr[0])
29
+ # Yeah yeah, #new_record? is not part of ActiveModel :p
30
+ if can_use_for_client_side_validation?(client_side_hash, validator) && uniqueness_validations_allowed_or_not_applicable?(validator)
31
+ kind_hash.merge!(validator.kind => client_side_hash.except(:on))
32
+ else
33
+ kind_hash.merge!({})
34
+ end
35
+ end
36
+
37
+ if validator_hash.present?
38
+ attr_hash.merge!(attr[0] => validator_hash)
39
+ else
40
+ attr_hash
41
+ end
42
+ else
43
+ attr_hash
44
+ end
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def can_use_for_client_side_validation?(client_side_hash, validator)
51
+ ((self.respond_to?(:new_record?) && validator.options[:on] == (self.new_record? ? :create : :update)) || validator.options[:on].nil?) && validator.kind != :block
52
+ end
53
+
54
+ def uniqueness_validations_allowed_or_not_applicable?(validator)
55
+ validator.kind != :uniqueness || !ClientSideValidations::Config.uniqueness_validator_disabled
56
+ end
57
+ end
58
+ end
59
+
60
+ ActiveModel::Validator.send(:include, ClientSideValidations::ActiveModel::Validator)
61
+ ActiveModel::Validations.send(:include, ClientSideValidations::ActiveModel::Validations)
62
+
63
+ %w{acceptance exclusion inclusion length format numericality presence}.each do |validator|
64
+ require "client_side_validations/active_model/#{validator}"
65
+ validator.capitalize!
66
+ eval "ActiveModel::Validations::#{validator}Validator.send(:include, ClientSideValidations::ActiveModel::#{validator})"
67
+ end
68
+
@@ -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
+