andrewroth-client_side_validations 3.2.0.beta.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. data/client_side_validations.gemspec +28 -0
  2. data/lib/client_side_validations.rb +18 -0
  3. data/lib/client_side_validations/action_view.rb +14 -0
  4. data/lib/client_side_validations/action_view/form_builder.rb +176 -0
  5. data/lib/client_side_validations/action_view/form_helper.rb +105 -0
  6. data/lib/client_side_validations/action_view/form_tag_helper.rb +12 -0
  7. data/lib/client_side_validations/active_model.rb +68 -0
  8. data/lib/client_side_validations/active_model/acceptance.rb +10 -0
  9. data/lib/client_side_validations/active_model/exclusion.rb +15 -0
  10. data/lib/client_side_validations/active_model/format.rb +10 -0
  11. data/lib/client_side_validations/active_model/inclusion.rb +15 -0
  12. data/lib/client_side_validations/active_model/length.rb +26 -0
  13. data/lib/client_side_validations/active_model/numericality.rb +33 -0
  14. data/lib/client_side_validations/active_model/presence.rb +10 -0
  15. data/lib/client_side_validations/active_record.rb +12 -0
  16. data/lib/client_side_validations/active_record/middleware.rb +50 -0
  17. data/lib/client_side_validations/active_record/uniqueness.rb +28 -0
  18. data/lib/client_side_validations/core_ext.rb +3 -0
  19. data/lib/client_side_validations/core_ext/range.rb +10 -0
  20. data/lib/client_side_validations/core_ext/regexp.rb +14 -0
  21. data/lib/client_side_validations/engine.rb +6 -0
  22. data/lib/client_side_validations/files.rb +8 -0
  23. data/lib/client_side_validations/generators.rb +12 -0
  24. data/lib/client_side_validations/generators/rails_validations.rb +15 -0
  25. data/lib/client_side_validations/middleware.rb +105 -0
  26. data/lib/client_side_validations/version.rb +3 -0
  27. data/lib/generators/client_side_validations/copy_assets_generator.rb +56 -0
  28. data/lib/generators/client_side_validations/install_generator.rb +22 -0
  29. data/lib/generators/templates/client_side_validations/initializer.rb +11 -0
  30. data/vendor/assets/javascripts/rails.validations.js +424 -0
  31. metadata +213 -0
@@ -0,0 +1,10 @@
1
+ module ClientSideValidations::ActiveModel
2
+ module Format
3
+ private
4
+
5
+ def message_type
6
+ :invalid
7
+ end
8
+ end
9
+ end
10
+
@@ -0,0 +1,15 @@
1
+ module ClientSideValidations::ActiveModel
2
+ module Inclusion
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
+
@@ -0,0 +1,26 @@
1
+ module ClientSideValidations::ActiveModel
2
+ module Length
3
+
4
+ def client_side_hash(model, attribute)
5
+ options = self.options.dup
6
+ hash = { :messages => {} }
7
+ hash[:js_tokenizer] = options[:js_tokenizer] if options[:js_tokenizer]
8
+ hash[:allow_blank] = true if options[:allow_blank]
9
+
10
+ self.class::MESSAGES.each do |option, message_type|
11
+ if count = options[option]
12
+ options[:message] = options[message_type] if options[message_type].present?
13
+ options.delete(:message) if options[:message].nil?
14
+ hash[:messages][option] = model.errors.generate_message(attribute, message_type, options.merge(:count => count))
15
+ hash[option] = count
16
+ end
17
+ end
18
+
19
+ copy_conditional_attributes(hash, options)
20
+
21
+ hash
22
+ end
23
+
24
+ end
25
+ end
26
+
@@ -0,0 +1,33 @@
1
+ module ClientSideValidations::ActiveModel
2
+ module Numericality
3
+
4
+ OPTION_MAP = {}
5
+
6
+ def self.included(base)
7
+ OPTION_MAP.merge!(base::CHECKS.keys.inject({}) { |hash, key| hash.merge!(key => key) })
8
+ end
9
+
10
+ def client_side_hash(model, attribute)
11
+ options = self.options.dup
12
+ hash = { :messages => { :numericality => model.errors.generate_message(attribute, :not_a_number, options) } }
13
+
14
+ if options[:only_integer]
15
+ hash[:messages][:only_integer] = model.errors.generate_message(attribute, :not_an_integer, options)
16
+ hash[:only_integer] = true
17
+ end
18
+
19
+ OPTION_MAP.each do |option, message_type|
20
+ if count = options[option]
21
+ hash[:messages][option] = model.errors.generate_message(attribute, message_type, options.merge(:count => count))
22
+ hash[option] = count
23
+ end
24
+ end
25
+
26
+ copy_conditional_attributes(hash, options)
27
+
28
+ hash
29
+ end
30
+
31
+ end
32
+ end
33
+
@@ -0,0 +1,10 @@
1
+ module ClientSideValidations::ActiveModel
2
+ module Presence
3
+ private
4
+
5
+ def message_type
6
+ :blank
7
+ end
8
+ end
9
+ end
10
+
@@ -0,0 +1,12 @@
1
+ require 'client_side_validations/active_model'
2
+ require 'client_side_validations/middleware'
3
+ require 'client_side_validations/active_record/middleware'
4
+
5
+ %w{uniqueness}.each do |validator|
6
+ require "client_side_validations/active_record/#{validator}"
7
+ validator.capitalize!
8
+ eval "ActiveRecord::Validations::#{validator}Validator.send(:include, ClientSideValidations::ActiveRecord::#{validator})"
9
+ end
10
+
11
+ ActiveRecord::Base.send(:include, ClientSideValidations::ActiveModel::Validations)
12
+ ClientSideValidations::Middleware::Uniqueness.register_orm(ClientSideValidations::ActiveRecord::Middleware)
@@ -0,0 +1,50 @@
1
+ module ClientSideValidations::ActiveRecord
2
+ class Middleware
3
+
4
+ def self.is_class?(klass)
5
+ klass < ::ActiveRecord::Base
6
+ end
7
+
8
+ def self.is_unique?(klass, attribute, value, params)
9
+ value = type_cast_value(klass, attribute, value)
10
+ column = klass.columns_hash[attribute.to_s]
11
+ value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s if column.text?
12
+
13
+ t = klass.arel_table
14
+
15
+ if params[:case_sensitive] == 'true'
16
+ if t.engine.connection.instance_variable_get("@config")[:adapter] == 'mysql'
17
+ relation = Arel::Nodes::SqlLiteral.new("BINARY #{t[attribute].eq(value).to_sql}")
18
+ else
19
+ relation = t[attribute].eq(value)
20
+ end
21
+ else
22
+ relation = t[attribute].matches(value)
23
+ end
24
+
25
+ if relation.is_a?(Arel::Nodes::SqlLiteral)
26
+ relation = Arel::Nodes::SqlLiteral.new("BINARY #{t[attribute].eq(value).to_sql} AND #{t.primary_key.not_eq(params[:id]).to_sql}")
27
+ else
28
+ relation = relation.and(t.primary_key.not_eq(params[:id])) if params[:id]
29
+ end
30
+
31
+ (params[:scope] || {}).each do |attribute, value|
32
+ value = type_cast_value(klass, attribute, value)
33
+ if relation.is_a?(Arel::Nodes::SqlLiteral)
34
+ relation = Arel::Nodes::SqlLiteral.new("#{relation} AND #{t[attribute].eq(value).to_sql}")
35
+ else
36
+ relation = relation.and(t[attribute].eq(value))
37
+ end
38
+ end
39
+
40
+ !klass.where(relation).exists?
41
+ end
42
+
43
+ private
44
+
45
+ def self.type_cast_value(klass, attribute, value)
46
+ klass.columns_hash[attribute].type_cast(value)
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,28 @@
1
+ module ClientSideValidations::ActiveRecord
2
+ module Uniqueness
3
+ def client_side_hash(model, attribute)
4
+ hash = {}
5
+ hash[:message] = model.errors.generate_message(attribute, message_type, options.except(:scope))
6
+ hash[:case_sensitive] = options[:case_sensitive]
7
+ hash[:id] = model.id unless model.new_record?
8
+ if options.key?(:scope) && options[:scope].present?
9
+ hash[:scope] = Array.wrap(options[:scope]).inject({}) do |scope_hash, scope_item|
10
+ scope_hash.merge!(scope_item => model.send(scope_item))
11
+ end
12
+ end
13
+
14
+ unless model.class.name.demodulize == model.class.name
15
+ hash[:class] = model.class.name.underscore
16
+ end
17
+
18
+ hash
19
+ end
20
+
21
+ private
22
+
23
+ def message_type
24
+ :taken
25
+ end
26
+ end
27
+ end
28
+
@@ -0,0 +1,3 @@
1
+ require 'active_support/json'
2
+ require 'client_side_validations/core_ext/range'
3
+ require 'client_side_validations/core_ext/regexp'
@@ -0,0 +1,10 @@
1
+ class Range
2
+ def as_json(options = nil)
3
+ [first, last]
4
+ end
5
+
6
+ def to_json(options = nil)
7
+ as_json(options).inspect
8
+ end
9
+ end
10
+
@@ -0,0 +1,14 @@
1
+ class Regexp
2
+ def as_json(options = nil)
3
+ Regexp.new inspect.sub('\\A','^').sub('\\Z','$').sub('\\z','$').sub(/^\//,'').sub(/\/[a-z]*$/,'').gsub(/\(\?#.+\)/, '').gsub(/\(\?-\w+:/,'('), self.options
4
+ end
5
+
6
+ def to_json(options = nil)
7
+ as_json(options).inspect
8
+ end
9
+
10
+ def encode_json(encoder)
11
+ inspect
12
+ end
13
+ end
14
+
@@ -0,0 +1,6 @@
1
+ module ClientSideValidations
2
+ class Engine < ::Rails::Engine
3
+ config.app_middleware.use ClientSideValidations::Middleware::Validators
4
+ end
5
+ end
6
+
@@ -0,0 +1,8 @@
1
+ # This is only used by dependant libraries that need to find the files
2
+
3
+ module ClientSideValidations
4
+ module Files
5
+ Initializer = File.expand_path(File.dirname(__FILE__) + '/../generators/templates/client_side_validations/initializer.rb')
6
+ Javascript = File.expand_path(File.dirname(__FILE__) + '/../../vendor/assets/javascripts/rails.validations.js')
7
+ end
8
+ end
@@ -0,0 +1,12 @@
1
+ module ClientSideValidations
2
+ module Generators
3
+ Assets = []
4
+
5
+ def self.register_assets(klass)
6
+ Assets.push(*klass.assets)
7
+ end
8
+ end
9
+ end
10
+
11
+ require 'client_side_validations/generators/rails_validations'
12
+
@@ -0,0 +1,15 @@
1
+ module ClientSideValidations
2
+ module Generators
3
+ class RailsValidations
4
+ def self.assets
5
+ [{
6
+ :path => File.expand_path('../../../../vendor/assets/javascripts', __FILE__),
7
+ :file => 'rails.validations.js'
8
+ }]
9
+ end
10
+
11
+ Generators.register_assets(self)
12
+ end
13
+ end
14
+ end
15
+
@@ -0,0 +1,105 @@
1
+ # encoding: utf-8
2
+
3
+ require 'client_side_validations/core_ext'
4
+
5
+ module ClientSideValidations
6
+
7
+ module Middleware
8
+ class Validators
9
+ def initialize(app)
10
+ @app = app
11
+ end
12
+
13
+ def call(env)
14
+ matches = /^\/validators\/(\w+)$/.match(env['PATH_INFO'])
15
+ if !matches || (matches[1] == 'uniqueness' && Config.uniqueness_validator_disabled)
16
+ @app.call(env)
17
+ else
18
+ "::ClientSideValidations::Middleware::#{matches[1].camelize}".constantize.new(env).response
19
+ end
20
+ end
21
+ end
22
+
23
+ class Base
24
+ attr_accessor :request, :body, :status
25
+
26
+ def initialize(env)
27
+ self.body = ''
28
+ self.status = 200
29
+ self.request = ActionDispatch::Request.new(env)
30
+ end
31
+
32
+ def response
33
+ [status, {'Content-Type' => content_type, 'Content-Length' => body.length.to_s}, [body]]
34
+ end
35
+
36
+ def content_type
37
+ 'application/json'
38
+ end
39
+ end
40
+
41
+ class Uniqueness < Base
42
+ IGNORE_PARAMS = %w{case_sensitive id scope}
43
+ REGISTERED_ORMS = []
44
+
45
+ def response
46
+ if is_unique?
47
+ self.status = 404
48
+ self.body = 'true'
49
+ else
50
+ self.status = 200
51
+ self.body = 'false'
52
+ end
53
+ super
54
+ end
55
+
56
+ def self.register_orm(orm)
57
+ registered_orms << orm
58
+ end
59
+
60
+ def self.registered_orms
61
+ REGISTERED_ORMS
62
+ end
63
+
64
+ def registered_orms
65
+ self.class.registered_orms
66
+ end
67
+
68
+ private
69
+
70
+ def is_unique?
71
+ convert_scope_value_from_null_to_nil
72
+ resource = extract_resource
73
+ klass = resource.classify.constantize
74
+ attribute = request.params[resource].keys.first
75
+ value = request.params[resource][attribute]
76
+ middleware_class = nil
77
+
78
+ registered_orms.each do |orm|
79
+ if orm.is_class?(klass)
80
+ middleware_class = orm
81
+ break
82
+ end
83
+ end
84
+
85
+ middleware_class.is_unique?(klass, attribute, value, request.params)
86
+ end
87
+
88
+ def convert_scope_value_from_null_to_nil
89
+ if request.params['scope']
90
+ request.params['scope'].each do |key, value|
91
+ if value == 'null'
92
+ request.params['scope'][key] = nil
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ def extract_resource
99
+ parent_key = (request.params.keys - IGNORE_PARAMS).first
100
+ end
101
+ end
102
+ end
103
+
104
+ end
105
+
@@ -0,0 +1,3 @@
1
+ module ClientSideValidations
2
+ VERSION = '3.2.0.beta.4'
3
+ end
@@ -0,0 +1,56 @@
1
+ module ClientSideValidations
2
+ module Generators
3
+ class CopyAssetsGenerator < Rails::Generators::Base
4
+
5
+ def copy_javascript_asset
6
+ if self.class == CopyAssetsGenerator || !asset_pipeline_enabled?
7
+ assets.each do |asset|
8
+ source_paths << asset[:path]
9
+ copy_file asset[:file], "#{asset_directory}/#{asset[:file]}"
10
+ end
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def self.asset_directory
17
+ if asset_pipeline_enabled?
18
+ "app#{Rails.configuration.assets.prefix}/javascripts"
19
+ else
20
+ 'public/javascripts'
21
+ end
22
+ end
23
+
24
+ def asset_directory
25
+ CopyAssetsGenerator.asset_directory
26
+ end
27
+
28
+ def self.assets
29
+ Assets
30
+ end
31
+
32
+ def assets
33
+ CopyAssetsGenerator.assets
34
+ end
35
+
36
+ def self.asset_file_names
37
+ assets.map { |asset| asset[:file] }.join(', ')
38
+ end
39
+
40
+ def self.asset_pipeline_enabled?
41
+ (Rails.configuration.respond_to?(:assets) ? (Rails.configuration.assets || {}) : {})[:enabled]
42
+ end
43
+
44
+ def asset_pipeline_enabled?
45
+ self.class.asset_pipeline_enabled?
46
+ end
47
+
48
+ def self.installation_message
49
+ "Copies #{asset_file_names} to #{asset_directory}"
50
+ end
51
+
52
+ desc installation_message
53
+ end
54
+ end
55
+ end
56
+