pure_validator 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +7 -0
- data/.travis.yml +5 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +47 -0
- data/LICENSE.txt +22 -0
- data/README.md +139 -0
- data/Rakefile +1 -0
- data/bin/console +14 -0
- data/lib/pure_validator/args_validator.rb +107 -0
- data/lib/pure_validator/concern.rb +136 -0
- data/lib/pure_validator/core_extensions/class_attribute.rb +143 -0
- data/lib/pure_validator/core_extensions/humanize.rb +44 -0
- data/lib/pure_validator/errors.rb +23 -0
- data/lib/pure_validator/i18n.rb +7 -0
- data/lib/pure_validator/locales/en.yml +24 -0
- data/lib/pure_validator/locales/ru.yml +24 -0
- data/lib/pure_validator/validation_errors.rb +248 -0
- data/lib/pure_validator/validator.rb +150 -0
- data/lib/pure_validator/validators/email_validator.rb +48 -0
- data/lib/pure_validator/validators/exclusion_validator.rb +22 -0
- data/lib/pure_validator/validators/inclusion_validator.rb +27 -0
- data/lib/pure_validator/validators/length_validator.rb +32 -0
- data/lib/pure_validator/validators/not_nil_validator.rb +25 -0
- data/lib/pure_validator/validators/numericality_validator.rb +39 -0
- data/lib/pure_validator/validators/presence_validator.rb +26 -0
- data/lib/pure_validator/validators/regexp_validator.rb +21 -0
- data/lib/pure_validator/validators/url_validator.rb +25 -0
- data/lib/pure_validator/validators.rb +11 -0
- data/lib/pure_validator/version.rb +3 -0
- data/lib/pure_validator.rb +43 -0
- data/pure_validator.gemspec +26 -0
- data/spec/pure_validator/args_validator_spec.rb +169 -0
- data/spec/pure_validator/errors_spec.rb +10 -0
- data/spec/pure_validator/validation_errors_spec.rb +109 -0
- data/spec/pure_validator/validator_spec.rb +234 -0
- data/spec/pure_validator/validators/email_validator_spec.rb +35 -0
- data/spec/pure_validator/validators/exclusion_validator_spec.rb +23 -0
- data/spec/pure_validator/validators/inclusion_validator_spec.rb +23 -0
- data/spec/pure_validator/validators/length_validator_spec.rb +38 -0
- data/spec/pure_validator/validators/not_nil_validator_spec.rb +44 -0
- data/spec/pure_validator/validators/numericality_validator_spec.rb +49 -0
- data/spec/pure_validator/validators/presence_validator_spec.rb +38 -0
- data/spec/pure_validator/validators/regexp_validator_spec.rb +23 -0
- data/spec/pure_validator/validators/url_validator_spec.rb +35 -0
- data/spec/spec_helper.rb +21 -0
- metadata +175 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b2eaaaed1d01fb6d73c7caecc66ad7b97422502b
|
4
|
+
data.tar.gz: febaaa1df0a19e771ea4246253ccf6592a46d429
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fdebd2254d6696cfec46bc455893d9622721a0f70a63efc63364847ce7b500ba55c06e4f7891076d89965e6ff076e98ae3549e36153982bc9b7394cf051f26ac
|
7
|
+
data.tar.gz: 1d881ee39ddc16f215dbe39be4450ef49ec84e2ad6ae0bdc94c9bc3bf4dc2b9030aa943fbec50f97c86be8a1fb2eda342b4f5603ea0f9a756f03e18e69b90856
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
pure_validator (0.3.0)
|
5
|
+
i18n
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
byebug (9.0.6)
|
11
|
+
codecov (0.1.9)
|
12
|
+
json
|
13
|
+
simplecov
|
14
|
+
url
|
15
|
+
diff-lcs (1.2.5)
|
16
|
+
docile (1.1.5)
|
17
|
+
i18n (0.6.9)
|
18
|
+
json (2.0.2)
|
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
|
+
simplecov (0.12.0)
|
29
|
+
docile (~> 1.1.0)
|
30
|
+
json (>= 1.8, < 3)
|
31
|
+
simplecov-html (~> 0.10.0)
|
32
|
+
simplecov-html (0.10.0)
|
33
|
+
url (0.3.2)
|
34
|
+
|
35
|
+
PLATFORMS
|
36
|
+
ruby
|
37
|
+
|
38
|
+
DEPENDENCIES
|
39
|
+
bundler (~> 1.3)
|
40
|
+
byebug
|
41
|
+
codecov
|
42
|
+
pure_validator!
|
43
|
+
rake
|
44
|
+
rspec
|
45
|
+
|
46
|
+
BUNDLED WITH
|
47
|
+
1.13.6
|
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,139 @@
|
|
1
|
+
# PureValidator [![Build Status](https://travis-ci.org/ddd-ruby/pure_validator.png)](https://travis-ci.org/ddd-ruby/pure_validator) [![Code Climate](https://codeclimate.com/github/ddd-ruby/pure_validator/badges/gpa.svg)](https://codeclimate.com/github/ddd-ruby/pure_validator) [![codecov](https://codecov.io/gh/ddd-ruby/pure_validator/branch/master/graph/badge.svg)](https://codecov.io/gh/ddd-ruby/pure_validator)
|
2
|
+
|
3
|
+
PureValidator is a simple, mostly dependency-free (except `i18n`) library to validate your domain Ruby objects.
|
4
|
+
|
5
|
+
It keeps the concerns of validation separate from the Entity / Value object itself.
|
6
|
+
|
7
|
+
This gives you the option to have different validations rules for different ocasions for the same object in a very clean and unit-testable way.
|
8
|
+
|
9
|
+
It is a simple step to separate those concerns, but it will give you unlimited flexibility and save your *SS from debugging entangled and conflicting validations for non-trivial requirements.
|
10
|
+
|
11
|
+
Do yourself a favor and start using PureValidator today, you will never look back!
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
Let's say you have the following class and you want to validate its instances:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
class Contact
|
18
|
+
attr_accessor :first_name, :last_name, :position, :age, :type, :email, :color, :status, :stage, :description, :companies
|
19
|
+
end
|
20
|
+
```
|
21
|
+
|
22
|
+
To validate objects of the Contact class define following validator:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
class ContactValidator
|
26
|
+
include PureValidator::Validator
|
27
|
+
|
28
|
+
validates :first_name, presence: true, length: { min: 4, max: 7 }
|
29
|
+
validates :last_name, length: { equal_to: 5 }
|
30
|
+
validates :position, length: { not_equal_to: 5 }
|
31
|
+
validates :age, numericality: { greater_than: 0, less_than: 150 }
|
32
|
+
validates :type, numericality: { greater_than_or_equal_to: 1, less_than_or_equal_to: 5 }
|
33
|
+
validates :email, email: true
|
34
|
+
validates :color, regexp: /#\w{6}/
|
35
|
+
validates :status, inclusion: { in: [:new, :lead] }
|
36
|
+
validates :stage, exclusion: { in: [:wrong, :bad] }
|
37
|
+
|
38
|
+
validate_associated :companies, validator: CompanyValidator
|
39
|
+
|
40
|
+
validate :check_description
|
41
|
+
|
42
|
+
def check_description(entity, errors)
|
43
|
+
if entity.description.nil?
|
44
|
+
errors.add(:description, "can't be empty")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
Instantiate the validator and call `validate` with a contact object:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
errors = ContactValidator.new.validate(contact)
|
54
|
+
```
|
55
|
+
|
56
|
+
`errors` is a Hash that contains all validation errors.
|
57
|
+
If the object is valid then errors will be an empty Hash.
|
58
|
+
|
59
|
+
### Adding own validators
|
60
|
+
|
61
|
+
PureValidator can be extended by adding your own validators.
|
62
|
+
To add a validator define a class with 2 static methods: `validate` and `validate_options`:
|
63
|
+
|
64
|
+
The following example shows the built-in inclusion validator, it validates that specified value is one of the defined values.
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
class PureValidator::Validators::InclusionValidator
|
68
|
+
|
69
|
+
# Validates that given value inscluded in the specified list
|
70
|
+
# @param value [Object] object to validate
|
71
|
+
# @parm options [Hash] validation options, e.g. { in: [:small, :medium, :large], message: "not included in the list of allowed items" }
|
72
|
+
# where :in - list of allowed values,
|
73
|
+
# message - is a message to return if value is not included in the list
|
74
|
+
# @return [Array] empty array if object is valid, list of errors otherwise
|
75
|
+
def self.validate(value, options)
|
76
|
+
return [] if value.nil?
|
77
|
+
|
78
|
+
errors = []
|
79
|
+
if options[:in]
|
80
|
+
unless options[:in].include?(value)
|
81
|
+
errors << (options[:message] || PureValidator::I18n.t('errors.should_be_included_in_list', list: options[:in]))
|
82
|
+
end
|
83
|
+
end
|
84
|
+
errors
|
85
|
+
end
|
86
|
+
|
87
|
+
# Validates that options specified in
|
88
|
+
# :inclusion are valid
|
89
|
+
def self.validate_options(options)
|
90
|
+
raise ArgumentError, "validation options should be a Hash" unless options.is_a?(Hash)
|
91
|
+
raise ArgumentError, "validation options should have :in option and it should be an array of allowed values" unless options[:in].is_a?(Array)
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
```
|
96
|
+
|
97
|
+
And register it in PureValidator:
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
PureValidator.add_validator(:inclusion, PureValidator::Validators::InclusionValidator)
|
101
|
+
```
|
102
|
+
|
103
|
+
Now you can use it:
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
class SomeValidator
|
107
|
+
include PureValidator::Validator
|
108
|
+
|
109
|
+
validates :size, inclusion: { in: [:small, :medium, :large] }
|
110
|
+
end
|
111
|
+
```
|
112
|
+
|
113
|
+
## Installation
|
114
|
+
|
115
|
+
Add this line to your application's Gemfile:
|
116
|
+
|
117
|
+
gem 'pure_validator'
|
118
|
+
|
119
|
+
And then execute:
|
120
|
+
|
121
|
+
$ bundle
|
122
|
+
|
123
|
+
Or install it yourself as:
|
124
|
+
|
125
|
+
$ gem install pure_validator
|
126
|
+
|
127
|
+
## Contributing
|
128
|
+
|
129
|
+
1. Fork it
|
130
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
131
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
132
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
133
|
+
5. Create new Pull Request
|
134
|
+
|
135
|
+
|
136
|
+
## Authors
|
137
|
+
|
138
|
+
- Albert Gazizov, [@deeper4k](https://twitter.com/deeper4k)
|
139
|
+
- Roman Heinrich, [@mindreframer](https://twitter.com/mindreframer)
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "pure_validator"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# Helper class for arguments validation
|
2
|
+
module PureValidator::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
|
+
allow!(obj, [Symbol], "#{obj_name} should be a Symbol")
|
12
|
+
end
|
13
|
+
|
14
|
+
# Checks that specifid +obj+ is a boolean
|
15
|
+
# @param obj some object
|
16
|
+
# @param obj_name object's name, used to clarify error causer in exception
|
17
|
+
def is_boolean!(obj, obj_name)
|
18
|
+
allow!(obj, [TrueClass, FalseClass], "#{obj_name} should be a Boolean")
|
19
|
+
end
|
20
|
+
|
21
|
+
# Checks that specifid +obj+ is an integer
|
22
|
+
# @param obj some object
|
23
|
+
# @param obj_name object's name, used to clarify error causer in exception
|
24
|
+
def is_integer!(obj, obj_name)
|
25
|
+
allow!(obj, [Integer], "#{obj_name} should be an Integer")
|
26
|
+
end
|
27
|
+
|
28
|
+
# Checks that specifid +obj+ is an Array
|
29
|
+
# @param obj some object
|
30
|
+
# @param obj_name object's name, used to clarify error causer in exception
|
31
|
+
def is_array!(obj, obj_name)
|
32
|
+
allow!(obj, [Array], "#{obj_name} should be an Array")
|
33
|
+
end
|
34
|
+
|
35
|
+
# Checks that specifid +obj+ is a Hash
|
36
|
+
# @param obj some object
|
37
|
+
# @param obj_name object's name, used to clarify error causer in exception
|
38
|
+
def is_hash!(obj, obj_name)
|
39
|
+
allow!(obj, [Hash], "#{obj_name} should be a Hash")
|
40
|
+
end
|
41
|
+
|
42
|
+
# Checks that specifid +obj+ is an integer or float
|
43
|
+
# @param obj some object
|
44
|
+
# @param obj_name object's name, used to clarify error causer in exception
|
45
|
+
def is_integer_or_float!(obj, obj_name)
|
46
|
+
allow!(obj, [Integer, Float], "#{obj_name} should be an Integer or Float")
|
47
|
+
end
|
48
|
+
|
49
|
+
# Checks that specifid +obj+ is a string or regexp
|
50
|
+
# @param obj some object
|
51
|
+
# @param obj_name object's name, used to clarify error causer in exception
|
52
|
+
def is_string_or_regexp!(obj, obj_name)
|
53
|
+
allow!(obj, [String, Regexp], "#{obj_name} should be a String or Regexp")
|
54
|
+
end
|
55
|
+
|
56
|
+
# Checks that specifid +obj+ is a symbol or class
|
57
|
+
# @param obj some object
|
58
|
+
# @param obj_name object's name, used to clarify error causer in exception
|
59
|
+
def is_class_or_symbol!(obj, obj_name)
|
60
|
+
allow!(obj, [Symbol, Class], "#{obj_name} should be a Symbol or Class")
|
61
|
+
end
|
62
|
+
|
63
|
+
# Checks that specifid +obj+ is a symbol or block
|
64
|
+
# @param obj some object
|
65
|
+
# @param obj_name object's name, used to clarify error causer in exception
|
66
|
+
def is_symbol_or_block!(obj, obj_name)
|
67
|
+
allow!(obj, [Symbol, Proc], "#{obj_name} should be a Symbol or Proc")
|
68
|
+
end
|
69
|
+
|
70
|
+
# Checks that specifid +hash+ has a specified +key+
|
71
|
+
# @param hash some hash
|
72
|
+
# @param key hash's key
|
73
|
+
def has_key!(hash, key)
|
74
|
+
unless hash.has_key?(key)
|
75
|
+
raise ArgError, "#{hash} should have '#{key}' key"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def not_nil!(obj, obj_name)
|
80
|
+
if obj.nil?
|
81
|
+
raise ArgError, "#{obj_name} can't be nil"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def has_only_allowed_keys!(hash, keys, obj_name)
|
86
|
+
remaining_keys = hash.keys - keys
|
87
|
+
unless remaining_keys.empty?
|
88
|
+
raise ArgError, "#{obj_name} has unacceptable options #{remaining_keys}"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Checks that specified +block+ is given
|
93
|
+
# @param block some block
|
94
|
+
def block_given!(block)
|
95
|
+
unless block
|
96
|
+
raise ArgError, "Block should be given"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def allow!(obj, klasses, msg)
|
103
|
+
return if klasses.any?{|klass| obj.is_a?(klass)}
|
104
|
+
raise ArgError, msg
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
module PureValidator
|
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,44 @@
|
|
1
|
+
module PureValidator
|
2
|
+
module Humanize
|
3
|
+
# poor mans humanize... (to not depend on inflector)
|
4
|
+
# puts humanize "hello_there", format: :class
|
5
|
+
def self.humanize(value, options = {})
|
6
|
+
options[:format] = :sentence if options.empty?
|
7
|
+
|
8
|
+
values = if value.include? '_'
|
9
|
+
value.split('_')
|
10
|
+
else
|
11
|
+
[value]
|
12
|
+
end
|
13
|
+
values.each { |v| v.downcase! }
|
14
|
+
|
15
|
+
if options[:format] == :allcaps
|
16
|
+
values.each do |value|
|
17
|
+
value.capitalize!
|
18
|
+
end
|
19
|
+
|
20
|
+
if options.empty?
|
21
|
+
options[:seperator] = " "
|
22
|
+
end
|
23
|
+
|
24
|
+
return values.join " "
|
25
|
+
end
|
26
|
+
|
27
|
+
if options[:format] == :class
|
28
|
+
values.each do |value|
|
29
|
+
value.capitalize!
|
30
|
+
end
|
31
|
+
return values.join ""
|
32
|
+
end
|
33
|
+
|
34
|
+
if options[:format] == :sentence
|
35
|
+
values[0].capitalize!
|
36
|
+
return values.join " "
|
37
|
+
end
|
38
|
+
|
39
|
+
if options[:format] == :nocaps
|
40
|
+
return values.join " "
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|