attr_validator 0.0.1

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 (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"