knight 0.0.1 → 0.0.2
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 +1 -0
- data/.rspec +2 -0
- data/.travis.yml +9 -0
- data/Gemfile +7 -0
- data/Gemfile.dev +16 -0
- data/README.md +3 -1
- data/Rakefile +6 -1
- data/knight.gemspec +4 -1
- data/lib/knight.rb +18 -2
- data/lib/knight/error.rb +60 -0
- data/lib/knight/instance_methods.rb +69 -0
- data/lib/knight/result.rb +86 -0
- data/lib/knight/rule.rb +74 -0
- data/lib/knight/rule/exact_length.rb +63 -0
- data/lib/knight/rule/format.rb +62 -0
- data/lib/knight/rule/inclusion.rb +62 -0
- data/lib/knight/rule/maximum_length.rb +63 -0
- data/lib/knight/rule/minimum_length.rb +63 -0
- data/lib/knight/rule/presence.rb +37 -0
- data/lib/knight/rule/range_length.rb +64 -0
- data/lib/knight/validator.rb +91 -0
- data/lib/knight/version.rb +3 -1
- data/metrics/flog.yml +2 -0
- data/metrics/rubocop.yml +17 -0
- data/spec/integration/contextual_validator_spec.rb +41 -0
- data/spec/integration/knight_spec.rb +30 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/unit/knight/error/class_methods/new_spec.rb +15 -0
- data/spec/unit/knight/error/message_spec.rb +26 -0
- data/spec/unit/knight/instance_methods/class_methods/included_spec.rb +24 -0
- data/spec/unit/knight/instance_methods/class_methods/new_spec.rb +12 -0
- data/spec/unit/knight/instance_methods/class_methods/validator_spec.rb +13 -0
- data/spec/unit/knight/instance_methods/run_spec.rb +29 -0
- data/spec/unit/knight/result/class_methods/new_spec.rb +13 -0
- data/spec/unit/knight/result/errors_spec.rb +71 -0
- data/spec/unit/knight/result/valid_predicate_spec.rb +28 -0
- data/spec/unit/knight/rule/class_methods/new_spec.rb +38 -0
- data/spec/unit/knight/rule/error_spec.rb +29 -0
- data/spec/unit/knight/rule/exact_length/class_methods/new_spec.rb +27 -0
- data/spec/unit/knight/rule/exact_length/matches_predicate_spec.rb +19 -0
- data/spec/unit/knight/rule/exact_length/to_hash_spec.rb +12 -0
- data/spec/unit/knight/rule/format/class_methods/new_spec.rb +26 -0
- data/spec/unit/knight/rule/format/matches_predicate_spec.rb +31 -0
- data/spec/unit/knight/rule/format/to_hash_spec.rb +12 -0
- data/spec/unit/knight/rule/inclusion/class_methods/new_spec.rb +28 -0
- data/spec/unit/knight/rule/inclusion/matches_predicate_spec.rb +48 -0
- data/spec/unit/knight/rule/inclusion/to_hash_spec.rb +12 -0
- data/spec/unit/knight/rule/maximum_length/class_methods/new_spec.rb +27 -0
- data/spec/unit/knight/rule/maximum_length/matches_predicate_spec.rb +21 -0
- data/spec/unit/knight/rule/maximum_length/to_hash_spec.rb +12 -0
- data/spec/unit/knight/rule/minimum_length/class_methods/new_spec.rb +27 -0
- data/spec/unit/knight/rule/minimum_length/matches_predicate_spec.rb +21 -0
- data/spec/unit/knight/rule/minimum_length/to_hash_spec.rb +12 -0
- data/spec/unit/knight/rule/presence/matches_predicate_spec.rb +27 -0
- data/spec/unit/knight/rule/range_length/class_methods/new_spec.rb +27 -0
- data/spec/unit/knight/rule/range_length/matches_predicate_spec.rb +19 -0
- data/spec/unit/knight/rule/range_length/to_hash_spec.rb +12 -0
- data/spec/unit/knight/rule/to_hash_spec.rb +13 -0
- data/spec/unit/knight/validator/add_spec.rb +29 -0
- data/spec/unit/knight/validator/class_methods/new_spec.rb +18 -0
- data/spec/unit/knight/validator/context_spec.rb +13 -0
- data/spec/unit/knight/validator/rules_spec.rb +29 -0
- data/spec/unit/knight/validator/run_spec.rb +26 -0
- metadata +138 -6
data/.gitignore
CHANGED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/Gemfile.dev
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
group :development do
|
4
|
+
gem 'rake', '~>10.1'
|
5
|
+
gem 'rspec', '~>2.13'
|
6
|
+
end
|
7
|
+
|
8
|
+
group :metrics do
|
9
|
+
gem 'flog', '~>4.1'
|
10
|
+
gem 'rubocop', git: 'https://github.com/bbatsov/rubocop'
|
11
|
+
gem 'simplecov', '~>0.7'
|
12
|
+
gem 'mutant', '0.3.0.rc1'
|
13
|
+
gem 'coveralls', '~>0.6.7', require: false
|
14
|
+
end
|
15
|
+
|
16
|
+
eval_gemfile('Gemfile.local') if File.exists?('Gemfile.local')
|
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# Knight
|
2
2
|
|
3
|
-
|
3
|
+
[](https://travis-ci.org/handiwiguna/knight)
|
4
|
+
[](https://codeclimate.com/github/handiwiguna/knight)
|
5
|
+
[](https://coveralls.io/r/handiwiguna/knight)
|
4
6
|
|
5
7
|
## Installation
|
6
8
|
|
data/Rakefile
CHANGED
data/knight.gemspec
CHANGED
@@ -7,7 +7,7 @@ Gem::Specification.new do |gem|
|
|
7
7
|
gem.name = "knight"
|
8
8
|
gem.version = Knight::VERSION
|
9
9
|
gem.authors = ["Handi Wiguna"]
|
10
|
-
gem.email = ["
|
10
|
+
gem.email = ["handi_wiguna@yahoo.com"]
|
11
11
|
gem.description = ""
|
12
12
|
gem.summary = ""
|
13
13
|
gem.homepage = ""
|
@@ -16,4 +16,7 @@ Gem::Specification.new do |gem|
|
|
16
16
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
17
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
18
|
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_dependency('equalizer', '~>0.0.5')
|
21
|
+
gem.add_dependency('abstract_type', '~>0.0.5')
|
19
22
|
end
|
data/lib/knight.rb
CHANGED
@@ -1,5 +1,21 @@
|
|
1
|
-
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'equalizer'
|
4
|
+
require 'abstract_type'
|
5
|
+
|
6
|
+
require 'knight/version'
|
7
|
+
require 'knight/rule'
|
8
|
+
require 'knight/rule/presence'
|
9
|
+
require 'knight/rule/exact_length'
|
10
|
+
require 'knight/rule/maximum_length'
|
11
|
+
require 'knight/rule/minimum_length'
|
12
|
+
require 'knight/rule/range_length'
|
13
|
+
require 'knight/rule/format'
|
14
|
+
require 'knight/rule/inclusion'
|
15
|
+
require 'knight/error'
|
16
|
+
require 'knight/result'
|
17
|
+
require 'knight/validator'
|
18
|
+
require 'knight/instance_methods'
|
2
19
|
|
3
20
|
module Knight
|
4
|
-
# Your code goes here...
|
5
21
|
end
|
data/lib/knight/error.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Knight
|
4
|
+
|
5
|
+
# A representation of an error
|
6
|
+
class Error
|
7
|
+
include Equalizer.new(:rule, :resource)
|
8
|
+
|
9
|
+
# Broken rule
|
10
|
+
#
|
11
|
+
# @return [Rule]
|
12
|
+
#
|
13
|
+
# @api private
|
14
|
+
attr_reader :rule
|
15
|
+
|
16
|
+
# Object under validation
|
17
|
+
#
|
18
|
+
# @return [Object]
|
19
|
+
#
|
20
|
+
# @api private
|
21
|
+
attr_reader :resource
|
22
|
+
|
23
|
+
# Initialize an error
|
24
|
+
#
|
25
|
+
# @param [Rule] rule
|
26
|
+
# @param [Object] resource
|
27
|
+
#
|
28
|
+
# @return [Error]
|
29
|
+
#
|
30
|
+
# @api private
|
31
|
+
def initialize(rule, resource)
|
32
|
+
@rule = rule
|
33
|
+
@resource = resource
|
34
|
+
end
|
35
|
+
|
36
|
+
# Return formatted error message
|
37
|
+
#
|
38
|
+
# @example
|
39
|
+
# error_message = error.message
|
40
|
+
#
|
41
|
+
# @return [String]
|
42
|
+
#
|
43
|
+
# @api public
|
44
|
+
def message
|
45
|
+
rule.message % rule.to_hash.merge(value: value)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
# Return attribute value
|
51
|
+
#
|
52
|
+
# @return [Object]
|
53
|
+
#
|
54
|
+
# @api private
|
55
|
+
def value
|
56
|
+
attribute = rule.attribute_name
|
57
|
+
resource.public_send(attribute) if resource.respond_to?(attribute)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Knight
|
4
|
+
|
5
|
+
# Base functionality that added when included to a class
|
6
|
+
module InstanceMethods
|
7
|
+
|
8
|
+
# Return the resource
|
9
|
+
#
|
10
|
+
# @return [Object]
|
11
|
+
#
|
12
|
+
# @api private
|
13
|
+
attr_reader :resource
|
14
|
+
|
15
|
+
# Extend base class to support validation
|
16
|
+
#
|
17
|
+
# @param [Object] object
|
18
|
+
#
|
19
|
+
# @return [undefined]
|
20
|
+
#
|
21
|
+
# @api private
|
22
|
+
def self.included(base)
|
23
|
+
super
|
24
|
+
base.extend(ClassMethods)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Set the resource
|
28
|
+
#
|
29
|
+
# @param [Object] resource
|
30
|
+
#
|
31
|
+
# @return [undefined]
|
32
|
+
#
|
33
|
+
# @api private
|
34
|
+
def initialize(resource)
|
35
|
+
@resource = resource
|
36
|
+
end
|
37
|
+
|
38
|
+
# Return the validation result
|
39
|
+
#
|
40
|
+
# @example
|
41
|
+
# user = User.new
|
42
|
+
#
|
43
|
+
# class RegistrationValidator
|
44
|
+
# include Knight::InstanceMethods
|
45
|
+
# validator.add(Knight::Rule::Presence.new(:username))
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# result = RegistrationValidator.new(user).result
|
49
|
+
#
|
50
|
+
# @return [Result]
|
51
|
+
#
|
52
|
+
# @api public
|
53
|
+
def run(context = Validator::DEFAULT_CONTEXT)
|
54
|
+
self.class.validator.run(resource, context)
|
55
|
+
end
|
56
|
+
|
57
|
+
module ClassMethods
|
58
|
+
|
59
|
+
# Return the validator object
|
60
|
+
#
|
61
|
+
# @return [Validator]
|
62
|
+
#
|
63
|
+
# @api private
|
64
|
+
def validator
|
65
|
+
@validator = @validator || Validator.new
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Knight
|
4
|
+
class Result
|
5
|
+
include Equalizer.new(:resource, :rules)
|
6
|
+
|
7
|
+
# Return the resource
|
8
|
+
#
|
9
|
+
# @return [Object]
|
10
|
+
#
|
11
|
+
# @api private
|
12
|
+
attr_reader :resource
|
13
|
+
|
14
|
+
# Return the rule set
|
15
|
+
#
|
16
|
+
# @return [Set(Rule)]
|
17
|
+
#
|
18
|
+
# @api private
|
19
|
+
attr_reader :rules
|
20
|
+
|
21
|
+
# Initialize a validation result
|
22
|
+
#
|
23
|
+
# @param [Object] resource
|
24
|
+
# @param [Set(Rule)] rules
|
25
|
+
#
|
26
|
+
# @return [Result]
|
27
|
+
#
|
28
|
+
# @api private
|
29
|
+
def initialize(resource, rules)
|
30
|
+
@resource = resource
|
31
|
+
@rules = rules
|
32
|
+
end
|
33
|
+
|
34
|
+
# Check the result valid or not
|
35
|
+
#
|
36
|
+
# @example
|
37
|
+
# validator = Validator.new(Rule::Presence.new(:username))
|
38
|
+
# user = User.new(username: 'john')
|
39
|
+
#
|
40
|
+
# result = validator.run(resource)
|
41
|
+
# result.valid?
|
42
|
+
#
|
43
|
+
# @return [true] if valid
|
44
|
+
# @return [false] otherwise
|
45
|
+
#
|
46
|
+
# @api public
|
47
|
+
def valid?
|
48
|
+
errors.empty?
|
49
|
+
end
|
50
|
+
|
51
|
+
# Return the result errors
|
52
|
+
#
|
53
|
+
# @example
|
54
|
+
# validator = Validator.new(Rule::Presence.new(:username))
|
55
|
+
# user = User.new(username: 'john')
|
56
|
+
#
|
57
|
+
# result = validator.run(resource)
|
58
|
+
# result.errors
|
59
|
+
#
|
60
|
+
# @return [Set(Error)]
|
61
|
+
#
|
62
|
+
# @api public
|
63
|
+
def errors
|
64
|
+
rules.each_with_object(Set.new) do |rule, errors|
|
65
|
+
error = attribute_check(rule)
|
66
|
+
errors << error if error
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
# Check an attribute to the rule
|
73
|
+
#
|
74
|
+
# @return [Error] if invalid
|
75
|
+
# @return [nil] otherwise
|
76
|
+
#
|
77
|
+
# @api private
|
78
|
+
def attribute_check(rule)
|
79
|
+
if resource.respond_to?(rule.attribute_name)
|
80
|
+
rule.error(resource)
|
81
|
+
else
|
82
|
+
Error.new(rule, resource) if rule.kind_of?(Rule::Presence)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/lib/knight/rule.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Knight
|
4
|
+
|
5
|
+
# An abstract class of a rule
|
6
|
+
class Rule
|
7
|
+
include AbstractType
|
8
|
+
include Equalizer.new(:attribute_name, :options)
|
9
|
+
|
10
|
+
DEFAULT_MESSAGE = ''.freeze
|
11
|
+
|
12
|
+
# Attribute name to validate
|
13
|
+
#
|
14
|
+
# @return [Symbol]
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
attr_reader :attribute_name
|
18
|
+
|
19
|
+
# Attribute name to validate
|
20
|
+
#
|
21
|
+
# @return [Symbol]
|
22
|
+
#
|
23
|
+
# @api private
|
24
|
+
attr_reader :options
|
25
|
+
|
26
|
+
# Message to displayed when rule is violated
|
27
|
+
#
|
28
|
+
# @return [String]
|
29
|
+
#
|
30
|
+
# @api private
|
31
|
+
attr_reader :message
|
32
|
+
|
33
|
+
# Initialize a rule
|
34
|
+
#
|
35
|
+
# @param [Symbol] attribute_name
|
36
|
+
# @param [Hash] options for this rule
|
37
|
+
#
|
38
|
+
# @option options [String] :message error message
|
39
|
+
#
|
40
|
+
# @return [undefined]
|
41
|
+
#
|
42
|
+
# @api private
|
43
|
+
def initialize(attribute_name, options = {})
|
44
|
+
@attribute_name = attribute_name
|
45
|
+
@options = options
|
46
|
+
@message = @options.fetch(:message, self.class::DEFAULT_MESSAGE)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Return error if the rule not satisfied
|
50
|
+
#
|
51
|
+
# @param [Object] resource
|
52
|
+
#
|
53
|
+
# @return [Error] if the rule not matched
|
54
|
+
# @return [nil] if the rule is satisfied
|
55
|
+
#
|
56
|
+
# @api private
|
57
|
+
def error(resource)
|
58
|
+
value = resource.public_send(attribute_name)
|
59
|
+
Error.new(self, resource) unless matches?(value)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Return the rule as a hash
|
63
|
+
#
|
64
|
+
# @return [Hash]
|
65
|
+
#
|
66
|
+
# @example
|
67
|
+
# hash = rule.to_hash
|
68
|
+
#
|
69
|
+
# @api public
|
70
|
+
def to_hash
|
71
|
+
{ attribute: attribute_name }.freeze
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Knight
|
4
|
+
class Rule
|
5
|
+
|
6
|
+
# A rule for checking the length of a value
|
7
|
+
class ExactLength < self
|
8
|
+
DEFAULT_MESSAGE = '%{attribute} has an invalid length'.freeze
|
9
|
+
|
10
|
+
# Length value
|
11
|
+
#
|
12
|
+
# @return [Fixnum]
|
13
|
+
#
|
14
|
+
# @api private
|
15
|
+
attr_reader :length
|
16
|
+
|
17
|
+
# Initialize an exact length rule
|
18
|
+
#
|
19
|
+
# @param [Symbol] attribute_name
|
20
|
+
# @param [Fixnum] length
|
21
|
+
# @param [Hash] options for this rule
|
22
|
+
#
|
23
|
+
# @option options [String] :message error message
|
24
|
+
#
|
25
|
+
# @example
|
26
|
+
# rule = Knight::Rule::ExactLength.new(:username, 20)
|
27
|
+
#
|
28
|
+
# @return [undefined]
|
29
|
+
#
|
30
|
+
# @api public
|
31
|
+
def initialize(attribute_name, length, options = {})
|
32
|
+
super(attribute_name, options)
|
33
|
+
@length = length
|
34
|
+
end
|
35
|
+
|
36
|
+
# Check length of the value
|
37
|
+
#
|
38
|
+
# @param [Object] value
|
39
|
+
#
|
40
|
+
# @return [true] if the length is matched
|
41
|
+
# @return [false] otherwise
|
42
|
+
#
|
43
|
+
# @api private
|
44
|
+
def matches?(value)
|
45
|
+
length == value.length
|
46
|
+
end
|
47
|
+
|
48
|
+
# Return the rule as a hash
|
49
|
+
#
|
50
|
+
# @return [Hash]
|
51
|
+
#
|
52
|
+
# @example
|
53
|
+
# hash = rule.to_hash
|
54
|
+
#
|
55
|
+
# @api public
|
56
|
+
def to_hash
|
57
|
+
super.merge({
|
58
|
+
length: length
|
59
|
+
}).freeze
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|