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.
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
+