toolchain 0.1.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 (61) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +10 -0
  5. data/Gemfile.lock +34 -0
  6. data/LICENSE +22 -0
  7. data/README.md +254 -0
  8. data/Rakefile +1 -0
  9. data/lib/toolchain.rb +2 -0
  10. data/lib/toolchain/attributes.rb +154 -0
  11. data/lib/toolchain/attributes/configuration.rb +41 -0
  12. data/lib/toolchain/attributes/errors.rb +5 -0
  13. data/lib/toolchain/attributes/errors/invalid_hash_transformation.rb +4 -0
  14. data/lib/toolchain/attributes/errors/invalid_mass_assignment.rb +4 -0
  15. data/lib/toolchain/attributes/errors/type_mismatch.rb +4 -0
  16. data/lib/toolchain/attributes/ext/boolean.rb +4 -0
  17. data/lib/toolchain/attributes/helpers.rb +72 -0
  18. data/lib/toolchain/validations.rb +103 -0
  19. data/lib/toolchain/validations/delegation.rb +31 -0
  20. data/lib/toolchain/validations/delegator.rb +51 -0
  21. data/lib/toolchain/validations/helpers.rb +58 -0
  22. data/lib/toolchain/validations/validation_errors.rb +82 -0
  23. data/lib/toolchain/validations/validators.rb +12 -0
  24. data/lib/toolchain/validations/validators/acceptance.rb +25 -0
  25. data/lib/toolchain/validations/validators/base.rb +54 -0
  26. data/lib/toolchain/validations/validators/confirmation.rb +39 -0
  27. data/lib/toolchain/validations/validators/email.rb +26 -0
  28. data/lib/toolchain/validations/validators/exclusion.rb +29 -0
  29. data/lib/toolchain/validations/validators/format.rb +25 -0
  30. data/lib/toolchain/validations/validators/inclusion.rb +29 -0
  31. data/lib/toolchain/validations/validators/length.rb +60 -0
  32. data/lib/toolchain/validations/validators/presence.rb +25 -0
  33. data/lib/toolchain/version.rb +3 -0
  34. data/spec/spec_helper.rb +11 -0
  35. data/spec/toolchain/attributes/attribute_spec.rb +47 -0
  36. data/spec/toolchain/attributes/attributes_spec.rb +193 -0
  37. data/spec/toolchain/attributes/base_helper.rb +37 -0
  38. data/spec/toolchain/attributes/boolean_spec.rb +38 -0
  39. data/spec/toolchain/attributes/configuration_spec.rb +33 -0
  40. data/spec/toolchain/attributes/date_time_spec.rb +21 -0
  41. data/spec/toolchain/attributes/hash_spec.rb +61 -0
  42. data/spec/toolchain/attributes/include_attributes_spec.rb +19 -0
  43. data/spec/toolchain/attributes/initializer_spec.rb +32 -0
  44. data/spec/toolchain/validations/base_helper.rb +2 -0
  45. data/spec/toolchain/validations/custom_validations_spec.rb +26 -0
  46. data/spec/toolchain/validations/delegation_spec.rb +70 -0
  47. data/spec/toolchain/validations/include_validations_spec.rb +20 -0
  48. data/spec/toolchain/validations/inheritence_spec.rb +26 -0
  49. data/spec/toolchain/validations/multiple_validations_spec.rb +19 -0
  50. data/spec/toolchain/validations/nested_validations_spec.rb +68 -0
  51. data/spec/toolchain/validations/validation_errors_spec.rb +9 -0
  52. data/spec/toolchain/validations/validators/acceptance_spec.rb +48 -0
  53. data/spec/toolchain/validations/validators/confirmation_spec.rb +86 -0
  54. data/spec/toolchain/validations/validators/email_spec.rb +41 -0
  55. data/spec/toolchain/validations/validators/exclusion_spec.rb +53 -0
  56. data/spec/toolchain/validations/validators/format_spec.rb +44 -0
  57. data/spec/toolchain/validations/validators/inclusion_spec.rb +50 -0
  58. data/spec/toolchain/validations/validators/length_spec.rb +134 -0
  59. data/spec/toolchain/validations/validators/presence_spec.rb +34 -0
  60. data/toolchain.gemspec +21 -0
  61. metadata +161 -0
@@ -0,0 +1,41 @@
1
+ module Toolchain::Attributes::Configuration
2
+ extend self
3
+
4
+ # @param type [Symbol] :symbol or :string.
5
+ #
6
+ def hash_transformation=(type)
7
+ if [:symbolize_keys, :stringify_keys].include?(type)
8
+ @hash_transformation = type
9
+ else
10
+ raise Toolchain::Attributes::Errors::InvalidHashTransformation,
11
+ "valid types: :symbolize_keys, :stringify_keys."
12
+ end
13
+ end
14
+
15
+ # @return [Symbol] :symbolize_keys or :stringify_keys.
16
+ #
17
+ def hash_transformation
18
+ @hash_transformation ||= :symbolize_keys
19
+ end
20
+
21
+ # Determines whether or not to include nil values
22
+ # in the object.attributes Hash.
23
+ #
24
+ # @param value [Boolean]
25
+ #
26
+ def include_nil_in_attributes=(value)
27
+ @include_nil_in_attributes = value
28
+ end
29
+
30
+ # @return [Boolean]
31
+ #
32
+ def include_nil_in_attributes
33
+ @include_nil_in_attributes ||= false
34
+ end
35
+
36
+ # @yield Toolchain::Attributes::Configuration
37
+ #
38
+ def configure
39
+ yield self
40
+ end
41
+ end
@@ -0,0 +1,5 @@
1
+ module Toolchain::Attributes::Errors
2
+ require_relative "errors/invalid_hash_transformation"
3
+ require_relative "errors/invalid_mass_assignment"
4
+ require_relative "errors/type_mismatch"
5
+ end
@@ -0,0 +1,4 @@
1
+ module Toolchain::Attributes::Errors
2
+ class InvalidHashTransformation < StandardError
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Toolchain::Attributes::Errors
2
+ class InvalidMassAssignment < StandardError
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Toolchain::Attributes::Errors
2
+ class TypeMismatch < StandardError
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ unless defined?(Boolean)
2
+ class Boolean
3
+ end
4
+ end
@@ -0,0 +1,72 @@
1
+ module Toolchain::Attributes::Helpers
2
+ extend self
3
+
4
+ # @param klass [Class]
5
+ #
6
+ # @yield [Symbol] Each defined attribute (key) name.
7
+ #
8
+ def each_key(klass)
9
+ while ![Class, Module, Object, BasicObject, nil].include?(klass)
10
+ klass.keys.each { |key| yield key }
11
+ klass = klass.superclass
12
+ end
13
+ end
14
+
15
+ # @param value [Object]
16
+ # @param types [Array<Class>]
17
+ #
18
+ # @return [Boolean] true if the provided value doesn't
19
+ # match any of the provided type classes.
20
+ #
21
+ def invalid_value?(value, *types)
22
+ value = value.call if value.kind_of?(Proc)
23
+
24
+ return false if value.nil?
25
+
26
+ types.flatten.each do |type|
27
+ return false if value.kind_of?(type)
28
+ end
29
+
30
+ true
31
+ end
32
+
33
+ # Converts all keys to Symbol-type, including
34
+ # nested hashes.
35
+ #
36
+ # @param value [Hash]
37
+ # @return [Hash]
38
+ #
39
+ def symbolize_keys(value)
40
+ deep_transform_keys(value) { |key| key.to_sym rescue key }
41
+ end
42
+
43
+ # Converts all keys to String-type, including
44
+ # nested hashes.
45
+ #
46
+ # @param value [Hash]
47
+ # @return [Hash]
48
+ #
49
+ def stringify_keys(value)
50
+ deep_transform_keys(value) { |key| key.to_s rescue key }
51
+ end
52
+
53
+ private
54
+
55
+ # Recursive key transformation method.
56
+ #
57
+ # @param value [Hash]
58
+ # @param block [Proc]
59
+ #
60
+ def deep_transform_keys(value, &block)
61
+ Hash.new.tap do |result|
62
+ value.each do |key, value|
63
+ result[yield(key)] =
64
+ if value.kind_of?(Hash)
65
+ deep_transform_keys(value, &block)
66
+ else
67
+ value
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,103 @@
1
+ require_relative "version"
2
+
3
+ module Toolchain::Validations
4
+ require_relative "validations/helpers"
5
+ require_relative "validations/validation_errors"
6
+ require_relative "validations/validators"
7
+ require_relative "validations/delegator"
8
+ require_relative "validations/delegation"
9
+
10
+ module ClassMethods
11
+
12
+ # @return [Array] all the standard validation operations to run.
13
+ #
14
+ def validations
15
+ @validations ||= []
16
+ end
17
+
18
+ # @return [Array] all the custom validation operations to run.
19
+ #
20
+ def custom_validations
21
+ @custom_validations ||= []
22
+ end
23
+
24
+ # Adds the provided validator to the validations array for
25
+ # the provided attribute key_path.
26
+ #
27
+ # @param args [Array<Symbol, Hash>]
28
+ #
29
+ # @example
30
+ # class Device
31
+ # validates :name, presence: true
32
+ # validates :config, :url, presence: true
33
+ # end
34
+ #
35
+ def validates(*args)
36
+ options, key_path = args.pop, args
37
+
38
+ options.each do |key, data|
39
+ if validator = Helpers.find_validator(key)
40
+ validations.push(key_path: key_path, validator: validator, data: data)
41
+ end
42
+ end
43
+ end
44
+
45
+ # Adds the provided method name to the validations array.
46
+ # This method will be called during validation time and allows
47
+ # you to do program-specific validations.
48
+ #
49
+ # @param method [Symbol, String]
50
+ #
51
+ def validate(method)
52
+ custom_validations.push(method.to_sym)
53
+ end
54
+
55
+ # Takes a Proc that contains validation logic and applies
56
+ # that to this class.
57
+ #
58
+ # @param validations [Proc]
59
+ #
60
+ def include_validations(validations)
61
+ class_eval(&validations)
62
+ end
63
+ end
64
+
65
+ module InstanceMethods
66
+
67
+ # @return [Toolchain::Validations::ValidationErrors]
68
+ #
69
+ def errors
70
+ @errors ||= ValidationErrors.new
71
+ end
72
+
73
+ # Runs all the validation operations and will apply error
74
+ # messages to the ValidationErrors object when validator errors occur.
75
+ #
76
+ def validate
77
+ Helpers.each_validation(self.class) do |v|
78
+ v[:validator].new(self, v[:key_path], v[:data]).validate
79
+ end
80
+
81
+ Helpers.each_validation(self.class, :custom_validations) { |v| send(v) }
82
+ end
83
+
84
+ # Resets the ValidationErrors object, re-validates the current object
85
+ # and then determines whether or not this object is valid.
86
+ #
87
+ # @note If invalid (returns `false`), errors will have been
88
+ # added to the `object.errors` class.
89
+ #
90
+ # @return [Boolean]
91
+ #
92
+ def valid?
93
+ errors.reset
94
+ validate
95
+ errors.empty?
96
+ end
97
+ end
98
+
99
+ def self.included(base)
100
+ base.send(:include, InstanceMethods)
101
+ base.extend(ClassMethods)
102
+ end
103
+ end
@@ -0,0 +1,31 @@
1
+ module Toolchain::Validations::Delegation
2
+ module InstanceMethods
3
+
4
+ # @return [Object]
5
+ #
6
+ attr_reader :delegator
7
+
8
+ # @param delegator [Object]
9
+ #
10
+ def initialize(delegator)
11
+ @delegator = delegator
12
+ end
13
+ end
14
+
15
+ # @param base [Class]
16
+ #
17
+ def self.included(base)
18
+ base.send(:include, InstanceMethods)
19
+ base.class_eval do
20
+
21
+ alias_method :_valid?, :valid?
22
+
23
+ # @return [Boolean]
24
+ #
25
+ def valid?
26
+ self.attributes = delegator.attributes
27
+ _valid?.tap { delegator.errors = errors }
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,51 @@
1
+ module Toolchain::Validations::Delegator
2
+ module InstanceMethods
3
+
4
+ # @param value [Hash]
5
+ #
6
+ attr_writer :errors
7
+
8
+ # @return [Hash]
9
+ #
10
+ def errors
11
+ @errors || {}
12
+ end
13
+
14
+ # @return [Boolean]
15
+ #
16
+ def valid?
17
+ validator.valid?
18
+ end
19
+ end
20
+
21
+ module ClassMethods
22
+
23
+ # @param block [Proc]
24
+ #
25
+ # @example
26
+ # class MyClass
27
+ # include Toolchain::Validations::Delegator
28
+ #
29
+ # validate_with do |instance|
30
+ # if instance.new?
31
+ # NewResourceValidator
32
+ # else
33
+ # ExistingResourceValidator
34
+ # end
35
+ # end
36
+ # end
37
+ #
38
+ def validate_with(&block)
39
+ define_method(:validator) do
40
+ @validator ||= block.call(self).new(self)
41
+ end
42
+ end
43
+ end
44
+
45
+ # @param base [Class]
46
+ #
47
+ def self.included(base)
48
+ base.send(:include, InstanceMethods)
49
+ base.extend(ClassMethods)
50
+ end
51
+ end
@@ -0,0 +1,58 @@
1
+ module Toolchain::Validations::Helpers
2
+ extend self
3
+
4
+ # Finds the validator by key name.
5
+ #
6
+ # @param key [Symbol, String]
7
+ #
8
+ # @return [Object, nil] nil if the validator doesn't exist.
9
+ #
10
+ def find_validator(key)
11
+ Toolchain::Validations::Validators.const_get(classify(key))
12
+ rescue NameError
13
+ end
14
+
15
+ # Yields all defined validations from the current class,
16
+ # as well as all the (relavant) super-classes.
17
+ #
18
+ # @param klass [Class]
19
+ # @param method [Symbol, String]
20
+ #
21
+ # @yield [Hash]
22
+ #
23
+ def each_validation(klass, method = :validations)
24
+ while ![Class, Module, Object, BasicObject, nil].include?(klass)
25
+ klass.send(method).each { |v| yield v } if klass.respond_to?(method)
26
+ klass = klass.superclass
27
+ end
28
+ end
29
+
30
+ # Converts the provided value to it's class name.
31
+ #
32
+ # @param value [Symbol, String]
33
+ #
34
+ # @return [String]
35
+ #
36
+ def classify(value)
37
+ value.to_s.split("_").map(&:capitalize).join
38
+ end
39
+
40
+ # Custom inject method that includes a 3rd yield argument
41
+ # that determines whether the current iteration is the last.
42
+ #
43
+ # @param array [Array]
44
+ # @param init [Object] uses first value of array if nil.
45
+ #
46
+ # @yield memo [Object], value [Object], last [Boolean]
47
+ #
48
+ # @return [Object] The final memo result.
49
+ #
50
+ def inject(array, init = nil)
51
+ memo = init || array.shift
52
+ length = array.length
53
+ array.each_with_index do |value, index|
54
+ memo = yield memo, value, index == length - 1
55
+ end
56
+ memo
57
+ end
58
+ end
@@ -0,0 +1,82 @@
1
+ class Toolchain::Validations::ValidationErrors
2
+
3
+ Helpers = Toolchain::Validations::Helpers
4
+
5
+ # @return [Hash]
6
+ #
7
+ attr_reader :errors
8
+
9
+ # Creates a new instance of Toolchain::Validations::ValidationErrors
10
+ # and sets the initial errors state to an empty Hash.
11
+ #
12
+ def initialize
13
+ @errors = {}
14
+ end
15
+
16
+ # Adds a new message to the Toolchain::Validations::ValidationErrors object.
17
+ #
18
+ # @param args [Array<Symbol, String>]
19
+ #
20
+ # @example
21
+ # errors.add(:name, "is required")
22
+ # errors.add(:config, :url, "isn't valid")
23
+ #
24
+ def add(*args)
25
+ message, key_path = args.pop, args
26
+ key_path = [key_path].flatten
27
+
28
+ Helpers.inject(key_path, errors) do |memo, key, last|
29
+ if last
30
+ memo[key] ||= []
31
+ memo[key].push(message)
32
+ else
33
+ memo[key] ||= {}
34
+ end
35
+
36
+ memo[key]
37
+ end
38
+ end
39
+
40
+ # Resets all errors to an empty Hash.
41
+ #
42
+ def reset
43
+ @errors = {}
44
+ end
45
+
46
+ # Proxy method to allow individual values of the errors Hash
47
+ # to be accessed directly through the Toolchain::Validations::ValidationErrors object.
48
+ #
49
+ # @param key [Symbol] The key of the value you want to retrieve.
50
+ #
51
+ # @return [Object] the value of the key you want to retrieve.
52
+ #
53
+ def [](key)
54
+ errors[key]
55
+ end
56
+
57
+ # Proxy method to delegate the comparison of two values to
58
+ # the errors Hash.
59
+ #
60
+ # @param value [Object]
61
+ #
62
+ # @return [Boolean] true if both values are equal.
63
+ #
64
+ def ==(value)
65
+ errors == value
66
+ end
67
+
68
+ # Proxy method to check if there currently are any errors.
69
+ #
70
+ # @return [Boolean] true if there aren't any errors.
71
+ #
72
+ def empty?
73
+ errors.empty?
74
+ end
75
+
76
+ # @return [Hash] the Hash representation of the
77
+ # Toolchain::Validations::ValidationErrors.
78
+ #
79
+ def to_hash
80
+ errors
81
+ end
82
+ end