pure_validator 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +7 -0
  3. data/.travis.yml +5 -0
  4. data/Gemfile +8 -0
  5. data/Gemfile.lock +47 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +139 -0
  8. data/Rakefile +1 -0
  9. data/bin/console +14 -0
  10. data/lib/pure_validator/args_validator.rb +107 -0
  11. data/lib/pure_validator/concern.rb +136 -0
  12. data/lib/pure_validator/core_extensions/class_attribute.rb +143 -0
  13. data/lib/pure_validator/core_extensions/humanize.rb +44 -0
  14. data/lib/pure_validator/errors.rb +23 -0
  15. data/lib/pure_validator/i18n.rb +7 -0
  16. data/lib/pure_validator/locales/en.yml +24 -0
  17. data/lib/pure_validator/locales/ru.yml +24 -0
  18. data/lib/pure_validator/validation_errors.rb +248 -0
  19. data/lib/pure_validator/validator.rb +150 -0
  20. data/lib/pure_validator/validators/email_validator.rb +48 -0
  21. data/lib/pure_validator/validators/exclusion_validator.rb +22 -0
  22. data/lib/pure_validator/validators/inclusion_validator.rb +27 -0
  23. data/lib/pure_validator/validators/length_validator.rb +32 -0
  24. data/lib/pure_validator/validators/not_nil_validator.rb +25 -0
  25. data/lib/pure_validator/validators/numericality_validator.rb +39 -0
  26. data/lib/pure_validator/validators/presence_validator.rb +26 -0
  27. data/lib/pure_validator/validators/regexp_validator.rb +21 -0
  28. data/lib/pure_validator/validators/url_validator.rb +25 -0
  29. data/lib/pure_validator/validators.rb +11 -0
  30. data/lib/pure_validator/version.rb +3 -0
  31. data/lib/pure_validator.rb +43 -0
  32. data/pure_validator.gemspec +26 -0
  33. data/spec/pure_validator/args_validator_spec.rb +169 -0
  34. data/spec/pure_validator/errors_spec.rb +10 -0
  35. data/spec/pure_validator/validation_errors_spec.rb +109 -0
  36. data/spec/pure_validator/validator_spec.rb +234 -0
  37. data/spec/pure_validator/validators/email_validator_spec.rb +35 -0
  38. data/spec/pure_validator/validators/exclusion_validator_spec.rb +23 -0
  39. data/spec/pure_validator/validators/inclusion_validator_spec.rb +23 -0
  40. data/spec/pure_validator/validators/length_validator_spec.rb +38 -0
  41. data/spec/pure_validator/validators/not_nil_validator_spec.rb +44 -0
  42. data/spec/pure_validator/validators/numericality_validator_spec.rb +49 -0
  43. data/spec/pure_validator/validators/presence_validator_spec.rb +38 -0
  44. data/spec/pure_validator/validators/regexp_validator_spec.rb +23 -0
  45. data/spec/pure_validator/validators/url_validator_spec.rb +35 -0
  46. data/spec/spec_helper.rb +21 -0
  47. 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
@@ -0,0 +1,7 @@
1
+ vendor/
2
+ tags
3
+ .bundle
4
+ .DS_Store
5
+ pkg
6
+ coverage/
7
+ .byebug_history
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ rvm:
2
+ - 2.0.0
3
+ - 2.2.0
4
+ - 2.3.0
5
+ script: "bundle exec rspec spec/"
data/Gemfile ADDED
@@ -0,0 +1,8 @@
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
+ end
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