client_side_validations 3.1.0 → 3.2.0

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 (111) hide show
  1. data/client_side_validations.gemspec +4 -16
  2. data/lib/client_side_validations/action_view/form_builder.rb +31 -79
  3. data/lib/client_side_validations/action_view/form_helper.rb +59 -24
  4. data/lib/client_side_validations/active_model/exclusion.rb +14 -2
  5. data/lib/client_side_validations/active_model/format.rb +21 -0
  6. data/lib/client_side_validations/active_model/inclusion.rb +13 -2
  7. data/lib/client_side_validations/active_model/length.rb +4 -2
  8. data/lib/client_side_validations/active_model/numericality.rb +12 -1
  9. data/lib/client_side_validations/active_model.rb +100 -14
  10. data/lib/client_side_validations/active_record/middleware.rb +29 -3
  11. data/lib/client_side_validations/active_record/uniqueness.rb +4 -3
  12. data/lib/client_side_validations/active_record.rb +2 -1
  13. data/lib/client_side_validations/config.rb +9 -0
  14. data/lib/client_side_validations/core_ext/regexp.rb +1 -2
  15. data/lib/client_side_validations/generators/rails_validations.rb +15 -0
  16. data/lib/client_side_validations/generators.rb +12 -0
  17. data/lib/client_side_validations/middleware.rb +63 -24
  18. data/lib/client_side_validations/version.rb +1 -1
  19. data/lib/client_side_validations.rb +2 -2
  20. data/lib/generators/client_side_validations/copy_assets_generator.rb +58 -0
  21. data/lib/generators/client_side_validations/install_generator.rb +9 -21
  22. data/lib/generators/templates/client_side_validations/initializer.rb +2 -2
  23. data/vendor/assets/javascripts/rails.validations.js +474 -282
  24. metadata +156 -301
  25. data/lib/client_side_validations/formtastic.rb +0 -21
  26. data/lib/client_side_validations/mongo_mapper/middleware.rb +0 -20
  27. data/lib/client_side_validations/mongo_mapper/uniqueness.rb +0 -28
  28. data/lib/client_side_validations/mongo_mapper.rb +0 -9
  29. data/lib/client_side_validations/mongoid/middleware.rb +0 -20
  30. data/lib/client_side_validations/mongoid/uniqueness.rb +0 -28
  31. data/lib/client_side_validations/mongoid.rb +0 -9
  32. data/lib/client_side_validations/simple_form.rb +0 -24
  33. data/lib/generators/client_side_validations/copy_asset_generator.rb +0 -23
  34. data/lib/generators/templates/client_side_validations/README.rails.3.0 +0 -6
  35. data/lib/generators/templates/client_side_validations/README.rails.3.1 +0 -7
  36. data/test/action_view/cases/helper.rb +0 -176
  37. data/test/action_view/cases/test_helpers.rb +0 -600
  38. data/test/action_view/cases/test_legacy_helpers.rb +0 -217
  39. data/test/action_view/models/comment.rb +0 -35
  40. data/test/action_view/models/post.rb +0 -35
  41. data/test/action_view/models.rb +0 -3
  42. data/test/active_model/cases/helper.rb +0 -4
  43. data/test/active_model/cases/test_acceptance_validator.rb +0 -16
  44. data/test/active_model/cases/test_base.rb +0 -11
  45. data/test/active_model/cases/test_confirmation_validator.rb +0 -16
  46. data/test/active_model/cases/test_exclusion_validator.rb +0 -20
  47. data/test/active_model/cases/test_format_validator.rb +0 -21
  48. data/test/active_model/cases/test_inclusion_validator.rb +0 -21
  49. data/test/active_model/cases/test_length_validator.rb +0 -61
  50. data/test/active_model/cases/test_numericality_validator.rb +0 -46
  51. data/test/active_model/cases/test_presence_validator.rb +0 -16
  52. data/test/active_model/cases/test_validations.rb +0 -175
  53. data/test/active_model/models/person.rb +0 -17
  54. data/test/active_record/cases/helper.rb +0 -12
  55. data/test/active_record/cases/test_base.rb +0 -11
  56. data/test/active_record/cases/test_middleware.rb +0 -175
  57. data/test/active_record/cases/test_uniqueness_validator.rb +0 -50
  58. data/test/active_record/models/guid.rb +0 -7
  59. data/test/active_record/models/user.rb +0 -14
  60. data/test/base_helper.rb +0 -8
  61. data/test/core_ext/cases/test_core_ext.rb +0 -46
  62. data/test/formtastic/cases/helper.rb +0 -7
  63. data/test/formtastic/cases/test_form_builder.rb +0 -11
  64. data/test/formtastic/cases/test_form_helper.rb +0 -21
  65. data/test/generators/cases/test_generators.rb +0 -31
  66. data/test/javascript/config.ru +0 -3
  67. data/test/javascript/public/test/callbacks/elementAfter.js +0 -54
  68. data/test/javascript/public/test/callbacks/elementBefore.js +0 -54
  69. data/test/javascript/public/test/callbacks/elementFail.js +0 -70
  70. data/test/javascript/public/test/callbacks/elementPass.js +0 -70
  71. data/test/javascript/public/test/callbacks/formAfter.js +0 -45
  72. data/test/javascript/public/test/callbacks/formBefore.js +0 -45
  73. data/test/javascript/public/test/callbacks/formFail.js +0 -51
  74. data/test/javascript/public/test/callbacks/formPass.js +0 -50
  75. data/test/javascript/public/test/form_builders/validateForm.js +0 -66
  76. data/test/javascript/public/test/form_builders/validateFormtastic.js +0 -54
  77. data/test/javascript/public/test/form_builders/validateNestedForm.js +0 -66
  78. data/test/javascript/public/test/form_builders/validateSimpleForm.js +0 -57
  79. data/test/javascript/public/test/settings.js +0 -15
  80. data/test/javascript/public/test/validateElement.js +0 -179
  81. data/test/javascript/public/test/validators/acceptance.js +0 -42
  82. data/test/javascript/public/test/validators/confirmation.js +0 -25
  83. data/test/javascript/public/test/validators/exclusion.js +0 -41
  84. data/test/javascript/public/test/validators/format.js +0 -27
  85. data/test/javascript/public/test/validators/inclusion.js +0 -42
  86. data/test/javascript/public/test/validators/length.js +0 -76
  87. data/test/javascript/public/test/validators/numericality.js +0 -142
  88. data/test/javascript/public/test/validators/presence.js +0 -21
  89. data/test/javascript/public/test/validators/uniqueness.js +0 -96
  90. data/test/javascript/public/vendor/jquery.metadata.js +0 -122
  91. data/test/javascript/public/vendor/qunit.css +0 -196
  92. data/test/javascript/public/vendor/qunit.js +0 -1374
  93. data/test/javascript/server.rb +0 -84
  94. data/test/javascript/views/index.erb +0 -20
  95. data/test/javascript/views/layout.erb +0 -21
  96. data/test/middleware/cases/helper.rb +0 -18
  97. data/test/middleware/cases/test_middleware.rb +0 -8
  98. data/test/mongo_mapper/cases/helper.rb +0 -9
  99. data/test/mongo_mapper/cases/test_base.rb +0 -15
  100. data/test/mongo_mapper/cases/test_middleware.rb +0 -77
  101. data/test/mongo_mapper/cases/test_uniqueness_validator.rb +0 -50
  102. data/test/mongo_mapper/models/magazine.rb +0 -11
  103. data/test/mongoid/cases/helper.rb +0 -16
  104. data/test/mongoid/cases/test_base.rb +0 -15
  105. data/test/mongoid/cases/test_middleware.rb +0 -77
  106. data/test/mongoid/cases/test_uniqueness_validator.rb +0 -49
  107. data/test/mongoid/models/book.rb +0 -12
  108. data/test/simple_form/cases/helper.rb +0 -5
  109. data/test/simple_form/cases/test_form_builder.rb +0 -14
  110. data/test/simple_form/cases/test_form_helper.rb +0 -24
  111. data/test/test_loader.rb +0 -6
@@ -13,30 +13,18 @@ Gem::Specification.new do |s|
13
13
  s.description = %q{Client Side Validations}
14
14
 
15
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
16
  s.require_paths = ["lib"]
19
17
 
20
- s.add_development_dependency 'rails', '3.1.0.rc4'
18
+ s.add_development_dependency 'rails', '~> 3.2.0'
21
19
  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
20
  s.add_development_dependency 'mocha'
26
- s.add_development_dependency 'simple_form'
27
- s.add_development_dependency 'formtastic', '~> 2.0.0.rc3'
21
+ s.add_development_dependency 'm'
28
22
 
29
23
  # For QUnit testing
30
24
  s.add_development_dependency 'sinatra', '~> 1.0'
31
25
  s.add_development_dependency 'shotgun'
32
26
  s.add_development_dependency 'thin'
33
27
  s.add_development_dependency 'json'
34
-
35
- ruby_minor_version = RUBY_VERSION.split('.')[1].to_i
36
- if ruby_minor_version == 8
37
- s.add_development_dependency 'minitest'
38
- s.add_development_dependency 'ruby-debug'
39
- elsif ruby_minor_version == 9
40
- s.add_development_dependency 'ruby-debug19'
41
- end
28
+ s.add_development_dependency 'coffee-script'
29
+ s.add_development_dependency 'jquery-rails'
42
30
  end
@@ -5,7 +5,7 @@ module ClientSideValidations::ActionView::Helpers
5
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
6
  base.class_eval <<-RUBY_EVAL
7
7
  def #{selector}_with_client_side_validations(method, options = {})
8
- apply_client_side_validators(method, options)
8
+ build_validation_options(method, options)
9
9
  options.delete(:validate)
10
10
  #{selector}_without_client_side_validations(method, options)
11
11
  end
@@ -24,9 +24,9 @@ module ClientSideValidations::ActionView::Helpers
24
24
  alias_method_chain :grouped_collection_select, :client_side_validations
25
25
  alias_method_chain :time_zone_select, :client_side_validations
26
26
 
27
- def self.client_side_form_settings(options, form_helper)
27
+ def client_side_form_settings(options, form_helper)
28
28
  {
29
- :type => self.to_s,
29
+ :type => self.class.to_s,
30
30
  :input_tag => form_helper.class.field_error_proc.call(%{<span id="input_tag" />}, Struct.new(:error_message, :tag_id).new([], "")),
31
31
  :label_tag => form_helper.class.field_error_proc.call(%{<label id="label_tag" />}, Struct.new(:error_message, :tag_id).new([], ""))
32
32
  }
@@ -34,9 +34,17 @@ module ClientSideValidations::ActionView::Helpers
34
34
  end
35
35
  end
36
36
 
37
+ def validate(*attrs)
38
+ options = attrs.pop if attrs.last.is_a?(Hash)
39
+ (attrs.present? ? attrs : @object._validators.keys).each do |attr|
40
+ build_validation_options(attr, :validate => options)
41
+ end
42
+ nil
43
+ end
44
+
37
45
  def initialize_with_client_side_validations(object_name, object, template, options, proc)
38
46
  initialize_without_client_side_validations(object_name, object, template, options, proc)
39
- @options[:validators] = {}
47
+ @options[:validators] = { object => {} }
40
48
  end
41
49
 
42
50
  def fields_for_with_client_side_validations(record_or_name_or_array, *args, &block)
@@ -46,106 +54,50 @@ module ClientSideValidations::ActionView::Helpers
46
54
  end
47
55
 
48
56
  def check_box_with_client_side_validations(method, options = {}, checked_value = "1", unchecked_value = "0")
49
- apply_client_side_validators(method, options)
57
+ build_validation_options(method, options)
58
+ options.delete(:validate)
50
59
  check_box_without_client_side_validations(method, options, checked_value, unchecked_value)
51
60
  end
52
61
 
53
62
  def radio_button_with_client_side_validations(method, tag_value, options = {})
54
- apply_client_side_validators(method, options)
63
+ build_validation_options(method, options)
64
+ options.delete(:validate)
55
65
  radio_button_without_client_side_validations(method, tag_value, options)
56
66
  end
57
67
 
58
68
  def select_with_client_side_validations(method, choices, options = {}, html_options = {})
59
- apply_client_side_validators(method, html_options)
69
+ build_validation_options(method, html_options.merge(:name => options[:name]))
70
+ html_options.delete(:validate)
60
71
  select_without_client_side_validations(method, choices, options, html_options)
61
72
  end
62
73
 
63
74
  def collection_select_with_client_side_validations(method, collection, value_method, text_method, options = {}, html_options = {})
64
- apply_client_side_validators(method, html_options)
75
+ build_validation_options(method, html_options.merge(:name => options[:name]))
76
+ html_options.delete(:validate)
65
77
  collection_select_without_client_side_validations(method, collection, value_method, text_method, options, html_options)
66
78
  end
67
79
 
68
80
  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)
81
+ build_validation_options(method, html_options.merge(:name => options[:name]))
82
+ html_options.delete(:validate)
70
83
  grouped_collection_select_without_client_side_validations(method, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
71
84
  end
72
85
 
73
86
  def time_zone_select_with_client_side_validations(method, priority_zones = nil, options = {}, html_options = {})
74
- apply_client_side_validators(method, html_options)
87
+ build_validation_options(method, html_options.merge(:name => options[:name]))
88
+ html_options.delete(:validate)
75
89
  time_zone_select_without_client_side_validations(method, priority_zones = nil, options, html_options)
76
90
  end
77
91
 
78
92
  private
79
93
 
80
- def apply_client_side_validators(method, options = {})
81
- if @options[:validate] && options[:validate] != false && validators = filter_validators(@object.client_side_validation_hash[method], options[:validate])
82
- options.merge!("data-validate" => true)
83
- @options[:validators].merge!("#{@object_name}[#{method}]#{options[:multiple] ? "[]" : nil}" => validators)
84
- end
85
- end
86
-
87
- def filter_validators(validators, filters)
88
- if validators
89
- filtered_validators = validators.inject({}) do |filtered_validators, validator|
90
- filtered_validators[validator.first] = validator.last
91
- if has_filter_for_validator?(validator, filters)
92
- if filter_validator?(validator, filters)
93
- filtered_validators.delete(validator.first)
94
- elsif force_validator_despite_conditional?(validator, filters) && !can_run_validator?(validator)
95
- filtered_validators.delete(validator.first)
96
- end
97
- else
98
- if validator.last.key?(:if) || validator.last.key?(:unless)
99
- filtered_validators.delete(validator.first)
100
- end
101
- end
102
- filtered_validators[validator.first].delete(:if) if filtered_validators[validator.first]
103
- filtered_validators[validator.first].delete(:unless) if filtered_validators[validator.first]
104
- filtered_validators
105
- end
106
-
107
- filtered_validators.empty? ? nil : filtered_validators
108
- end
109
- end
110
-
111
- def has_filter_for_validator?(validator, filters)
112
- filters && (filters == true || filters.key?(validator.first))
113
- end
114
-
115
- def filter_validator?(validator, filters)
116
- filters != true && filters[validator.first] == false
117
- end
118
-
119
- def force_validator_despite_conditional?(validator, filters)
120
- filters == true || filters[validator.first] == true
121
- end
122
-
123
- def can_run_validator?(validator)
124
- result = true
125
- if_result = can_run_if_validator?(validator.last[:if])
126
- unless_result = can_run_unless_validator?(validator.last[:unless])
127
- result = result && if_result unless if_result.nil?
128
- result = result && unless_result unless unless_result.nil?
129
- result
130
- end
131
-
132
- def can_run_if_validator?(conditional)
133
- if conditional
134
- if conditional.is_a?(Symbol)
135
- !!@object.send(conditional)
136
- else
137
- !!conditional.call(@object)
138
- end
139
- end
140
- end
141
-
142
- def can_run_unless_validator?(conditional)
143
- if conditional
144
- if conditional.is_a?(Symbol)
145
- !@object.send(conditional)
146
- else
147
- !conditional.call(@object)
148
- end
94
+ def build_validation_options(method, options = {})
95
+ if @options[:validate]
96
+ name = options[:name] || "#{@object_name}[#{method}]"
97
+ child_index = @options[:child_index] ? "(\\d+|#{Regexp.escape(@options[:child_index])})" : "\\d+"
98
+ name = name.to_s.gsub(/_attributes\]\[#{child_index}\]/, '_attributes][]')
99
+ name = "#{name}#{options[:multiple] ? "[]" : nil}"
100
+ @options[:validators][@object][method] = { :name => name, :options => options[:validate] }
149
101
  end
150
102
  end
151
103
  end
@@ -2,38 +2,46 @@ module ClientSideValidations::ActionView::Helpers
2
2
  module FormHelper
3
3
  class Error < StandardError; end
4
4
 
5
- def form_for(record_or_name_or_array, *args, &proc)
5
+ def form_for(record, *args, &proc)
6
6
  options = args.extract_options!
7
7
  if options[:validate]
8
8
 
9
- content_for_name = options[:validate] unless options[:validate] == true
10
-
11
9
  # Always turn off HTML5 Validations
12
10
  options[:html] ||= {}
13
11
  options[:html][:novalidate] = 'novalidate'
14
12
 
15
- case record_or_name_or_array
13
+ case record
16
14
  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
15
+ raise ClientSideValidations::ActionView::Helpers::FormHelper::Error, 'Using form_for(:name, @resource) is not supported with ClientSideValidations. Please use form_for(@resource, :as => :name) instead.'
20
16
  else
21
- object = record_or_name_or_array
17
+ object = record.is_a?(Array) ? record.last : record
22
18
  end
23
19
  end
24
20
 
25
21
  @validators = {}
22
+
26
23
  # Order matters here. Rails mutates the options object
24
+ html_id = options[:html][:id] if options[:html]
25
+ form = super(record, *(args << options), &proc)
26
+ options[:id] = html_id if html_id
27
27
  script = client_side_form_settings(object, options)
28
- form = super(record_or_name_or_array, *(args << options), &proc)
28
+
29
29
  # Because of the load order requirement above this sub is necessary
30
30
  # Would be nice to not do this
31
31
  script = insert_validators_into_script(script)
32
- if content_for_name
33
- content_for(content_for_name) { script.html_safe }
34
- script = nil
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
35
44
  end
36
- "#{form}#{script}".html_safe
37
45
  end
38
46
 
39
47
  def apply_form_for_options!(object_or_array, options)
@@ -41,9 +49,9 @@ module ClientSideValidations::ActionView::Helpers
41
49
  options[:html][:validate] = true if options[:validate]
42
50
  end
43
51
 
44
- def fields_for(record_or_name_or_array, *args, &block)
52
+ def fields_for(record_or_name_or_array, record_object = nil, options = {}, &block)
45
53
  output = super
46
- @validators.merge!(args.last[:validators]) if @validators
54
+ @validators.merge!(options[:validators]) if @validators
47
55
  output
48
56
  end
49
57
 
@@ -54,30 +62,57 @@ module ClientSideValidations::ActionView::Helpers
54
62
  # But using String#sub has some issues. Undocumented "features"
55
63
  if script
56
64
  script = script.split(/"validator_hash"/)
57
- script = "#{script[0]}#{@validators.to_json}#{script[1]}"
65
+ script = "#{script[0]}#{construct_validators.to_json}#{script[1]}"
58
66
  end
59
67
 
60
68
  script
61
69
  end
62
70
 
71
+ def construct_validators
72
+ @validators.inject({}) do |validator_hash, object_opts|
73
+ option_hash = object_opts[1].inject({}) do |option_hash, attr|
74
+ option_hash.merge!(attr[0] => attr[1][:options])
75
+ end
76
+
77
+ validation_hash = object_opts[0].client_side_validation_hash(option_hash)
78
+
79
+ option_hash.each_key do |attr|
80
+ if validation_hash[attr]
81
+ validator_hash.merge!(object_opts[1][attr][:name] => validation_hash[attr])
82
+ end
83
+ end
84
+
85
+ validator_hash
86
+ end
87
+ end
88
+
63
89
  def client_side_form_settings(object, options)
64
90
  if options[:validate]
65
- builder = options[:builder] || ActionView::Base.default_form_builder
91
+ builder = options[:parent_builder]
66
92
 
67
- if options[:html] && options[:html][:id]
68
- var_name = options[:html][:id]
93
+ if options[:id]
94
+ var_name = options[:id]
69
95
  else
70
- var_name = if object.respond_to?(:persisted?) && object.persisted?
71
- options[:as] ? "#{options[:as]}_edit" : dom_id(object, :edit)
96
+ if Rails.version >= '3.2.0'
97
+ var_name = if object.respond_to?(:persisted?) && object.persisted?
98
+ options[:as] ? "edit_#{options[:as]}" : [options[:namespace], dom_id(object, :edit)].compact.join("_")
99
+ else
100
+ options[:as] ? "new_#{options[:as]}" : [options[:namespace], dom_id(object)].compact.join("_")
101
+ end
72
102
  else
73
- options[:as] ? "#{options[:as]}_new" : dom_id(object)
103
+ # This is to maintain backward compatibility with Rails 3.1
104
+ # see: https://github.com/rails/rails/commit/e29773f885fd500189ffd964550ae20061d745ba#commitcomment-948052
105
+ var_name = if object.respond_to?(:persisted?) && object.persisted?
106
+ options[:as] ? "#{options[:as]}_edit" : dom_id(object, :edit)
107
+ else
108
+ options[:as] ? "#{options[:as]}_new" : dom_id(object)
109
+ end
74
110
  end
75
111
  end
76
112
 
77
113
  content_tag(:script) do
78
- "window['#{var_name}'] = #{builder.client_side_form_settings(options, self).merge(:validators => 'validator_hash').to_json};".html_safe
114
+ "//<![CDATA[\nif(window.ClientSideValidations==undefined)window.ClientSideValidations={};if(window.ClientSideValidations.forms==undefined)window.ClientSideValidations.forms={};window.ClientSideValidations.forms['#{var_name}'] = #{builder.client_side_form_settings(options, self).merge(:validators => 'validator_hash').to_json};\n//]]>".html_safe
79
115
  end
80
-
81
116
  end
82
117
  end
83
118
 
@@ -1,12 +1,24 @@
1
1
  module ClientSideValidations::ActiveModel
2
2
  module Exclusion
3
3
 
4
- def client_side_hash(model, attribute)
5
- hash = super
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
+
6
17
  if hash[:in].is_a?(Range)
7
18
  hash[:range] = hash[:in]
8
19
  hash.delete(:in)
9
20
  end
21
+
10
22
  hash
11
23
  end
12
24
 
@@ -1,5 +1,26 @@
1
1
  module ClientSideValidations::ActiveModel
2
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
+
3
24
  private
4
25
 
5
26
  def message_type
@@ -1,8 +1,19 @@
1
1
  module ClientSideValidations::ActiveModel
2
2
  module Inclusion
3
3
 
4
- def client_side_hash(model, attribute)
5
- hash = super
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
+
6
17
  if hash[:in].is_a?(Range)
7
18
  hash[:range] = hash[:in]
8
19
  hash.delete(:in)
@@ -1,7 +1,7 @@
1
1
  module ClientSideValidations::ActiveModel
2
2
  module Length
3
3
 
4
- def client_side_hash(model, attribute)
4
+ def client_side_hash(model, attribute, force = nil)
5
5
  options = self.options.dup
6
6
  hash = { :messages => {} }
7
7
  hash[:js_tokenizer] = options[:js_tokenizer] if options[:js_tokenizer]
@@ -9,13 +9,15 @@ module ClientSideValidations::ActiveModel
9
9
 
10
10
  self.class::MESSAGES.each do |option, message_type|
11
11
  if count = options[option]
12
- options[:message] = options[message_type]
12
+ options[:message] = options[message_type] if options[message_type].present?
13
13
  options.delete(:message) if options[:message].nil?
14
14
  hash[:messages][option] = model.errors.generate_message(attribute, message_type, options.merge(:count => count))
15
15
  hash[option] = count
16
16
  end
17
17
  end
18
18
 
19
+ copy_conditional_attributes(hash, options)
20
+
19
21
  hash
20
22
  end
21
23
 
@@ -7,7 +7,7 @@ module ClientSideValidations::ActiveModel
7
7
  OPTION_MAP.merge!(base::CHECKS.keys.inject({}) { |hash, key| hash.merge!(key => key) })
8
8
  end
9
9
 
10
- def client_side_hash(model, attribute)
10
+ def client_side_hash(model, attribute, force = nil)
11
11
  options = self.options.dup
12
12
  hash = { :messages => { :numericality => model.errors.generate_message(attribute, :not_a_number, options) } }
13
13
 
@@ -16,13 +16,24 @@ module ClientSideValidations::ActiveModel
16
16
  hash[:only_integer] = true
17
17
  end
18
18
 
19
+ hash[:allow_blank] = true if options[:allow_nil]
20
+
19
21
  OPTION_MAP.each do |option, message_type|
20
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
21
30
  hash[:messages][option] = model.errors.generate_message(attribute, message_type, options.merge(:count => count))
22
31
  hash[option] = count
23
32
  end
24
33
  end
25
34
 
35
+ copy_conditional_attributes(hash, options)
36
+
26
37
  hash
27
38
  end
28
39
 
@@ -3,31 +3,38 @@ require 'client_side_validations/core_ext'
3
3
  module ClientSideValidations::ActiveModel
4
4
  module Validator
5
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]))
6
+ def client_side_hash(model, attribute, force = nil)
7
+ build_client_side_hash(model, attribute, self.options.dup)
8
+ end
9
+
10
+ def copy_conditional_attributes(to, from)
11
+ [:if, :unless].each { |key| to[key] = from[key] if from[key].present? }
9
12
  end
10
13
 
11
14
  private
12
15
 
16
+ def build_client_side_hash(model, attribute, options)
17
+ { :message => model.errors.generate_message(attribute, message_type, options) }.merge(options.except(*::ActiveModel::Errors::CALLBACKS_OPTIONS - [:allow_blank, :if, :unless]))
18
+ end
19
+
13
20
  def message_type
14
21
  kind
15
22
  end
16
23
  end
17
24
 
18
25
  module Validations
19
- def client_side_validation_hash
20
- @client_side_validation_hash ||= _validators.inject({}) do |attr_hash, attr|
26
+ def client_side_validation_hash(force = nil)
27
+ _validators.inject({}) do |attr_hash, attr|
21
28
  unless [nil, :block].include?(attr[0])
22
29
 
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
+ validator_hash = attr[1].inject(Hash.new { |h,k| h[k] = []}) do |kind_hash, validator|
31
+ if can_use_for_client_side_validation?(attr[0], validator, force)
32
+ if client_side_hash = validator.client_side_hash(self, attr[0], extract_force_option(attr[0], force))
33
+ kind_hash[validator.kind] << client_side_hash.except(:on, :if, :unless)
34
+ end
30
35
  end
36
+
37
+ kind_hash
31
38
  end
32
39
 
33
40
  if validator_hash.present?
@@ -43,8 +50,87 @@ module ClientSideValidations::ActiveModel
43
50
 
44
51
  private
45
52
 
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
53
+ def extract_force_option(attr, force)
54
+ case force
55
+ when FalseClass, TrueClass, NilClass
56
+ force
57
+ when Hash
58
+ extract_force_option(nil, force[attr])
59
+ else
60
+ nil
61
+ end
62
+ end
63
+
64
+ def can_use_for_client_side_validation?(attr, validator, force)
65
+ if validator_turned_off?(attr, validator, force)
66
+ result = false
67
+ else
68
+ # Yeah yeah, #new_record? is not part of ActiveModel :p
69
+ result = ((self.respond_to?(:new_record?) && validator.options[:on] == (self.new_record? ? :create : :update)) || validator.options[:on].nil?)
70
+ result = result && validator.kind != :block
71
+
72
+ if validator.options[:if] || validator.options[:unless]
73
+ if result = can_force_validator?(attr, validator, force)
74
+ if validator.options[:if]
75
+ result = result && run_conditional(validator.options[:if])
76
+ end
77
+ if validator.options[:unless]
78
+ result = result && !run_conditional(validator.options[:unless])
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ result
85
+ end
86
+
87
+ def run_conditional(method_name_or_proc)
88
+ case method_name_or_proc
89
+ when Proc
90
+ method_name_or_proc.call(self)
91
+ else
92
+ self.send(method_name_or_proc)
93
+ end
94
+ end
95
+
96
+ def validator_turned_off?(attr, validator, force)
97
+ case force
98
+ when FalseClass
99
+ true
100
+ when Hash
101
+ case force[attr]
102
+ when FalseClass
103
+ true
104
+ when Hash
105
+ force[attr][validator.kind] == false
106
+ else
107
+ false
108
+ end
109
+ else
110
+ ::ClientSideValidations::Config.disabled_validators.include?(validator.kind)
111
+ end
112
+ end
113
+
114
+ def can_force_validator?(attr, validator, force)
115
+ case force
116
+ when TrueClass
117
+ true
118
+ when Hash
119
+ case force[attr]
120
+ when TrueClass
121
+ true
122
+ when Hash
123
+ force[attr][validator.kind]
124
+ else
125
+ false
126
+ end
127
+ else
128
+ if (validator.options[:if] || validator.options[:unless]) =~ /changed\?/
129
+ true
130
+ else
131
+ false
132
+ end
133
+ end
48
134
  end
49
135
  end
50
136
  end
@@ -1,14 +1,20 @@
1
1
  module ClientSideValidations::ActiveRecord
2
2
  class Middleware
3
3
 
4
+ def self.is_class?(klass)
5
+ klass < ::ActiveRecord::Base
6
+ end
7
+
4
8
  def self.is_unique?(klass, attribute, value, params)
9
+ klass = find_topmost_superclass(klass)
10
+ value = type_cast_value(klass, attribute, value)
5
11
  column = klass.columns_hash[attribute.to_s]
6
12
  value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s if column.text?
7
13
 
8
14
  t = klass.arel_table
9
15
 
10
16
  if params[:case_sensitive] == 'true'
11
- if t.engine.connection.instance_variable_get("@config")[:adapter] == 'mysql'
17
+ if t.engine.connection.instance_variable_get("@config")[:adapter] =~ /^mysql/
12
18
  relation = Arel::Nodes::SqlLiteral.new("BINARY #{t[attribute].eq(value).to_sql}")
13
19
  else
14
20
  relation = t[attribute].eq(value)
@@ -23,11 +29,31 @@ module ClientSideValidations::ActiveRecord
23
29
  relation = relation.and(t.primary_key.not_eq(params[:id])) if params[:id]
24
30
  end
25
31
 
26
- (params[:scope] || {}).each do |key, value|
27
- relation = relation.and(t[key].eq(value))
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
28
39
  end
29
40
 
30
41
  !klass.where(relation).exists?
31
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
+
32
58
  end
33
59
  end