pure_validator 0.3.0

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