adamhunter-client_side_validations 2.9.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,60 @@
1
+ require 'rubygems'
2
+ require 'json'
3
+ require 'cgi'
4
+
5
+ module ClientSideValidations
6
+ def self.default_options=(options)
7
+ @default_options = options
8
+ end
9
+
10
+ def self.default_options
11
+ @default_options
12
+ end
13
+
14
+ class Uniqueness
15
+ def initialize(app)
16
+ @app = app
17
+ end
18
+
19
+ def call(env)
20
+ # By default CGI::parse will instantize a hash that defaults nil elements to [].
21
+ # We need to override this
22
+ case env['PATH_INFO']
23
+ when %r{^/validations/uniqueness.json}
24
+ params = {}.merge!(CGI::parse(env['QUERY_STRING']))
25
+ field = params.keys.first
26
+ resource, attribute = field.split(/[^\w]/)
27
+ value = params[field][0]
28
+ # Because params returns an array for each field value we want to always grab
29
+ # the first element of the array for id, even if it is nil
30
+ id = [params["#{resource}[id]"]].flatten.first
31
+ body = is_unique?(resource, attribute, value, id).to_s
32
+ [200, {'Content-Type' => 'application/json', 'Content-Length' => "#{body.length}"}, [body]]
33
+ else
34
+ @app.call(env)
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def is_unique?(resource, attribute, value, id = nil)
41
+ klass = constantize_resource(resource)
42
+ instance = nil
43
+ instance = klass.send("find_by_#{attribute}", value)
44
+
45
+ if instance
46
+ return instance.id.to_i == id.to_i
47
+ else
48
+ return true
49
+ end
50
+ end
51
+
52
+ def constantize_resource(resource)
53
+ eval(resource.split('_').map{ |word| word.capitalize}.join)
54
+ end
55
+ end
56
+ end
57
+
58
+ require 'client_side_validations/orm'
59
+ require 'client_side_validations/template'
60
+ require 'client_side_validations/rails' if defined?(Rails)
@@ -0,0 +1,153 @@
1
+ module ClientSideValidations
2
+ module Adapters
3
+ module ActionView
4
+ module BaseMethods
5
+
6
+ def self.included(base)
7
+ form_method = base.instance_method(:form_for)
8
+ base.class_eval do
9
+ attr_accessor :validate_options
10
+
11
+ define_method(:form_for) do |record_or_name_or_array, *args, &proc|
12
+ options = args.extract_options!.symbolize_keys!
13
+ script = ""
14
+ if validations = options.delete(:validations)
15
+ set_validate_options(validations, record_or_name_or_array, options)
16
+ unless options.has_key?(:html)
17
+ options[:html] = {}
18
+ end
19
+ options[:html]['data-csv'] = validate_options.delete('data-csv')
20
+ script = %{<script type='text/javascript'>var #{options[:html]['data-csv']}_validate_options=#{validate_options.to_json};</script>}
21
+ end
22
+ args << options
23
+ result = form_method.bind(self).call(record_or_name_or_array, *args, &proc)
24
+ if rails3?
25
+ result += script.html_safe
26
+ else
27
+ concat(script)
28
+ end
29
+ result
30
+ end
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def rails3?
37
+ version =
38
+ if defined?(ActionPack::VERSION::MAJOR)
39
+ ActionPack::VERSION::MAJOR
40
+ end
41
+ !version.blank? && version >= 3
42
+ end
43
+
44
+ def set_validate_options(validations, record_or_name_or_array, form_for_options)
45
+ options = ::ClientSideValidations.default_options || { }
46
+
47
+ case validations
48
+ when true
49
+ object_name = get_object_name(record_or_name_or_array, form_for_options)
50
+ data_csv = get_data_csv(record_or_name_or_array, form_for_options)
51
+ validate_options = rules_and_messages(record_or_name_or_array)
52
+ when Hash
53
+ validations.symbolize_keys!
54
+ if validations.has_key?(:instance)
55
+ object_name = get_object_name(validations[:instance], form_for_options)
56
+ data_csv = get_data_csv(validations[:instance], form_for_options)
57
+ validate_options = validations[:instance].validate_options
58
+ elsif validations.has_key?(:class)
59
+ object_name = get_object_name(validations[:class], form_for_options)
60
+ data_csv = get_data_csv(validations[:class], form_for_options)
61
+ validate_options = validations[:class].new.validate_options
62
+ else
63
+ object_name = get_object_name(record_or_name_or_array, form_for_options)
64
+ data_csv = get_data_csv(record_or_name_or_array, form_for_options)
65
+ validate_options = rules_and_messages(record_or_name_or_array)
66
+ end
67
+
68
+ options.merge!(validations[:options] || {})
69
+ else
70
+ object_name = get_object_name(validations, form_for_options)
71
+ data_csv = get_data_csv(validations, form_for_options)
72
+ validate_options = validations.new.validate_options
73
+ end
74
+
75
+ self.validate_options = build_validate_options(object_name, validate_options, options, data_csv)
76
+ end
77
+
78
+ def build_validate_options(object_name, validate_options, options, data_csv)
79
+ %w{rules messages}.each do |option|
80
+ validate_options[option].keys.each do |key|
81
+ validate_options[option]["#{object_name}[#{key}]"] = validate_options[option].delete(key)
82
+ end
83
+ end
84
+
85
+ validate_options['options'] = options unless options.empty?
86
+ validate_options['data-csv'] = data_csv
87
+ validate_options
88
+ end
89
+
90
+ def rules_and_messages(record_or_name_or_array)
91
+ case record_or_name_or_array
92
+ when String, Symbol
93
+ record_or_name_or_array.to_s.camelize.constantize.new.validate_options
94
+ when Array
95
+ rules_and_messages(record_or_name_or_array.last)
96
+ else
97
+ record_or_name_or_array.validate_options
98
+ end
99
+ end
100
+
101
+ def get_data_csv(object, form_for_options)
102
+ case object
103
+ when String, Symbol
104
+ form_for_options[:as] || object.to_s
105
+ when Array
106
+ get_data_csv(object.last, form_for_options)
107
+ when Class
108
+ form_for_options[:as] || object.to_s.underscore
109
+ else
110
+ if object.respond_to?(:persisted?) && object.persisted?
111
+ form_for_options[:as] ? "#{form_for_options[:as]}_edit" : dom_id(object)
112
+ else
113
+ form_for_options[:as] ? "#{form_for_options[:as]}_new" : dom_id(object)
114
+ end
115
+ end
116
+ end
117
+
118
+ def get_object_name(object, form_for_options)
119
+ case object
120
+ when String, Symbol
121
+ form_for_options[:as] || object.to_s
122
+ when Array
123
+ get_object_name(object.last, form_for_options)
124
+ when Class
125
+ get_object_name(object.new, form_for_options)
126
+ else
127
+ if rails3?
128
+ form_for_options[:as] || ::ActiveModel::Naming.singular(object)
129
+ else
130
+ form_for_options[:as] || ::ActionController::RecordIdentifier.singular_class_name(object)
131
+ end
132
+ end
133
+ end
134
+ end # BaseMethods
135
+
136
+ module FormBuilderMethods
137
+
138
+ def client_side_validations(options = {})
139
+ @template.send(:client_side_validations, @object_name, objectify_options(options))
140
+ end
141
+
142
+ end # FormBuilderMethods
143
+ end
144
+ end
145
+ end
146
+
147
+ ActionView::Base.class_eval do
148
+ include ClientSideValidations::Adapters::ActionView::BaseMethods
149
+ end
150
+
151
+ ActionView::Helpers::FormBuilder.class_eval do
152
+ include ClientSideValidations::Adapters::ActionView::FormBuilderMethods
153
+ end
@@ -0,0 +1,137 @@
1
+ require 'client_side_validations/adapters/orm_base'
2
+
3
+ module ClientSideValidations
4
+ module Adapters
5
+ class ActiveModel < ORMBase
6
+
7
+ def validations_to_hash(attr)
8
+ base._validators[attr.to_sym].inject({}) do |hash, validation|
9
+ validation.instance_variable_set('@options', validation.options.dup)
10
+ hash.merge!(build_validation_hash(validation.dup))
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def build_validation_hash(validation, message_key = 'message')
17
+ if validation.kind == :inclusion || validation.kind == :exclusion
18
+ validation.options[:in] = validation.options[:in].to_a
19
+ end
20
+
21
+ if validation.kind == :size
22
+ validation.kind = :length
23
+ end
24
+
25
+ if validation.kind == :length &&
26
+ (range = (validation.options.delete(:within) || validation.options.delete(:in)))
27
+ validation.options[:minimum] = range.first
28
+ validation.options[:maximum] = range.last
29
+ elsif validation.kind == :inclusion || validation.kind == :exclusion
30
+ validation.options[:in] = validation.options[:in].to_a
31
+ end
32
+
33
+ super
34
+ end
35
+
36
+ def supported_validation?(validation)
37
+ SupportedValidations.include?(validation.kind.to_sym)
38
+ end
39
+
40
+ def get_validation_message(validation)
41
+ default = case validation.kind
42
+ when :presence
43
+ I18n.translate(i18n_prefix + 'errors.messages.blank')
44
+ when :format
45
+ I18n.translate(i18n_prefix + 'errors.messages.invalid')
46
+ when :length
47
+ if count = validation.options[:is]
48
+ I18n.translate(i18n_prefix + 'errors.messages.wrong_length').sub(orm_error_interpolation(:count), count.to_s)
49
+ elsif count = validation.options[:minimum]
50
+ I18n.translate(i18n_prefix + 'errors.messages.too_short').sub(orm_error_interpolation(:count), count.to_s)
51
+ elsif count = validation.options[:maximum]
52
+ I18n.translate(i18n_prefix + 'errors.messages.too_long').sub(orm_error_interpolation(:count), count.to_s)
53
+ end
54
+ when :numericality
55
+ if validation.options[:only_integer]
56
+ I18n.translate(i18n_prefix + 'errors.messages.not_a_number')
57
+ elsif count = validation.options[:greater_than]
58
+ I18n.translate(i18n_prefix + 'errors.messages.greater_than').sub(orm_error_interpolation(:count), count.to_s)
59
+ elsif count = validation.options[:greater_than_or_equal_to]
60
+ I18n.translate(i18n_prefix + 'errors.messages.greater_than_or_equal_to').sub(orm_error_interpolation(:count), count.to_s)
61
+ elsif count = validation.options[:less_than]
62
+ I18n.translate(i18n_prefix + 'errors.messages.less_than').sub(orm_error_interpolation(:count), count.to_s)
63
+ elsif count = validation.options[:less_than_or_equal_to]
64
+ I18n.translate(i18n_prefix + 'errors.messages.less_than_or_equal_to').sub(orm_error_interpolation(:count), count.to_s)
65
+ elsif validation.options[:odd]
66
+ I18n.translate(i18n_prefix + 'errors.messages.odd')
67
+ elsif validation.options[:even]
68
+ I18n.translate(i18n_prefix + 'errors.messages.even')
69
+ else
70
+ I18n.translate(i18n_prefix + 'errors.messages.not_a_number')
71
+ end
72
+ when :uniqueness
73
+ if defined?(ActiveRecord) && base.kind_of?(ActiveRecord::Base)
74
+ I18n.translate('activerecord.errors.messages.taken')
75
+ elsif defined?(Mongoid) && base.class.included_modules.include?(Mongoid::Document)
76
+ if ruby18?
77
+ I18n.translate('errors.messages.taken')
78
+ else
79
+ I18n.translate('activemodel.errors.messages.taken')
80
+ end
81
+ end
82
+ when :confirmation
83
+ I18n.translate(i18n_prefix + 'errors.messages.confirmation')
84
+ when :acceptance
85
+ I18n.translate(i18n_prefix + 'errors.messages.accepted')
86
+ when :inclusion
87
+ I18n.translate(i18n_prefix + 'errors.messages.inclusion')
88
+ when :exclusion
89
+ I18n.translate(i18n_prefix + 'errors.messages.exclusion')
90
+ end
91
+
92
+ message = validation.options.delete(:message)
93
+ if message.kind_of?(String)
94
+ message
95
+ elsif message.kind_of?(Symbol)
96
+ I18n.translate(i18n_prefix + "errors.models.#{base.class.to_s.downcase}.attributes.#{validation.attributes.first}.#{message}")
97
+ else
98
+ default
99
+ end
100
+ end
101
+
102
+ def get_validation_method(validation)
103
+ validation.kind.to_s
104
+ end
105
+
106
+ def ruby18?
107
+ ruby_split[0] == '1' && ruby_split[1] == '8'
108
+ end
109
+
110
+ def ruby19?
111
+ ruby_split[0] == '1' && ruby_split[1] == '9'
112
+ end
113
+
114
+ def ruby_split
115
+ RUBY_VERSION.split('.')
116
+ end
117
+
118
+ def i18n_prefix
119
+ if defined?(::ActiveModel)
120
+ ''
121
+ else # ActiveRecord 2.x
122
+ 'activerecord.'
123
+ end
124
+ end
125
+
126
+ def orm_error_interpolation(name)
127
+ if defined?(::ActiveModel)
128
+ "%{#{name}}"
129
+
130
+ else # ActiveRecord 2.x
131
+ "{{#{name}}}"
132
+ end
133
+ end
134
+
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,89 @@
1
+ module ClientSideValidations
2
+ module Adapters
3
+ class ORMBase
4
+ attr_accessor :base
5
+
6
+ def initialize(base)
7
+ self.base = base
8
+ end
9
+
10
+ def validations_to_hash(attr)
11
+ end
12
+
13
+ def validation_fields
14
+ []
15
+ end
16
+
17
+ private
18
+
19
+ SupportedValidations = [:presence, :format, :length, :numericality, :uniqueness,
20
+ :confirmation, :acceptance, :inclusion, :exclusion]
21
+
22
+ def build_validation_hash(validation, message_key = 'message')
23
+ if can_validate?(validation)
24
+ validation_hash = {}
25
+ message = get_validation_message(validation)
26
+ options = get_validation_options(validation)
27
+ method = get_validation_method(validation)
28
+
29
+ if validation.options.has_key?(:minimum) && validation.options.has_key?(:maximum)
30
+ message_key = 'message_min'
31
+ cloned_validation = validation.clone
32
+ cloned_validation.options.delete(:minimum)
33
+ validation_hash.merge!(build_validation_hash(cloned_validation, 'message_max'))
34
+ end
35
+
36
+ new_validation_hash = { method => { message_key => message }.merge(options.stringify_keys) }
37
+
38
+ unless validation_hash.empty?
39
+ validation_hash["length"].merge!(new_validation_hash["length"])
40
+ validation_hash
41
+ else
42
+ new_validation_hash
43
+ end
44
+ else
45
+ {}
46
+ end
47
+ end
48
+
49
+ def can_validate?(validation)
50
+ if supported_validation?(validation)
51
+ if on = validation.options[:on]
52
+ on = on.to_sym
53
+ (on == :save) ||
54
+ (on == :create && base.new_record?) ||
55
+ (on == :update && !base.new_record?)
56
+ elsif(if_condition = (validation.options[:if] || validation.options[:allow_validation]))
57
+ base.instance_eval(if_condition.to_s)
58
+ elsif(unless_condition = (validation.options[:unless] || validation.options[:skip_validation]))
59
+ !base.instance_eval(unless_condition.to_s)
60
+ else
61
+ true
62
+ end
63
+ end
64
+ end
65
+
66
+ def get_validation_message(validation)
67
+ end
68
+
69
+ def get_validation_options(validation)
70
+ options = validation.options.clone
71
+ options.symbolize_keys!
72
+ deleteable_keys = [:on, :tokenizer, :allow_nil, :case_sensitive, :accept, :if, :unless, :allow_validation]
73
+ options.delete(:maximum) if options.has_key?(:minimum)
74
+ options.delete_if { |k, v| deleteable_keys.include?(k) }
75
+ if options[:with].kind_of?(Regexp)
76
+ options[:with] = options[:with].inspect.to_s.gsub("\\A","^").gsub("\\Z","$").sub(%r{^/},"").sub(%r{/i?$}, "")
77
+ end
78
+ if options[:only_integer] == false
79
+ options.delete(:only_integer)
80
+ end
81
+ options
82
+ end
83
+
84
+ def get_validation_method(validation)
85
+ end
86
+
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,226 @@
1
+ require 'client_side_validations/adapters/active_model'
2
+
3
+ module ClientSideValidations
4
+ module ORM
5
+ def validate_options
6
+ ValidateOptions.new(self).to_hash
7
+ end
8
+
9
+ def validation_fields
10
+ self._validators.keys
11
+ end
12
+
13
+ class ValidateOptions
14
+ attr_accessor :base
15
+
16
+ def initialize(base)
17
+ self.base = base
18
+ end
19
+
20
+ def to_hash
21
+ rules = Hash.new { |h, field| h[field] = {} }
22
+ messages = Hash.new { |h, field| h[field] = {} }
23
+ base.validation_fields.each do |field|
24
+ validations = validations_for(field)
25
+ rules[field.to_s].merge!(extract_rules(validations, field))
26
+ messages[field.to_s].merge!(extract_messages(validations))
27
+ end
28
+ {"rules" => rules, "messages" => messages}
29
+ end
30
+
31
+ def validations_for(field)
32
+ adapter.validations_to_hash(field)
33
+ end
34
+
35
+ private
36
+
37
+ def convert_kind(kind, options = nil)
38
+ case kind
39
+ when 'acceptance', 'exclusion', 'inclusion', 'format', 'confirmation'
40
+ kind
41
+ when 'presence'
42
+ 'required'
43
+ when 'numericality'
44
+ if options['only_integer']
45
+ 'digits'
46
+ elsif options['greater_than']
47
+ 'greater_than'
48
+ elsif options['greater_than_or_equal_to']
49
+ 'min'
50
+ elsif options['less_than']
51
+ 'less_than'
52
+ elsif options['less_than_or_equal_to']
53
+ 'max'
54
+ elsif options['odd']
55
+ 'odd'
56
+ elsif options['even']
57
+ 'even'
58
+ else
59
+ 'numericality'
60
+ end
61
+ when 'length'
62
+ if options['is']
63
+ 'islength'
64
+ elsif options['minimum'] && options['maximum']
65
+ ['minlength', 'maxlength']
66
+ elsif options['minimum']
67
+ 'minlength'
68
+ elsif options['maximum']
69
+ 'maxlength'
70
+ end
71
+ when 'uniqueness'
72
+ 'remote'
73
+ end
74
+ end
75
+
76
+ def extract_rules(validations, field = nil)
77
+ rules = {}
78
+ validations.each do |kind, options|
79
+ kind = convert_kind(kind, options)
80
+ value = case kind
81
+ when 'acceptance', 'required', 'digits', 'numericality', 'odd', 'even', 'confirmation'
82
+ true
83
+ when 'greater_than', 'less_than'
84
+ options[kind]
85
+ when 'max'
86
+ options['less_than_or_equal_to']
87
+ when 'min'
88
+ options['greater_than_or_equal_to']
89
+ when 'format'
90
+ options['with']
91
+ when 'exclusion', 'inclusion'
92
+ options['in']
93
+ when 'islength'
94
+ options['is']
95
+ when 'minlength'
96
+ options['minimum']
97
+ when 'maxlength'
98
+ options['maximum']
99
+ when 'remote'
100
+ if base.new_record?
101
+ data = {}
102
+ else
103
+ data = { "#{base.class.to_s.underscore}[id]" => base.id}
104
+ end
105
+ {'url' => '/validations/uniqueness.json', 'data' => data}
106
+ end
107
+
108
+ unless Array === kind
109
+ rules[kind] = value
110
+ else
111
+ kind.each do |k|
112
+ special_rule = case k
113
+ when 'minlength'
114
+ options['minimum']
115
+ when 'maxlength'
116
+ options['maximum']
117
+ end
118
+
119
+ rules[k] = special_rule
120
+ end
121
+ end
122
+
123
+ if numericality?(kind)
124
+ unless rules['numericality'] || rules['digits']
125
+ rules['numericality'] = true
126
+ end
127
+ end
128
+
129
+ if required?(kind, options)
130
+ unless rules['required']
131
+ rules['required'] = true
132
+ end
133
+ end
134
+
135
+ end
136
+ rules
137
+ end
138
+
139
+ def extract_messages(validations)
140
+ messages = {}
141
+ validations.each do |kind, options|
142
+ kind = convert_kind(kind, options)
143
+ unless Array === kind
144
+ messages[kind] = options['message']
145
+ else
146
+ kind.each do |k|
147
+ special_message = case k
148
+ when 'minlength'
149
+ options['message'] = options['message_min']
150
+ 'message_min'
151
+ when 'maxlength'
152
+ 'message_max'
153
+ end
154
+
155
+ messages[k] = options[special_message]
156
+ end
157
+ end
158
+
159
+ if numericality?(kind)
160
+ messages['numericality'] = I18n.translate(i18n_prefix + 'errors.messages.not_a_number')
161
+ end
162
+
163
+ if required?(kind, options)
164
+ unless messages['required']
165
+ if ['greater_than', 'min', 'less_than', 'max', 'odd', 'even'].include?(kind)
166
+ messages['required'] = I18n.translate(i18n_prefix + 'errors.messages.not_a_number')
167
+ else
168
+ messages['required'] = options['message']
169
+ end
170
+ end
171
+ end
172
+ end
173
+ messages
174
+ end
175
+
176
+ def i18n_prefix
177
+ if defined?(::ActiveModel)
178
+ ''
179
+ else # ActiveRecord 2.x
180
+ 'activerecord.'
181
+ end
182
+ end
183
+
184
+ def numericality?(kind)
185
+ ['greater_than', 'min', 'less_than', 'max', 'even', 'odd'].include?(kind)
186
+ end
187
+
188
+ def required?(kind, options)
189
+ case kind
190
+ when 'digits', 'exclusion', 'inclusion', 'islength', 'minlength', 'remote', 'numericality', 'greater_than', 'min', 'less_than', 'max', 'even', 'odd', 'format'
191
+ !options['allow_blank']
192
+ when Array
193
+ required?('minlength', options) if kind.include?('minlength')
194
+ end
195
+ end
196
+
197
+ def adapter
198
+ unless @adapter
199
+ @adapter = ClientSideValidations::Adapters::ActiveModel.new(base)
200
+ end
201
+ @adapter
202
+ end
203
+ end
204
+ end
205
+ end
206
+
207
+ if defined?(::ActiveModel)
208
+ klass = ::ActiveModel::Validations
209
+
210
+ elsif defined?(::ActiveRecord)
211
+ if ::ActiveRecord::VERSION::MAJOR == 2
212
+ require 'validation_reflection/active_model'
213
+ klass = ::ActiveRecord::Base
214
+
215
+ ActiveRecord::Base.class_eval do
216
+ ::ActiveRecordExtensions::ValidationReflection.reflected_validations << :validates_size_of
217
+ ::ActiveRecordExtensions::ValidationReflection.install(self)
218
+ end
219
+ end
220
+ end
221
+
222
+ if klass
223
+ klass.class_eval do
224
+ include ClientSideValidations::ORM
225
+ end
226
+ end