adamhunter-client_side_validations 2.9.10

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