attr_validator 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/.gitignore +4 -0
  2. data/.travis.yml +4 -0
  3. data/Gemfile +9 -0
  4. data/Gemfile.lock +37 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +119 -0
  7. data/Rakefile +1 -0
  8. data/attr_validator.gemspec +24 -0
  9. data/lib/attr_validator/args_validator.rb +118 -0
  10. data/lib/attr_validator/concern.rb +136 -0
  11. data/lib/attr_validator/core_extensions/class_attribute.rb +143 -0
  12. data/lib/attr_validator/errors.rb +23 -0
  13. data/lib/attr_validator/i18n.rb +7 -0
  14. data/lib/attr_validator/locales/en.yml +23 -0
  15. data/lib/attr_validator/validation_errors.rb +245 -0
  16. data/lib/attr_validator/validator.rb +148 -0
  17. data/lib/attr_validator/validators/email_validator.rb +48 -0
  18. data/lib/attr_validator/validators/exclusion_validator.rb +23 -0
  19. data/lib/attr_validator/validators/inclusion_validator.rb +28 -0
  20. data/lib/attr_validator/validators/length_validator.rb +32 -0
  21. data/lib/attr_validator/validators/not_nil_validator.rb +25 -0
  22. data/lib/attr_validator/validators/numericality_validator.rb +39 -0
  23. data/lib/attr_validator/validators/presence_validator.rb +26 -0
  24. data/lib/attr_validator/validators/regexp_validator.rb +21 -0
  25. data/lib/attr_validator/validators/url_validator.rb +25 -0
  26. data/lib/attr_validator/validators.rb +11 -0
  27. data/lib/attr_validator/version.rb +3 -0
  28. data/lib/attr_validator.rb +41 -0
  29. data/spec/attr_validator/validator_spec.rb +105 -0
  30. data/spec/attr_validator/validators/email_validator_spec.rb +24 -0
  31. data/spec/attr_validator/validators/exclusion_validator_spec.rb +24 -0
  32. data/spec/attr_validator/validators/inclusion_validator_spec.rb +24 -0
  33. data/spec/attr_validator/validators/length_validator_spec.rb +39 -0
  34. data/spec/attr_validator/validators/not_nil_validator_spec.rb +25 -0
  35. data/spec/attr_validator/validators/numericality_validator_spec.rb +50 -0
  36. data/spec/attr_validator/validators/presence_validator_spec.rb +25 -0
  37. data/spec/attr_validator/validators/regexp_validator_spec.rb +24 -0
  38. data/spec/attr_validator/validators/url_validator_spec.rb +24 -0
  39. data/spec/spec_helper.rb +7 -0
  40. metadata +145 -0
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ vendor/
2
+ tags
3
+ .bundle
4
+ .DS_Store
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ rvm:
2
+ - 1.9.3
3
+ - 2.0.0
4
+ script: "bundle exec rspec spec/"
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in aggregate_builder.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem 'rspec'
8
+ gem 'debugger'
9
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,37 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ attr_validator (0.0.1)
5
+ i18n
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ columnize (0.3.6)
11
+ debugger (1.6.5)
12
+ columnize (>= 0.3.1)
13
+ debugger-linecache (~> 1.2.0)
14
+ debugger-ruby_core_source (~> 1.3.1)
15
+ debugger-linecache (1.2.0)
16
+ debugger-ruby_core_source (1.3.1)
17
+ diff-lcs (1.2.5)
18
+ i18n (0.6.9)
19
+ rake (10.1.1)
20
+ rspec (2.14.1)
21
+ rspec-core (~> 2.14.0)
22
+ rspec-expectations (~> 2.14.0)
23
+ rspec-mocks (~> 2.14.0)
24
+ rspec-core (2.14.7)
25
+ rspec-expectations (2.14.4)
26
+ diff-lcs (>= 1.1.3, < 2.0)
27
+ rspec-mocks (2.14.4)
28
+
29
+ PLATFORMS
30
+ ruby
31
+
32
+ DEPENDENCIES
33
+ attr_validator!
34
+ bundler (~> 1.3)
35
+ debugger
36
+ rake
37
+ rspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Albert Gazizov
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,119 @@
1
+ # AttrValidator [![Build Status](https://travis-ci.org/AlbertGazizov/attr_validator.png)](https://travis-ci.org/AlbertGazizov/attr_validator) [![Code Climate](https://codeclimate.com/github/AlbertGazizov/attr_validator.png)](https://codeclimate.com/github/AlbertGazizov/attr_validator)
2
+
3
+
4
+ AttrValidator is simple library for validating ruby objects.
5
+ The main idea of the gem is separate all object validation logic from the object itself
6
+
7
+ ## Usage
8
+ Lets say you have the following class and you wan to validate objects of this class
9
+ ```ruby
10
+ class Contact
11
+ attr_accessor :first_name, :last_name, :position, :age, :type, :email, :color, :status, :stage, :description, :companies
12
+ end
13
+ ```
14
+ To validate objects of the Contact class define a validator:
15
+ ```ruby
16
+ class ContactValidator
17
+ include AttrValidator::Validator
18
+
19
+ validates :first_name, presence: true, length: { min: 4, max: 7 }
20
+ validates :last_name, length: { equal_to: 5 }
21
+ validates :position, length: { not_equal_to: 5 }
22
+ validates :age, numericality: { greater_than: 0, less_than: 150 }
23
+ validates :type, numericality: { greater_than_or_equal_to: 1, less_than_or_equal_to: 5 }
24
+ validates :email, email: true
25
+ validates :color, regexp: /#\w{6}/
26
+ validates :status, inclusion: { in: [:new, :lead] }
27
+ validates :stage, exclusion: { in: [:wrong, :bad] }
28
+
29
+ validate_associated :companies, validator: CompanyValidator
30
+
31
+ validate :check_description
32
+
33
+ def check_description(entity, errors)
34
+ if entity.description.nil?
35
+ errors.add(:description, "can't be empty")
36
+ end
37
+ end
38
+ end
39
+ ```
40
+
41
+ Instantiate the validator and pass a contact object inside:
42
+ ```ruby
43
+ errors = ContactValidator.new.validate(contact)
44
+ ```
45
+ errors is a hash which contains all validation errors
46
+ if object is valid then errors will be empty
47
+
48
+ ### Adding own validators
49
+ AttrValidator can be extended by adding your own validators.
50
+ To add a validator define a class with two the class method validate and validate_options:
51
+ The following example demonstrates the built in inclusion validator,
52
+ it validates that specified value is one of the defined value
53
+ ```ruby
54
+ class AttrValidator::Validators::InclusionValidator
55
+
56
+ # Validates that given value inscluded in the specified list
57
+ # @param value [Object] object to validate
58
+ # @parm options [Hash] validation options, e.g. { in: [:small, :medium, :large], message: "not included in the list of allowed items" }
59
+ # where :in - list of allowed values,
60
+ # message - is a message to return if value is not included in the list
61
+ # @return [Array] empty array if object is valid, list of errors otherwise
62
+ def self.validate(value, options)
63
+ return [] if value.nil?
64
+
65
+ errors = []
66
+ if options[:in]
67
+ unless options[:in].include?(value)
68
+ errors << (options[:message] || AttrValidator::I18n.t('errors.should_be_included_in_list', list: options[:in]))
69
+ end
70
+ end
71
+ errors
72
+ end
73
+
74
+ # Validates that options specified in
75
+ # :inclusion are valid
76
+ def self.validate_options(options)
77
+ raise ArgumentError, "validation options should be a Hash" unless options.is_a?(Hash)
78
+ raise ArgumentError, "validation options should have :in option and it should be an array of allowed values" unless options[:in].is_a?(Array)
79
+ end
80
+
81
+ end
82
+ ```
83
+ And register it in AttrValidator:
84
+ ```ruby
85
+ AttrValidator.add_validator(:inclusion, AttrValidator::Validators::InclusionValidator)
86
+ ```
87
+ Now you can use it:
88
+ ```ruby
89
+ class SomeValidator
90
+ include AttrValidator::Validator
91
+
92
+ validates :size, inclusion: { in: [:small, :medium, :large] }
93
+ end
94
+ ```
95
+
96
+ ## Installation
97
+
98
+ Add this line to your application's Gemfile:
99
+
100
+ gem 'attr_validator'
101
+
102
+ And then execute:
103
+
104
+ $ bundle
105
+
106
+ Or install it yourself as:
107
+
108
+ $ gem install attr_validator
109
+
110
+ ## Contributing
111
+
112
+ 1. Fork it
113
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
114
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
115
+ 4. Push to the branch (`git push origin my-new-feature`)
116
+ 5. Create new Pull Request
117
+
118
+ # TODO
119
+ 1. Document methods
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'attr_validator/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "attr_validator"
8
+ spec.version = AttrValidator::VERSION
9
+ spec.authors = ["Albert Gazizov"]
10
+ spec.email = ["deeper4k@gmail.com"]
11
+ spec.description = %q{Object validation library}
12
+ spec.summary = %q{Moves validation logic to validators}
13
+ spec.homepage = "http://github.com/AlbertGazizov/attr_validator"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(spec)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "i18n"
22
+ spec.add_development_dependency "bundler", "~> 1.3"
23
+ spec.add_development_dependency "rake"
24
+ end
@@ -0,0 +1,118 @@
1
+ # Helper class for arguments validation
2
+ module AttrValidator::ArgsValidator
3
+ class ArgError < StandardError; end
4
+
5
+ class << self
6
+
7
+ # Checks that specifid +obj+ is a symbol
8
+ # @param obj some object
9
+ # @param obj_name object's name, used to clarify error causer in exception
10
+ def is_symbol!(obj, obj_name)
11
+ unless obj.is_a?(Symbol)
12
+ raise ArgError, "#{obj_name} should be a Symbol"
13
+ end
14
+ end
15
+
16
+ # Checks that specifid +obj+ is a boolean
17
+ # @param obj some object
18
+ # @param obj_name object's name, used to clarify error causer in exception
19
+ def is_boolean!(obj, obj_name)
20
+ if !obj.is_a?(TrueClass) && !obj.is_a?(FalseClass)
21
+ raise ArgError, "#{obj_name} should be a Boolean"
22
+ end
23
+ end
24
+
25
+ # Checks that specifid +obj+ is an integer
26
+ # @param obj some object
27
+ # @param obj_name object's name, used to clarify error causer in exception
28
+ def is_integer!(obj, obj_name)
29
+ unless obj.is_a?(Integer)
30
+ raise ArgError, "#{obj_name} should be an Integer"
31
+ end
32
+ end
33
+
34
+ # Checks that specifid +obj+ is an Array
35
+ # @param obj some object
36
+ # @param obj_name object's name, used to clarify error causer in exception
37
+ def is_array!(obj, obj_name)
38
+ unless obj.is_a?(Array)
39
+ raise ArgError, "#{obj_name} should be an Array"
40
+ end
41
+ end
42
+
43
+ # Checks that specifid +obj+ is a Hash
44
+ # @param obj some object
45
+ # @param obj_name object's name, used to clarify error causer in exception
46
+ def is_hash!(obj, obj_name)
47
+ unless obj.is_a?(Hash)
48
+ raise ArgError, "#{obj_name} should be a Hash"
49
+ end
50
+ end
51
+
52
+ # Checks that specifid +obj+ is an integer or float
53
+ # @param obj some object
54
+ # @param obj_name object's name, used to clarify error causer in exception
55
+ def is_integer_or_float!(obj, obj_name)
56
+ if !obj.is_a?(Integer) && !obj.is_a?(Float)
57
+ raise ArgError, "#{obj_name} should be an Integer or Float"
58
+ end
59
+ end
60
+
61
+ # Checks that specifid +obj+ is a string or regexp
62
+ # @param obj some object
63
+ # @param obj_name object's name, used to clarify error causer in exception
64
+ def is_string_or_regexp!(obj, obj_name)
65
+ if !obj.is_a?(String) && !obj.is_a?(Regexp)
66
+ raise ArgError, "#{obj_name} should be a String or Regexp"
67
+ end
68
+ end
69
+
70
+ # Checks that specifid +obj+ is a symbol or class
71
+ # @param obj some object
72
+ # @param obj_name object's name, used to clarify error causer in exception
73
+ def is_class_or_symbol!(obj, obj_name)
74
+ if !obj.is_a?(Symbol) && !obj.is_a?(Class)
75
+ raise ArgError, "#{obj_name} should be a Symbol or Class"
76
+ end
77
+ end
78
+
79
+ # Checks that specifid +obj+ is a symbol or block
80
+ # @param obj some object
81
+ # @param obj_name object's name, used to clarify error causer in exception
82
+ def is_symbol_or_block!(obj, obj_name)
83
+ if !obj.is_a?(Symbol) && !obj.is_a?(Proc)
84
+ raise ArgError, "#{obj_name} should be a Symbol or Proc"
85
+ end
86
+ end
87
+
88
+ # Checks that specifid +hash+ has a specified +key+
89
+ # @param hash some hash
90
+ # @param key hash's key
91
+ def has_key!(hash, key)
92
+ unless hash.has_key?(key)
93
+ raise ArgError, "#{hash} should has #{key} key"
94
+ end
95
+ end
96
+
97
+ def not_nil!(obj, obj_name)
98
+ if obj.nil?
99
+ raise ArgError, "#{obj_name} can't be nil"
100
+ end
101
+ end
102
+
103
+ def has_only_allowed_keys!(hash, keys, obj_name)
104
+ remaining_keys = hash.keys - keys
105
+ unless remaining_keys.empty?
106
+ raise ArgError, "#{obj_name} has unacceptable options #{remaining_keys}"
107
+ end
108
+ end
109
+
110
+ # Checks that specified +block+ is given
111
+ # @param block some block
112
+ def block_given!(block)
113
+ unless block
114
+ raise ArgError, "Block should be given"
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,136 @@
1
+ module AttrValidator
2
+ # Copied from here https://github.com/rails/rails/blob/master/activesupport/lib/active_support/concern.rb
3
+ #
4
+ # A typical module looks like this:
5
+ #
6
+ # module M
7
+ # def self.included(base)
8
+ # base.extend ClassMethods
9
+ # base.class_eval do
10
+ # scope :disabled, -> { where(disabled: true) }
11
+ # end
12
+ # end
13
+ #
14
+ # module ClassMethods
15
+ # ...
16
+ # end
17
+ # end
18
+ #
19
+ # By using <tt>ActiveSupport::Concern</tt> the above module could instead be
20
+ # written as:
21
+ #
22
+ # require 'active_support/concern'
23
+ #
24
+ # module M
25
+ # extend ActiveSupport::Concern
26
+ #
27
+ # included do
28
+ # scope :disabled, -> { where(disabled: true) }
29
+ # end
30
+ #
31
+ # module ClassMethods
32
+ # ...
33
+ # end
34
+ # end
35
+ #
36
+ # Moreover, it gracefully handles module dependencies. Given a +Foo+ module
37
+ # and a +Bar+ module which depends on the former, we would typically write the
38
+ # following:
39
+ #
40
+ # module Foo
41
+ # def self.included(base)
42
+ # base.class_eval do
43
+ # def self.method_injected_by_foo
44
+ # ...
45
+ # end
46
+ # end
47
+ # end
48
+ # end
49
+ #
50
+ # module Bar
51
+ # def self.included(base)
52
+ # base.method_injected_by_foo
53
+ # end
54
+ # end
55
+ #
56
+ # class Host
57
+ # include Foo # We need to include this dependency for Bar
58
+ # include Bar # Bar is the module that Host really needs
59
+ # end
60
+ #
61
+ # But why should +Host+ care about +Bar+'s dependencies, namely +Foo+? We
62
+ # could try to hide these from +Host+ directly including +Foo+ in +Bar+:
63
+ #
64
+ # module Bar
65
+ # include Foo
66
+ # def self.included(base)
67
+ # base.method_injected_by_foo
68
+ # end
69
+ # end
70
+ #
71
+ # class Host
72
+ # include Bar
73
+ # end
74
+ #
75
+ # Unfortunately this won't work, since when +Foo+ is included, its <tt>base</tt>
76
+ # is the +Bar+ module, not the +Host+ class. With <tt>ActiveSupport::Concern</tt>,
77
+ # module dependencies are properly resolved:
78
+ #
79
+ # require 'active_support/concern'
80
+ #
81
+ # module Foo
82
+ # extend ActiveSupport::Concern
83
+ # included do
84
+ # def self.method_injected_by_foo
85
+ # ...
86
+ # end
87
+ # end
88
+ # end
89
+ #
90
+ # module Bar
91
+ # extend ActiveSupport::Concern
92
+ # include Foo
93
+ #
94
+ # included do
95
+ # self.method_injected_by_foo
96
+ # end
97
+ # end
98
+ #
99
+ # class Host
100
+ # include Bar # works, Bar takes care now of its dependencies
101
+ # end
102
+ module Concern
103
+ class MultipleIncludedBlocks < StandardError #:nodoc:
104
+ def initialize
105
+ super "Cannot define multiple 'included' blocks for a Concern"
106
+ end
107
+ end
108
+
109
+ def self.extended(base) #:nodoc:
110
+ base.instance_variable_set(:@_dependencies, [])
111
+ end
112
+
113
+ def append_features(base)
114
+ if base.instance_variable_defined?(:@_dependencies)
115
+ base.instance_variable_get(:@_dependencies) << self
116
+ return false
117
+ else
118
+ return false if base < self
119
+ @_dependencies.each { |dep| base.send(:include, dep) }
120
+ super
121
+ base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods)
122
+ base.class_eval(&@_included_block) if instance_variable_defined?(:@_included_block)
123
+ end
124
+ end
125
+
126
+ def included(base = nil, &block)
127
+ if base.nil?
128
+ raise MultipleIncludedBlocks if instance_variable_defined?(:@_included_block)
129
+
130
+ @_included_block = block
131
+ else
132
+ super
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,143 @@
1
+ module Kernel
2
+ # class_eval on an object acts like singleton_class.class_eval.
3
+ def class_eval(*args, &block)
4
+ singleton_class.class_eval(*args, &block)
5
+ end
6
+ end
7
+
8
+ class Module
9
+ def remove_possible_method(method)
10
+ if method_defined?(method) || private_method_defined?(method)
11
+ undef_method(method)
12
+ end
13
+ end
14
+
15
+ def redefine_method(method, &block)
16
+ remove_possible_method(method)
17
+ define_method(method, &block)
18
+ end
19
+ end
20
+
21
+ class Class
22
+ # Declare a class-level attribute whose value is inheritable by subclasses.
23
+ # Subclasses can change their own value and it will not impact parent class.
24
+ #
25
+ # class Base
26
+ # class_attribute :setting
27
+ # end
28
+ #
29
+ # class Subclass < Base
30
+ # end
31
+ #
32
+ # Base.setting = true
33
+ # Subclass.setting # => true
34
+ # Subclass.setting = false
35
+ # Subclass.setting # => false
36
+ # Base.setting # => true
37
+ #
38
+ # In the above case as long as Subclass does not assign a value to setting
39
+ # by performing <tt>Subclass.setting = _something_ </tt>, <tt>Subclass.setting</tt>
40
+ # would read value assigned to parent class. Once Subclass assigns a value then
41
+ # the value assigned by Subclass would be returned.
42
+ #
43
+ # This matches normal Ruby method inheritance: think of writing an attribute
44
+ # on a subclass as overriding the reader method. However, you need to be aware
45
+ # when using +class_attribute+ with mutable structures as +Array+ or +Hash+.
46
+ # In such cases, you don't want to do changes in places but use setters:
47
+ #
48
+ # Base.setting = []
49
+ # Base.setting # => []
50
+ # Subclass.setting # => []
51
+ #
52
+ # # Appending in child changes both parent and child because it is the same object:
53
+ # Subclass.setting << :foo
54
+ # Base.setting # => [:foo]
55
+ # Subclass.setting # => [:foo]
56
+ #
57
+ # # Use setters to not propagate changes:
58
+ # Base.setting = []
59
+ # Subclass.setting += [:foo]
60
+ # Base.setting # => []
61
+ # Subclass.setting # => [:foo]
62
+ #
63
+ # For convenience, an instance predicate method is defined as well.
64
+ # To skip it, pass <tt>instance_predicate: false</tt>.
65
+ #
66
+ # Subclass.setting? # => false
67
+ #
68
+ # Instances may overwrite the class value in the same way:
69
+ #
70
+ # Base.setting = true
71
+ # object = Base.new
72
+ # object.setting # => true
73
+ # object.setting = false
74
+ # object.setting # => false
75
+ # Base.setting # => true
76
+ #
77
+ # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
78
+ #
79
+ # object.setting # => NoMethodError
80
+ # object.setting? # => NoMethodError
81
+ #
82
+ # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
83
+ #
84
+ # object.setting = false # => NoMethodError
85
+ #
86
+ # To opt out of both instance methods, pass <tt>instance_accessor: false</tt>.
87
+ def class_attribute(*attrs)
88
+ options = attrs.last.is_a?(Hash) ? attrs.pop : {}
89
+ instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true)
90
+ instance_writer = options.fetch(:instance_accessor, true) && options.fetch(:instance_writer, true)
91
+ instance_predicate = options.fetch(:instance_predicate, true)
92
+
93
+ attrs.each do |name|
94
+ define_singleton_method(name) { nil }
95
+ define_singleton_method("#{name}?") { !!public_send(name) } if instance_predicate
96
+
97
+ ivar = "@#{name}"
98
+
99
+ define_singleton_method("#{name}=") do |val|
100
+ singleton_class.class_eval do
101
+ remove_possible_method(name)
102
+ define_method(name) { val }
103
+ end
104
+
105
+ if singleton_class?
106
+ class_eval do
107
+ remove_possible_method(name)
108
+ define_method(name) do
109
+ if instance_variable_defined? ivar
110
+ instance_variable_get ivar
111
+ else
112
+ singleton_class.send name
113
+ end
114
+ end
115
+ end
116
+ end
117
+ val
118
+ end
119
+
120
+ if instance_reader
121
+ remove_possible_method name
122
+ define_method(name) do
123
+ if instance_variable_defined?(ivar)
124
+ instance_variable_get ivar
125
+ else
126
+ self.class.public_send name
127
+ end
128
+ end
129
+ define_method("#{name}?") { !!public_send(name) } if instance_predicate
130
+ end
131
+
132
+ attr_writer name if instance_writer
133
+ end
134
+ end
135
+
136
+ private
137
+
138
+ unless respond_to?(:singleton_class?)
139
+ def singleton_class?
140
+ ancestors.first != self
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,23 @@
1
+ module AttrValidator::Errors
2
+ # Thrown when object has validation errors
3
+ class ValidationError < StandardError
4
+ attr_reader :errors
5
+
6
+ def initialize(message, errors)
7
+ @errors = errors
8
+ super(message)
9
+ end
10
+
11
+ def message
12
+ "#{@message}\n#{errors}"
13
+ end
14
+
15
+ def short_message
16
+ 'Validation error'
17
+ end
18
+ end
19
+
20
+
21
+ # Thrown when validator is not defined
22
+ class MissingValidatorError < StandardError; end
23
+ end
@@ -0,0 +1,7 @@
1
+ class AttrValidator::I18n
2
+
3
+ # Translates message to specific language
4
+ def self.t(message, options = {})
5
+ I18n.t(message, options)
6
+ end
7
+ end
@@ -0,0 +1,23 @@
1
+ en:
2
+ errors:
3
+ invalid_email: "invalid email"
4
+ can_not_be_email: "can't be email"
5
+ should_not_be_included_in_list: "should not be included in %{list}"
6
+ should_be_included_in_list: "should be included in %{list}"
7
+ can_not_be_less_than: "can not be less than %{length}"
8
+ can_not_be_more_than: "can not be more than %{length}"
9
+ should_be_equal_to: "should be equal to %{length}"
10
+ should_not_be_equal_to: "should not be equal to %{length}"
11
+ can_not_be_nil: "can not be nil"
12
+ should_be_nil: "should be nil"
13
+ should_be_greater_than: "should be greater than %{number}"
14
+ should_be_greater_than_or_equal_to: "should be greater than or equal to %{number}"
15
+ should_be_less_than: "should be less than %{number}"
16
+ should_be_less_than_or_equal_to: "should be less than or equal to %{number}"
17
+ should_be_even: "should be even number"
18
+ should_be_odd: "should be odd number"
19
+ can_not_be_blank: "can not be blank"
20
+ should_be_blank: "should be blank"
21
+ does_not_match: "does not match defined format"
22
+ invalid_url: "invalid url"
23
+ can_not_be_url: "can not be a url"