class_composer 0.0.1 → 1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a1bb350ed5d28637c5d2cf2574450eb66459c14c06a49e6f427eaece430df73f
4
- data.tar.gz: db684a27bbe7b948380538952c1a9f6a348c6c2c96c1e7774915b92709b0f372
3
+ metadata.gz: 7d53fe12431ff8f1dd4c42d7282d11a64637a0c3ab573e24a7d83443a8a9cac2
4
+ data.tar.gz: 0376e66b2fef6bc976289dd1f4e4517830dd0a63c0e12519730efc9786865c9f
5
5
  SHA512:
6
- metadata.gz: 3a316374ba96891a3b1e202893bb79d125db16126636a981b7a19f5aa3caad8b34756f6b650bbc4632996e2de49e8ceb2399b40773213145570d7804fc77a4bd
7
- data.tar.gz: 0e04d577ad1984244d0a89be64c407f456d53641486ec06531d7cf390ce8439029dc8cdea7ee42acadb123d8ee1c5e7888e061a1524cee3b2b52c842fa1282d8
6
+ metadata.gz: b08e706f2f55865e6d4fab779896450dab524b946412ee8e8be8a673b205a8f3ded0a438db6dbce53d8ebc06c6a88a2dc6cba5a4da3bd5e22a599a205ea9cc0f
7
+ data.tar.gz: bfa9491b16358fc59b221bd5ac847b861c52a6a5793dca4fc59c81a8ceef3e12c1d3148bd8679833e4c5a643e5738788a9647a1619f9aadc2171b9d497bd0f34
data/CHANGELOG.md CHANGED
@@ -1,26 +1,15 @@
1
1
  # Changelog
2
2
 
3
- All notable changes to this project will be documented in this file.
3
+ ### Versioning
4
4
 
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5
+ **Major:** x - Major Releases with new features that constitute a breaking change
7
6
 
8
- ## [Unreleased]
7
+ **Minor:** x.x - Minor changes or features added which are backwords compatible
9
8
 
10
- ## [0.0.1] - 2022-06-18
9
+ **Patch:** x.x.x - Patch Updates
11
10
 
12
- ### Added
13
- - New feature 1
14
- - New feature 2
11
+ # Release Notes
15
12
 
16
- ### Changed
17
- - Changed feature 1
18
- - Changed feature 2
19
-
20
- ### Removed
21
- - Removed feature 1
22
- - Removed feature 2
23
-
24
- ### Fixed
25
- - Bug fix 1
26
- - Bug fix 2
13
+ | Date | Version | Description |
14
+ |---|---|---|
15
+ | June 2022 | v1.0.0 | Initial launch of Composer
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- class_composer (0.0.1)
4
+ class_composer (0.1.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -1,14 +1,11 @@
1
1
  # ClassComposer
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be
4
- able to package up your Ruby library into a gem. Put your Ruby code in the file
5
- `lib/class_composer`. To experiment with that code, run
6
- `bin/console` for an interactive prompt.
7
-
8
- TODO: Delete this and the text above, and describe your gem
3
+ Basic configuration is relatively simple but tedious to do if done multiple times. throughout a project. The intention of `ClassComposer` is to DRY up as much of that configuration as possible to allow you to just write code.
9
4
 
10
5
  ## Installation
11
6
 
7
+ `ClassComposer` is hosted on RubyGems https://rubygems.org/gems/class_composer
8
+
12
9
  Add this line to your application's Gemfile:
13
10
 
14
11
  ```ruby
@@ -25,7 +22,138 @@ Or install it yourself as:
25
22
 
26
23
  ## Usage
27
24
 
28
- TODO: Write usage instructions here
25
+ ### Basic
26
+
27
+ `add_composer` is the driving method behind the composer gem. It will
28
+ - Add a setter Method
29
+ - Add a getter Method
30
+ - Add instance variable
31
+ - Add validation Method
32
+
33
+ In short, Composer will behave similarly to `attr_accessor`
34
+
35
+
36
+ ```ruby
37
+ require 'class_composer'
38
+
39
+ class MyConfigurableClass
40
+ include ClassComposer::Generator
41
+
42
+ ALLOWED_FIBONACCI = [0, 2, 8, 13, 34]
43
+
44
+ add_composer :status, allowed: Integer, default: 35
45
+ add_composer :number, allowed: Integer, default: 0, validator: -> (val) { val < 50 }
46
+ # when no default is provided, nil will be returned
47
+ add_composer :fibbonacci, allowed: Array, validator: ->(val) { val.all? {|i| i.is_a?(Integer) } && val.all? { |i| ALLOWED_FIBONACCI.include?(i) } }, invalid_message: ->(val) { "We only allow #{ALLOWED_FIBONACCI} numbers. Received #{val}" }
48
+ # Allowed can be passed an array of allowed class types
49
+ add_composer :type, allowed: [Proc, Integer], default: 35
50
+ end
51
+
52
+ instance = MyConfigurableClass.new
53
+ instance.type
54
+ => 35
55
+ instance.number = 75
56
+ ClassComposer::ValidatorError: MyConfigurableClass.number failed validation. number is expected to be Integer.
57
+ from /gem/lib/class_composer/generator.rb:71:in `block in __composer_assignment__`
58
+ instance.number = 15
59
+ => 15
60
+ instance.number
61
+ => 15
62
+ instance.fibbonacci
63
+ => nil
64
+ instance.fibbonacci = [1,2,3]
65
+ ClassComposer::ValidatorError: MyConfigurableClass.fibbonacci failed validation. fibbonacci is expected to be [Array]. We only allow [0, 2, 8, 13, 34] numbers. Received [1, 2, 3]
66
+ from /gem/lib/class_composer/generator.rb:71:in `block in __composer_assignment__`
67
+ instance.fibbonacci = [0,13,34]
68
+ => [0, 13, 34]
69
+ instance.fibbonacci
70
+ => [0, 13, 34]
71
+ ```
72
+
73
+ ### KWarg Options
74
+
75
+ ```
76
+ allowed
77
+ - Required: True
78
+ - What: Expected value of the name of the composed method
79
+ - Type: Array of Class types or Single Class type
80
+
81
+ validator
82
+ - Required: False
83
+ - What: Custom way to validate the value of the composed method
84
+ - Type: Proc
85
+ - Default: ->(_) { true }
86
+ - By default validation happens on the `allowed` KWARG first and then the passed in validator function. Proc should expect that the type passed in is one of `allowed`
87
+
88
+ validation_error_klass
89
+ - Required: false
90
+ - What: Class to raise when a validation error occurs from `allowed` KWarg or from the passed in `validator` proc
91
+ - Type: Class
92
+ - Default: ClassComposer::ValidatorError
93
+
94
+ validation_error_klass
95
+ - Required: false
96
+ - What: Class to raise when a errors occur outside of validation. This can be for composer method errors or proc errors during validation
97
+ - Type: Class
98
+ - Default: ClassComposer::Error
99
+
100
+ default
101
+ - Required: false
102
+ - What: This is the default value to set for the composed method
103
+ - Type: Should match the `allowed` KWarg
104
+ - Default: nil
105
+ - Note: When no default value is provided, the return value from the getter will be `nil`. However, this does not mean that NilClass will be an acceptable value during the setter method
106
+
107
+ invalid_message
108
+ - Required: False
109
+ - What: Message to add to the base invalid setter method
110
+ - Type: Proc or String
111
+ - Proc: ->(val) { } # where val is the failed value of the setter method
112
+
113
+ ```
114
+
115
+ ### Advanced
116
+
117
+ #### Usage with Array as Allowed
118
+ Arrays are treated special with the composed methods. `ClassComposer` will inject a custom method `<<` so that it can be treated as a regular array with the added benefit of validation still occuring.
119
+
120
+ ```ruby
121
+ class CustomArrayClass
122
+ include ClassComposer::Generator
123
+
124
+ add_composer :array, allowed: Array, default: [], validator: ->(val) { val.sum < 40 }, invalid_message: ->(val) { "Array sum of [#{val.sum}] must be less than 40" }
125
+ end
126
+
127
+ instance = CustomArrayClass.new
128
+ instance.array << 1
129
+ instance.array << 2
130
+ instance.array
131
+ => [1, 2]
132
+ instance.array << 50
133
+ ClassComposer::ValidatorError: CustomArrayClass.array failed validation. array is expected to be Array. Array sum of [53] must be less than 40
134
+
135
+ ```
136
+
137
+ #### Usage with complex configuration
138
+
139
+ ```ruby
140
+ class ComplexDependencies
141
+ include ClassComposer::Generator
142
+
143
+ add_composer :use_scope, allowed: [TrueClass, FalseClass], default: false
144
+ add_composer :scope, allowed: Proc
145
+
146
+ def scope
147
+ # skip unless use_scope is explicitly set
148
+ return -> {} unless @use_scope
149
+
150
+ # use passed in scope if present
151
+ # Otherwise default to blank default
152
+ @scope || -> {}
153
+ end
154
+ end
155
+ ```
156
+ Adding custom methods allows for higher level of complexity. The methods can be used and accessed just as an `attr_accessor` would.
29
157
 
30
158
  ## Development
31
159
 
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
8
8
  spec.authors = ["Matt Taylor"]
9
9
  spec.email = ["mattius.taylor@gmail.com"]
10
10
 
11
- spec.summary = "Describe the gem here"
12
- spec.description = "Describe the gem here"
11
+ spec.summary = "Easily compose a class via inline code or passed in YAML config. Add instance attributes with custom validations that will DRY up your code"
12
+ spec.description = "Compose configurations for any class."
13
13
  spec.homepage = "https://github.com/matt-taylor/class_composer"
14
14
  spec.license = "MIT"
15
15
 
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "class_composer/version"
4
+
5
+ # This is to add a falsey like default behavior
6
+ # When default value is not passed in let this be an allowed value
7
+ # This is intended to eventually be configurable
8
+
9
+ module ClassComposer
10
+ module DefaultObject
11
+ module_function
12
+
13
+ def value
14
+ nil
15
+ end
16
+ end
17
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "class_composer/version"
3
+ require "class_composer/default_object"
4
4
 
5
5
  module ClassComposer
6
6
  module Generator
@@ -13,22 +13,35 @@ module ClassComposer
13
13
  COMPOSER_ASSIGNED_ATTR_NAME = ->(name) { :"@__composer_#{name}_value_assigned__" }
14
14
  COMPOSER_ASSIGNED_ARRAY_METHODS = ->(name) { :"@__composer_#{name}_array_methods_set__" }
15
15
 
16
- def add_composer(name, allowed:, default: nil, accessor: true, validator: ->(_) { true }, **params)
17
- validate_proc = __composer_validator_proc__(validator: validator, allowed: allowed, name: name)
18
- __composer_validate_options__!(name: name, validate_proc: validate_proc, default: default)
16
+ def add_composer(name, allowed:, accessor: true, validator: ->(_) { true }, validation_error_klass: ::ClassComposer::ValidatorError, error_klass: ::ClassComposer::Error, **params)
17
+ default =
18
+ if params.has_key?(:default)
19
+ params[:default]
20
+ else
21
+ if allowed.is_a?(Array)
22
+ allowed << ClassComposer::DefaultObject
23
+ else
24
+ allowed = [allowed, ClassComposer::DefaultObject]
25
+ end
26
+ ClassComposer::DefaultObject
27
+ end
28
+
29
+ allowed.include?(ClassComposer::DefaultObject)
30
+ validate_proc = __composer_validator_proc__(validator: validator, allowed: allowed, name: name, error_klass: error_klass)
31
+ __composer_validate_options__!(name: name, validate_proc: validate_proc, default: default, validation_error_klass: validation_error_klass, error_klass: error_klass)
19
32
 
20
33
  array_proc = __composer_array_proc__(name: name, validator: validator, allowed: allowed, params: params)
21
- __composer_assignment__(name: name, params: params, validator: validate_proc, array_proc: array_proc)
34
+ __composer_assignment__(name: name, allowed: allowed, params: params, validator: validate_proc, array_proc: array_proc, validation_error_klass: validation_error_klass, error_klass: error_klass)
22
35
  __composer_retrieval__(name: name, default: default, array_proc: array_proc)
23
36
  end
24
37
 
25
- def __composer_validate_options__!(name:, validate_proc:, default:)
38
+ def __composer_validate_options__!(name:, validate_proc:, default:, params: {}, validation_error_klass:, error_klass:)
26
39
  unless validate_proc.(default)
27
- raise ClassComposer::ValidatorError, "Default value [#{default}] for #{self.class}.#{name} is not valid"
40
+ raise validation_error_klass, "Default value [#{default}] for #{self.class}.#{name} is not valid"
28
41
  end
29
42
 
30
- if self.class.instance_methods.include?(name.to_sym)
31
- raise ClassComposer::Error, "#{name} is already defined. Ensure composer names are all uniq and do not class with class instance methods"
43
+ if instance_methods.include?(name.to_sym)
44
+ raise error_klass, "[#{name}] is already defined. Ensure composer names are all uniq and do not class with class instance methods"
32
45
  end
33
46
  end
34
47
 
@@ -39,7 +52,7 @@ module ClassComposer
39
52
  end
40
53
 
41
54
  # create assignment method for the incoming name
42
- def __composer_assignment__(name:, params:, validator:, array_proc:)
55
+ def __composer_assignment__(name:, params:, allowed:, validator:, array_proc:, validation_error_klass:, error_klass:)
43
56
  define_method(:"#{name}=") do |value|
44
57
  is_valid = validator.(value)
45
58
 
@@ -47,11 +60,15 @@ module ClassComposer
47
60
  instance_variable_set(COMPOSER_ASSIGNED_ATTR_NAME.(name), true)
48
61
  instance_variable_set(:"@#{name}", value)
49
62
  else
50
- value.pop
51
- message = ["#{self.class}.#{name} failed validation."]
63
+ message = ["#{self.class}.#{name} failed validation. #{name} is expected to be #{allowed}."]
52
64
 
53
65
  message << (params[:invalid_message].is_a?(Proc) ? params[:invalid_message].(value) : params[:invalid_message].to_s)
54
- raise ClassComposer::ValidatorError, message.compact.join(" ")
66
+ if value.is_a?(Array)
67
+ # we assigned the array value...pop it from the array
68
+ # must be done after the message is created so that failing value can get passed appropriately
69
+ value.pop
70
+ end
71
+ raise validation_error_klass, message.compact.join(" ")
55
72
  end
56
73
 
57
74
  if value.is_a?(Array) && !value.instance_variable_get(COMPOSER_ASSIGNED_ARRAY_METHODS.(name))
@@ -79,14 +96,15 @@ module ClassComposer
79
96
  end
80
97
  default.instance_variable_set(COMPOSER_ASSIGNED_ARRAY_METHODS.(name), true)
81
98
  end
82
- default
99
+
100
+ default == ClassComposer::DefaultObject ? ClassComposer::DefaultObject.value : default
83
101
  end
84
102
  end
85
103
 
86
104
  # create validator method for incoming name
87
- def __composer_validator_proc__(validator:, allowed:, name:)
105
+ def __composer_validator_proc__(validator:, allowed:, name:, error_klass:)
88
106
  if validator && !validator.is_a?(Proc)
89
- raise ClassComposer::Error, "Expected validator to be a Proc. Received [#{validator.class}]"
107
+ raise error_klass, "Expected validator to be a Proc. Received [#{validator.class}]"
90
108
  end
91
109
 
92
110
  # Proc will validate the entire attribute -- Full assignment must occur before validate is called
@@ -98,9 +116,11 @@ module ClassComposer
98
116
  else
99
117
  allowed == value.class
100
118
  end
101
- allow && validator.(value)
119
+ # order is important -- Do not run validator if it is the default object
120
+ # Default object will likely raise an error if there is a custom validator
121
+ (allowed.include?(ClassComposer::DefaultObject) && value == ClassComposer::DefaultObject) || (allow && validator.(value))
102
122
  rescue StandardError => e
103
- raise ClassComposer::Error, "#{e} occured during validation for value [#{value}]. Check custom validator for #{name}"
123
+ raise error_klass, "#{e} occured during validation for value [#{value}]. Check custom validator for #{name}"
104
124
  end
105
125
  end
106
126
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ClassComposer
4
- VERSION = "0.0.1"
4
+ VERSION = "1.0.2"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: class_composer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Taylor
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-06-24 00:00:00.000000000 Z
11
+ date: 2022-07-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pry-byebug
@@ -66,7 +66,7 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: 0.17.0
69
- description: Describe the gem here
69
+ description: Compose configurations for any class.
70
70
  email:
71
71
  - mattius.taylor@gmail.com
72
72
  executables: []
@@ -90,7 +90,7 @@ files:
90
90
  - class_composer.gemspec
91
91
  - docker-compose.yml
92
92
  - lib/class_composer.rb
93
- - lib/class_composer/array.rb
93
+ - lib/class_composer/default_object.rb
94
94
  - lib/class_composer/generator.rb
95
95
  - lib/class_composer/version.rb
96
96
  homepage: https://github.com/matt-taylor/class_composer
@@ -117,5 +117,6 @@ requirements: []
117
117
  rubygems_version: 3.3.11
118
118
  signing_key:
119
119
  specification_version: 4
120
- summary: Describe the gem here
120
+ summary: Easily compose a class via inline code or passed in YAML config. Add instance
121
+ attributes with custom validations that will DRY up your code
121
122
  test_files: []
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ClassComposer
4
- class Array
5
- def initialize(validator)
6
- @validator = validator
7
- super
8
- end
9
-
10
- def <<
11
- value = super
12
-
13
-
14
-
15
- value
16
- end
17
- end
18
- end