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.
- data/.gitignore +4 -0
- data/.travis.yml +4 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +37 -0
- data/LICENSE.txt +22 -0
- data/README.md +119 -0
- data/Rakefile +1 -0
- data/attr_validator.gemspec +24 -0
- data/lib/attr_validator/args_validator.rb +118 -0
- data/lib/attr_validator/concern.rb +136 -0
- data/lib/attr_validator/core_extensions/class_attribute.rb +143 -0
- data/lib/attr_validator/errors.rb +23 -0
- data/lib/attr_validator/i18n.rb +7 -0
- data/lib/attr_validator/locales/en.yml +23 -0
- data/lib/attr_validator/validation_errors.rb +245 -0
- data/lib/attr_validator/validator.rb +148 -0
- data/lib/attr_validator/validators/email_validator.rb +48 -0
- data/lib/attr_validator/validators/exclusion_validator.rb +23 -0
- data/lib/attr_validator/validators/inclusion_validator.rb +28 -0
- data/lib/attr_validator/validators/length_validator.rb +32 -0
- data/lib/attr_validator/validators/not_nil_validator.rb +25 -0
- data/lib/attr_validator/validators/numericality_validator.rb +39 -0
- data/lib/attr_validator/validators/presence_validator.rb +26 -0
- data/lib/attr_validator/validators/regexp_validator.rb +21 -0
- data/lib/attr_validator/validators/url_validator.rb +25 -0
- data/lib/attr_validator/validators.rb +11 -0
- data/lib/attr_validator/version.rb +3 -0
- data/lib/attr_validator.rb +41 -0
- data/spec/attr_validator/validator_spec.rb +105 -0
- data/spec/attr_validator/validators/email_validator_spec.rb +24 -0
- data/spec/attr_validator/validators/exclusion_validator_spec.rb +24 -0
- data/spec/attr_validator/validators/inclusion_validator_spec.rb +24 -0
- data/spec/attr_validator/validators/length_validator_spec.rb +39 -0
- data/spec/attr_validator/validators/not_nil_validator_spec.rb +25 -0
- data/spec/attr_validator/validators/numericality_validator_spec.rb +50 -0
- data/spec/attr_validator/validators/presence_validator_spec.rb +25 -0
- data/spec/attr_validator/validators/regexp_validator_spec.rb +24 -0
- data/spec/attr_validator/validators/url_validator_spec.rb +24 -0
- data/spec/spec_helper.rb +7 -0
- metadata +145 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
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 [](https://travis-ci.org/AlbertGazizov/attr_validator) [](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,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"
|