toolchain 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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