knight 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/.gitignore +1 -0
  2. data/.rspec +2 -0
  3. data/.travis.yml +9 -0
  4. data/Gemfile +7 -0
  5. data/Gemfile.dev +16 -0
  6. data/README.md +3 -1
  7. data/Rakefile +6 -1
  8. data/knight.gemspec +4 -1
  9. data/lib/knight.rb +18 -2
  10. data/lib/knight/error.rb +60 -0
  11. data/lib/knight/instance_methods.rb +69 -0
  12. data/lib/knight/result.rb +86 -0
  13. data/lib/knight/rule.rb +74 -0
  14. data/lib/knight/rule/exact_length.rb +63 -0
  15. data/lib/knight/rule/format.rb +62 -0
  16. data/lib/knight/rule/inclusion.rb +62 -0
  17. data/lib/knight/rule/maximum_length.rb +63 -0
  18. data/lib/knight/rule/minimum_length.rb +63 -0
  19. data/lib/knight/rule/presence.rb +37 -0
  20. data/lib/knight/rule/range_length.rb +64 -0
  21. data/lib/knight/validator.rb +91 -0
  22. data/lib/knight/version.rb +3 -1
  23. data/metrics/flog.yml +2 -0
  24. data/metrics/rubocop.yml +17 -0
  25. data/spec/integration/contextual_validator_spec.rb +41 -0
  26. data/spec/integration/knight_spec.rb +30 -0
  27. data/spec/spec_helper.rb +18 -0
  28. data/spec/unit/knight/error/class_methods/new_spec.rb +15 -0
  29. data/spec/unit/knight/error/message_spec.rb +26 -0
  30. data/spec/unit/knight/instance_methods/class_methods/included_spec.rb +24 -0
  31. data/spec/unit/knight/instance_methods/class_methods/new_spec.rb +12 -0
  32. data/spec/unit/knight/instance_methods/class_methods/validator_spec.rb +13 -0
  33. data/spec/unit/knight/instance_methods/run_spec.rb +29 -0
  34. data/spec/unit/knight/result/class_methods/new_spec.rb +13 -0
  35. data/spec/unit/knight/result/errors_spec.rb +71 -0
  36. data/spec/unit/knight/result/valid_predicate_spec.rb +28 -0
  37. data/spec/unit/knight/rule/class_methods/new_spec.rb +38 -0
  38. data/spec/unit/knight/rule/error_spec.rb +29 -0
  39. data/spec/unit/knight/rule/exact_length/class_methods/new_spec.rb +27 -0
  40. data/spec/unit/knight/rule/exact_length/matches_predicate_spec.rb +19 -0
  41. data/spec/unit/knight/rule/exact_length/to_hash_spec.rb +12 -0
  42. data/spec/unit/knight/rule/format/class_methods/new_spec.rb +26 -0
  43. data/spec/unit/knight/rule/format/matches_predicate_spec.rb +31 -0
  44. data/spec/unit/knight/rule/format/to_hash_spec.rb +12 -0
  45. data/spec/unit/knight/rule/inclusion/class_methods/new_spec.rb +28 -0
  46. data/spec/unit/knight/rule/inclusion/matches_predicate_spec.rb +48 -0
  47. data/spec/unit/knight/rule/inclusion/to_hash_spec.rb +12 -0
  48. data/spec/unit/knight/rule/maximum_length/class_methods/new_spec.rb +27 -0
  49. data/spec/unit/knight/rule/maximum_length/matches_predicate_spec.rb +21 -0
  50. data/spec/unit/knight/rule/maximum_length/to_hash_spec.rb +12 -0
  51. data/spec/unit/knight/rule/minimum_length/class_methods/new_spec.rb +27 -0
  52. data/spec/unit/knight/rule/minimum_length/matches_predicate_spec.rb +21 -0
  53. data/spec/unit/knight/rule/minimum_length/to_hash_spec.rb +12 -0
  54. data/spec/unit/knight/rule/presence/matches_predicate_spec.rb +27 -0
  55. data/spec/unit/knight/rule/range_length/class_methods/new_spec.rb +27 -0
  56. data/spec/unit/knight/rule/range_length/matches_predicate_spec.rb +19 -0
  57. data/spec/unit/knight/rule/range_length/to_hash_spec.rb +12 -0
  58. data/spec/unit/knight/rule/to_hash_spec.rb +13 -0
  59. data/spec/unit/knight/validator/add_spec.rb +29 -0
  60. data/spec/unit/knight/validator/class_methods/new_spec.rb +18 -0
  61. data/spec/unit/knight/validator/context_spec.rb +13 -0
  62. data/spec/unit/knight/validator/rules_spec.rb +29 -0
  63. data/spec/unit/knight/validator/run_spec.rb +26 -0
  64. metadata +138 -6
data/.gitignore CHANGED
@@ -15,3 +15,4 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ bin
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --order random
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ script: "COVERAGE=true bundle exec rspec"
3
+ rvm:
4
+ - 1.9.3
5
+ - 2.0.0
6
+ notifications:
7
+ email:
8
+ - handi_wiguna@yahoo.com
9
+
data/Gemfile CHANGED
@@ -1,4 +1,11 @@
1
+ # encoding: utf-8
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in knight.gemspec
4
6
  gemspec
7
+
8
+ group :development, :test do
9
+ gem 'rontgen'
10
+ eval_gemfile('Gemfile.dev') if File.exists?('Gemfile.dev')
11
+ end
@@ -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
- TODO: Write a gem description
3
+ [![Build Status](https://travis-ci.org/handiwiguna/knight.png?branch=master)](https://travis-ci.org/handiwiguna/knight)
4
+ [![Code Climate](https://codeclimate.com/github/handiwiguna/knight.png)](https://codeclimate.com/github/handiwiguna/knight)
5
+ [![Coverage Status](https://coveralls.io/repos/handiwiguna/knight/badge.png?branch=master)](https://coveralls.io/r/handiwiguna/knight)
4
6
 
5
7
  ## Installation
6
8
 
data/Rakefile CHANGED
@@ -1 +1,6 @@
1
- require "bundler/gem_tasks"
1
+ # encoding: utf-8
2
+
3
+ require 'bundler/gem_tasks'
4
+
5
+ require 'rontgen'
6
+ Rontgen.load_tasks
@@ -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 = ["handi@domikado.com"]
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
@@ -1,5 +1,21 @@
1
- require "knight/version"
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
@@ -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
@@ -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