class_composer 1.0.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7d53fe12431ff8f1dd4c42d7282d11a64637a0c3ab573e24a7d83443a8a9cac2
4
- data.tar.gz: 0376e66b2fef6bc976289dd1f4e4517830dd0a63c0e12519730efc9786865c9f
3
+ metadata.gz: dd01814260b83eaa363cdbdff3d1f078ee85b7d6850841a847e7d89edcef6e29
4
+ data.tar.gz: 43d27db959b1dfdbd364cc1be9af76d0debe4786031822d290d7dac6292afefd
5
5
  SHA512:
6
- metadata.gz: b08e706f2f55865e6d4fab779896450dab524b946412ee8e8be8a673b205a8f3ded0a438db6dbce53d8ebc06c6a88a2dc6cba5a4da3bd5e22a599a205ea9cc0f
7
- data.tar.gz: bfa9491b16358fc59b221bd5ac847b861c52a6a5793dca4fc59c81a8ceef3e12c1d3148bd8679833e4c5a643e5738788a9647a1619f9aadc2171b9d497bd0f34
6
+ metadata.gz: 2802e0cae4ad7be7d5f866436250e24e89e5ef032d1196d0951b517e18de28b7db6473f757277f239519c167de6de3f15f10a19853fdef9818ae2e400e13e3af
7
+ data.tar.gz: 175613d8ea5f331652eabad44e6090fb16f50703dbcb099dfb72bf96a5a2cd564eb8b3c491ed411124e908adada075ec6603b6b85a41f64e9661195b665e9b00
data/.circleci/config.yml CHANGED
@@ -10,10 +10,10 @@ workflows:
10
10
  - cst/enforce-gem-version-bump
11
11
  - cst/rspec-ruby:
12
12
  rspec-system-args: "SIMPLE_COV_RUN=true"
13
- cc-report-collect-ruby: "2.7.5"
13
+ cc-report-collect-ruby: "3.3.5"
14
14
  matrix:
15
15
  parameters:
16
- ruby-version: ["2.7.5" , "3.0.0", "3.0.3"]
16
+ ruby-version: ["3.1.5" , "3.2.4", "3.3.5"]
17
17
  alias: required-matrix-tests
18
18
  name: test-ruby<< matrix.ruby-version >>
19
19
  - cst/publish-gem:
data/.gitignore CHANGED
@@ -7,6 +7,7 @@
7
7
  /spec/reports/
8
8
  /tmp/
9
9
  .DS_Store
10
+ spec/examples.txt
10
11
 
11
12
  # rspec failure tracking
12
13
  .rspec_status
data/CHANGELOG.md CHANGED
@@ -4,12 +4,26 @@
4
4
 
5
5
  **Major:** x - Major Releases with new features that constitute a breaking change
6
6
 
7
- **Minor:** x.x - Minor changes or features added which are backwords compatible
7
+ **Minor:** x.x - Minor changes or features added which are backwards compatible
8
8
 
9
9
  **Patch:** x.x.x - Patch Updates
10
10
 
11
11
  # Release Notes
12
12
 
13
- | Date | Version | Description |
14
- |---|---|---|
15
- | June 2022 | v1.0.0 | Initial launch of Composer
13
+ ## v2.0.0 (Nov 2024)
14
+ - Minimum Ruby Version bumped to v3.1
15
+ - Initializer File Generator
16
+ - Add(`desc:`) key to your configs and ClassComposer can create a Initializer for you with all options
17
+ - Automatically will track new options added
18
+ - Options added:
19
+ - `desc:` -- Describe what the configuration is doing. Recommended but optional
20
+ - `default_shown:` With Config Initializer file Generation, you can add a custom value to display as the default (For example: Use this options with ENV variables or sensitive arguments)
21
+ - `&block`: Provide a block to the composer class method. This block gets executed on assignment after validation.
22
+ - Methods Added:
23
+ - `add_composer_blocking`(Class): Recommended composer method to use when default is another ClassComposer included class (See Readme for more details)
24
+ - `class_composer_assign_defaults!` (Instance): Class Composer values are lazily loaded. This option allows you to load configured options by calling a method (See Readme for more details)
25
+ - `class_composer_freeze_objects!`(Instance): Call this instance method if you want to make the instance immutable to changes. Helpful to ensure all changes are made before the application code runs (See Readme for more details)
26
+ - `composer_generate_config`(Instance): Create the Configuration text for an initializer file. (See Readme for more details)
27
+
28
+ ## v1.0.0 June 2022
29
+ - Initial Launch!
data/Dockerfile CHANGED
@@ -1,4 +1,4 @@
1
- FROM ruby:3.0.1
1
+ FROM ruby:3.3.6
2
2
  RUN cd /tmp && curl -L --output ghr.tar.gz https://github.com/tcnksm/ghr/releases/download/v0.12.0/ghr_v0.12.0_linux_amd64.tar.gz && \
3
3
  tar -xzvf ghr.tar.gz && chmod +x ghr_v0.12.0_linux_amd64/ghr && mv ghr_v0.12.0_linux_amd64/ghr /usr/local/bin/ghr && rm -rf /tmp/*
4
4
 
data/Gemfile CHANGED
@@ -5,8 +5,8 @@ source "https://rubygems.org"
5
5
  # Specify your gem's dependencies in GEMNAME.gemspec
6
6
  gemspec
7
7
 
8
- gem 'faker'
9
- gem 'pry'
10
- gem 'rspec', '~> 3.0'
11
- gem 'rspec_junit_formatter'
12
- gem 'simplecov', require: false, group: :test
8
+ gem "faker"
9
+ gem "pry"
10
+ gem "rspec"
11
+ gem "rspec_junit_formatter"
12
+ gem "simplecov"
data/Gemfile.lock CHANGED
@@ -1,60 +1,54 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- class_composer (0.1.0)
4
+ class_composer (2.0.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
- byebug (11.1.3)
10
9
  coderay (1.1.3)
11
- concurrent-ruby (1.1.10)
12
- diff-lcs (1.5.0)
13
- docile (1.4.0)
14
- faker (2.21.0)
10
+ concurrent-ruby (1.3.4)
11
+ diff-lcs (1.5.1)
12
+ docile (1.4.1)
13
+ faker (3.5.1)
15
14
  i18n (>= 1.8.11, < 2)
16
- i18n (1.10.0)
15
+ i18n (1.14.6)
17
16
  concurrent-ruby (~> 1.0)
18
- method_source (1.0.0)
19
- pry (0.13.1)
17
+ method_source (1.1.0)
18
+ pry (0.15.0)
20
19
  coderay (~> 1.1)
21
20
  method_source (~> 1.0)
22
- pry-byebug (3.9.0)
23
- byebug (~> 11.0)
24
- pry (~> 0.13.0)
25
- rake (12.3.3)
26
- rspec (3.11.0)
27
- rspec-core (~> 3.11.0)
28
- rspec-expectations (~> 3.11.0)
29
- rspec-mocks (~> 3.11.0)
30
- rspec-core (3.11.0)
31
- rspec-support (~> 3.11.0)
32
- rspec-expectations (3.11.0)
21
+ rspec (3.13.0)
22
+ rspec-core (~> 3.13.0)
23
+ rspec-expectations (~> 3.13.0)
24
+ rspec-mocks (~> 3.13.0)
25
+ rspec-core (3.13.2)
26
+ rspec-support (~> 3.13.0)
27
+ rspec-expectations (3.13.3)
33
28
  diff-lcs (>= 1.2.0, < 2.0)
34
- rspec-support (~> 3.11.0)
35
- rspec-mocks (3.11.1)
29
+ rspec-support (~> 3.13.0)
30
+ rspec-mocks (3.13.2)
36
31
  diff-lcs (>= 1.2.0, < 2.0)
37
- rspec-support (~> 3.11.0)
38
- rspec-support (3.11.0)
39
- rspec_junit_formatter (0.5.1)
32
+ rspec-support (~> 3.13.0)
33
+ rspec-support (3.13.1)
34
+ rspec_junit_formatter (0.6.0)
40
35
  rspec-core (>= 2, < 4, != 2.12.0)
41
- simplecov (0.21.2)
36
+ simplecov (0.22.0)
42
37
  docile (~> 1.1)
43
38
  simplecov-html (~> 0.11)
44
39
  simplecov_json_formatter (~> 0.1)
45
- simplecov-html (0.12.3)
40
+ simplecov-html (0.13.1)
46
41
  simplecov_json_formatter (0.1.4)
47
42
 
48
43
  PLATFORMS
44
+ aarch64-linux
49
45
  x86_64-linux
50
46
 
51
47
  DEPENDENCIES
52
48
  class_composer!
53
49
  faker
54
50
  pry
55
- pry-byebug
56
- rake (~> 12.0)
57
- rspec (~> 3.0)
51
+ rspec
58
52
  rspec_junit_formatter
59
53
  simplecov
60
54
 
data/README.md CHANGED
@@ -22,7 +22,7 @@ Or install it yourself as:
22
22
 
23
23
  ## Usage
24
24
 
25
- ### Basic
25
+ ### Basic Add Composer
26
26
 
27
27
  `add_composer` is the driving method behind the composer gem. It will
28
28
  - Add a setter Method
@@ -32,144 +32,32 @@ Or install it yourself as:
32
32
 
33
33
  In short, Composer will behave similarly to `attr_accessor`
34
34
 
35
+ Check out [Basic Composer](docs/basic_composer.md) for usage details
35
36
 
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
- ```
37
+ ### Usage with Arrays
72
38
 
73
- ### KWarg Options
39
+ Composer allows interactions with arrays in a native way. When the allowed type is `Array`, ClassComposer will add a custom method `<<` to overwrite the native Array method. This ensures that ClassComposer Validations run when adding to the Array.
74
40
 
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
41
+ For more information, check out [Array Usage](docs/array_usage.md)
112
42
 
113
- ```
43
+ ### Freezing Objects
114
44
 
115
- ### Advanced
45
+ Sometimes you want freeze an instance of a Configuration. Freezing it will make it immutable from changes Users may try to make.
116
46
 
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.
47
+ For more information, check out [Freezing Class Composer Instances](docs/freezing.md)
119
48
 
120
- ```ruby
121
- class CustomArrayClass
122
- include ClassComposer::Generator
49
+ ### Complex usage: Composer Blocking
123
50
 
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
51
+ Composer blocking builds on top of the [Basic Composer](docs/basic_composer.md) to help with nested Configurations that include `ClassCompser::Generator`.
52
+ Nested configuration's allow complex configurations for entire projects to work seamlessly together.
126
53
 
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.
54
+ For Examples and use cases, check out [Composer Blocking](docs/composer_blocking.md)
157
55
 
158
56
  ## Development
159
57
 
160
58
  After checking out the repo, run `bin/setup` to install dependencies. Then, run
161
- `rake rspec` to run the tests. You can also run `bin/console` for an interactive
162
- prompt that will allow you to experiment. Run `bundle exec class_composer` to use
163
- the gem in this directory, ignoring other installed copies of this gem.
164
-
165
- To install this gem onto your local machine, run `bundle exec rake install`.
166
-
167
- To release a new version:
168
-
169
- 1. Update the version number in [lib/class_composer/version.rb]
170
- 2. Update [CHANGELOG.md]
171
- 3. Merge to the main branch. This will trigger an automatic build in CircleCI
172
- and push the new gem to the repo.
59
+ `bundle exec rspec` to run the tests. You can also run `bin/console` for an interactive
60
+ prompt that will allow you to experiment.
173
61
 
174
62
  ## Contributing
175
63
 
data/bin/setup CHANGED
@@ -5,6 +5,4 @@ set -vx
5
5
 
6
6
  bundle install
7
7
 
8
- lefthook install
9
-
10
8
  # Do any other automated setup that you need to do here
@@ -13,7 +13,7 @@ Gem::Specification.new do |spec|
13
13
  spec.homepage = "https://github.com/matt-taylor/class_composer"
14
14
  spec.license = "MIT"
15
15
 
16
- spec.required_ruby_version = Gem::Requirement.new(">= 2.7")
16
+ spec.required_ruby_version = Gem::Requirement.new(">= 3.1")
17
17
 
18
18
  spec.metadata = {
19
19
  "homepage_uri" => spec.homepage,
@@ -25,12 +25,5 @@ Gem::Specification.new do |spec|
25
25
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
26
26
  %x(git ls-files -z).split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
27
27
  end
28
- spec.bindir = "exe"
29
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
28
  spec.require_paths = ["lib"]
31
-
32
- spec.add_development_dependency "pry-byebug"
33
- spec.add_development_dependency "rake", "~> 12.0"
34
- spec.add_development_dependency "rspec", "~> 3.0"
35
- spec.add_development_dependency "simplecov", "~> 0.17.0"
36
29
  end
data/docker-compose.yml CHANGED
@@ -1,5 +1,3 @@
1
- version: '3'
2
-
3
1
  services:
4
2
  class-composer:
5
3
  command: tail -f /dev/null
@@ -0,0 +1,25 @@
1
+ # Usage with Array
2
+
3
+ For more details on basic setup, visit [Basic Composer Page](basic_composer.md)
4
+
5
+ ---
6
+
7
+ 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 occurring.
8
+
9
+
10
+ ```ruby
11
+ class CustomArrayClass
12
+ include ClassComposer::Generator
13
+
14
+ add_composer :array, allowed: Array, default: [], validator: ->(val) { val.sum < 40 }, invalid_message: ->(val) { "Array sum of [#{val.sum}] must be less than 40" }
15
+ end
16
+
17
+ instance = CustomArrayClass.new
18
+ instance.array << 1
19
+ instance.array << 2
20
+ instance.array
21
+ => [1, 2]
22
+ instance.array << 50
23
+ ClassComposer::ValidatorError: CustomArrayClass.array failed validation. array is expected to be Array. Array sum of [53] must be less than 40
24
+
25
+ ```
@@ -0,0 +1,118 @@
1
+ # Add Composer
2
+
3
+ `ClassComposer` by default is a DRY way to compose configurations for a class. It provides re-usable validations to ensure variables are set correctly. It is lazily loaded by default and has some wicked options available
4
+
5
+ ```ruby
6
+ require "class_composer"
7
+ class MyCoolClass
8
+ include ClassComposer::Generator
9
+
10
+ add_composer :status, allowed: Symbol, default: :in_progress
11
+ end
12
+ ```
13
+
14
+
15
+ ## Available Options:
16
+
17
+ ### Allowed
18
+ - Required: Yes
19
+ - Description: This defines allowed types for this composed item. If `===` fails, it will return a runtime error
20
+ - Type: Type of allowed assignments. Or an Array of allowed assignments --
21
+
22
+ Examples:
23
+
24
+ ```ruby
25
+ add_composer :status, allowed: Symbol, default: :in_progress
26
+
27
+ add_composer :status, allowed: [Symbol, String], default: :in_progress
28
+ ```
29
+
30
+ ### Default
31
+ - Required: False
32
+ - Description: This option allows you to set a sane default for configuration while still allowing others to overwrite the value
33
+ - Type: It _should_ be one of the `allowed` types to ensure it passes validation
34
+
35
+ Examples
36
+ ```ruby
37
+ add_composer :status, allowed: Symbol, default: :different_default
38
+
39
+ add_composer :status, allowed: Symbol, default: "This type does not match Symbol. This default value will raise error"
40
+ ```
41
+
42
+ ### Description
43
+ - Required: False (Recommend)
44
+ - Description: This option provides a human readable description of what the current configuration option does. This value is useful when [Generating an Initializer](generating_initializer.md)
45
+ - Type: String
46
+
47
+ ```ruby
48
+ add_composer :status, allowed: Symbol, default: :in_progress, desc: "This config value is the current status for the entity."
49
+ ```
50
+
51
+
52
+ ### Default Shown
53
+ - Required: False (Recommend)
54
+ - Description: This option is helpful when the `default` value is a class or retrieved from an ENV variable. It will overload the `default` value when [Generating an Initializer](generating_initializer.md)
55
+ - Type: String
56
+
57
+ ```ruby
58
+ add_composer :status, allowed: Symbol, default: :in_progress, desc: "This config value is the current status for the entity.", default_shown: "completed"
59
+ ```
60
+
61
+ ### Provide a Block
62
+ - Required: False
63
+ - Description: The optional block will get executed after all validations have completed successful. This is a valuable option when you need to do a secondary action after the value is assigned/changed.
64
+ - Type: Proc that accepts (`key`, `value`)
65
+
66
+ ```ruby
67
+ BLOCK = Proc.new do |key, value|
68
+ User.send_mail("Your Status has changed to #{key}")
69
+ end
70
+
71
+ add_composer :status, allowed: Symbol, default: :in_progress, desc: "This config value is the current status for the entity.", default_shown: "completed", &BLOCK
72
+ ```
73
+
74
+ ### Validator
75
+ - Required: False
76
+ - Description: This option provides additional validation to do on the item during assignment.
77
+ - Type: Proc that returns a truthy or falsey value. Truthy response will pass validation. Falsey response will fail validation and raise a runtime error
78
+ - Default: `->(_) { true }`
79
+
80
+ Examples:
81
+ ```ruby
82
+ add_composer :status, allowed: Symbol, default: :in_progress, validator: ->(value) { [:backlog,:in_progress, :complete].include?(value) }
83
+ ```
84
+
85
+ ### Invalid Message
86
+ - Required: False (Recommended with `validator`)
87
+ - Description: When provided, you can add a custom validation message to the runtime error. Recommended when `validator` is provided
88
+ - Type: Proc that returns a string to add to validation message
89
+
90
+ Examples:
91
+ ```ruby
92
+ ALLOWED = [:backlog,:in_progress, :complete]
93
+ add_composer :status, allowed: Symbol, default: :in_progress, validator: ->(value) { ALLOWED.include?(value) }, invalid_message: ->(value) { "Value must be one of #{ALLOWED}" }
94
+ ```
95
+
96
+ ### Validation Error Class
97
+ - Required False
98
+ - Description: The default error to raise when validation fails.
99
+ - Default: `ClassComposer::ValidatorError`
100
+ - Type: Class that has `StandardError` Ancestor
101
+
102
+ ### Error Class
103
+ - Required: False
104
+ - Description: The default error class to raise when errors outside of ClassComposer occur. EG during custom validation
105
+ - Default: `ClassComposer::Error`
106
+ - Type: Class that has `StandardError` ancestor
107
+
108
+ Examples:
109
+ ```ruby
110
+ ALLOWED = [:backlog,:in_progress, :complete]
111
+ add_composer :status, allowed: Symbol, default: :in_progress, validator: ->(value) { UNDEFINED_VARIABLE.include?(value) } validation_error_klass: Exception
112
+ # Will raise with `Exception`
113
+ ```
114
+
115
+ ---
116
+
117
+ To see some complete Examples, visit [Basic Composer Example](basic_composer_example,md)
118
+
@@ -0,0 +1,35 @@
1
+ ```ruby
2
+ require 'class_composer'
3
+
4
+ class MyConfigurableClass
5
+ include ClassComposer::Generator
6
+
7
+ ALLOWED_FIBONACCI = [0, 2, 8, 13, 34]
8
+
9
+ add_composer :status, allowed: Integer, default: 35
10
+ add_composer :number, allowed: Integer, default: 0, validator: -> (val) { val < 50 }
11
+ # when no default is provided, nil will be returned
12
+ 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}" }
13
+ # Allowed can be passed an array of allowed class types
14
+ add_composer :type, allowed: [Proc, Integer], default: 35
15
+ end
16
+
17
+ instance = MyConfigurableClass.new
18
+ instance.type
19
+ => 35
20
+ instance.number = 75
21
+ ClassComposer::ValidatorError: MyConfigurableClass.number failed validation. number is expected to be Integer.
22
+ from /gem/lib/class_composer/generator.rb:71:in `block in __composer_assignment__`
23
+ instance.number = 15
24
+ => 15
25
+ instance.number
26
+ => 15
27
+ instance.fibbonacci
28
+ => nil
29
+ instance.fibbonacci = [1,2,3]
30
+ 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]
31
+ from /gem/lib/class_composer/generator.rb:71:in `block in __composer_assignment__`
32
+ instance.fibbonacci = [0,13,34]
33
+ => [0, 13, 34]
34
+ instance.fibbonacci
35
+ => [0, 13, 34]
@@ -0,0 +1,84 @@
1
+ # Composer Blocking
2
+
3
+ Composer Blocking enhances nested configuration's that utilize `ClassComposer::Generator`. It is a method that provides additional validations and usability.
4
+
5
+ Basic Example:
6
+ ```ruby
7
+ require "class_composer"
8
+
9
+ class LoginStrategy
10
+ include ClassComposer::Generator
11
+
12
+ add_composer :password_regex, allowed: Regexp, default: /\A\w{6,20}\z/, desc: "Password must include valid characters between 6 and 20 in length"
13
+
14
+ add_composer :username_length, allowed: Integer, default: 10
15
+
16
+ add_composer :type, allowed: String, default: "plain_text"
17
+ end
18
+
19
+ class LockableStrategy
20
+ include ClassComposer::Generator
21
+
22
+ add_composer :enable, default: false, allowed: [TrueClass, FalseClass], desc: "By default Lockable Strategy is disabled."
23
+ add_composer :password_attempts, default: 10, allowed: Integer, desc: "Max password attempts before the account is locked"
24
+ end
25
+
26
+ class AppConfiguration
27
+ include ClassComposer::Generator
28
+
29
+ add_composer_blocking :login, composer_class: LoginStrategy, desc: "Login Strategy for my Application"
30
+
31
+ add_composer_blocking :lockable, composer_class: LockableStrategy, enable_attr: :enable, desc: "Lock Strategy for my Application. By default this is disabled"
32
+ end
33
+
34
+ instance = AppConfiguration.new
35
+ instance.with_login! do |login|
36
+ login.type = "oauth"
37
+ login.username_length = 20
38
+ end
39
+ instance.login.type
40
+ => "oauth"
41
+ instance.login.type = "plain_text"
42
+ => "plain_text"
43
+
44
+ instance.lockable?
45
+ => false
46
+ # Calling the block automatically enables the config when passed the `enable_attr`
47
+ instance.with_lockable! do |lock|
48
+ lock.password_attempts = 5
49
+ end
50
+ instance.lockable?
51
+ => true
52
+ ```
53
+
54
+ ## What does it produce?
55
+ ### Configuration Blocking Method
56
+ Provides easy invocation for setting a nested composer configuration. By providing a block, you can easily and cleanly set your options for a specific composer item.
57
+
58
+ ### Check for composer Enable
59
+ When the `enable_attr` method is provided, you can easily check if the composer instance is enabled by invoking `item_name?`. This convenience method can be used throughout the application to easily check which code paths to go down
60
+
61
+ ## Allowed Options
62
+
63
+ ### Composer Class
64
+ - Required: True
65
+ - Description: This is the Class object that includes `ClassComposer::Generator`. If the inclusion is missing, the method will raise an runtime error.
66
+ - Type: Class that includes `ClassComposer::Generator`
67
+
68
+ ### Description
69
+ - Required: False (Recommend)
70
+ - Description: This option provides a human readable description of what the current configuration option does. This value is useful when [Generating an Initializer](generating_initializer.md)
71
+ - Type: String
72
+
73
+ ### Block Prepend
74
+ - Required: False
75
+ - Description: By default, Composer Blocking prepends blocks with `with_`. This options allows you to set a custom prepended name
76
+ - Default: `with`
77
+ - Type: String or Symbol
78
+
79
+ ### Enable Attr
80
+ - Required: False
81
+ - Description: When passed the `enable_attr:`, Class composer will enable the composer instance when called with a block and provide a convenience method `item_name?` to check for if the item is enabled.
82
+ - Type: String or Symbol
83
+
84
+
data/docs/freezing.md ADDED
@@ -0,0 +1,58 @@
1
+ # Freezing ClassComposer
2
+
3
+ `ClassComposer` provides a simple way to freeze instances of its classes. Freezing can help ensure that configurations do not change during the life of the script or application.
4
+
5
+ Different behaviors are available when a user attempts to change a composed item after the instance has been frozen
6
+
7
+ ## Allowed Options:
8
+ ### Behavior:
9
+ - Required: When `&block` is nil, behavior is required
10
+ - Description: The behavior ClassComposer should enact when a composed item tries to get changed
11
+ - Type: Symbol [:raise, :log_and_allow, :log_and_skip]
12
+
13
+ ### Children:
14
+ - Required: false
15
+ - Description: Any ClassComposed item that includes `ClassComposer::Generator` is considered a nested Child. When option set to true, We will iterate the tree and set all child instances to the same behavior as the parent. One stop shop to freeze all nested configuration
16
+ - Type: Boolean
17
+
18
+ ### Block
19
+ - Required: When `behavior` is nil, block is required
20
+ - Description: Custom behavior tailored to your use case. For example, In test, maybe you raise, but production maybe you allow
21
+ - Type: Passed in block, Return `true` to allow the variable to get set. Return `false` to not allow the variable to get set
22
+
23
+
24
+ ```ruby
25
+ MyCoolEngine.config.class_composer_freeze_objects!(children: true) do |instance, key|
26
+ if Rails.staging?
27
+ # allow the variable to get set in staging
28
+ Rails.logger("Yikes! you are changing a config variable after boot. We will honor this")
29
+ true
30
+ elsif Rails.prod?
31
+ # disallow the variable to get set in prod
32
+ Rails.logger("Yikes! you are changing a config variable after boot. We will NOT honor this")
33
+ false
34
+ else
35
+ raise Error, "Cant change value on #{instance.class} for key. Please change"
36
+ end
37
+ end
38
+ ```
39
+
40
+ ## Usage
41
+
42
+ ### Rails Engine
43
+ When building out a complex nested configuration structure for a Rails Engine, you may want to ensure changes to the configuration do not occur after the Rails App runs its initializers. As example code, this can get added to your `*engine.rb` file
44
+
45
+ ```ruby
46
+ # MyCoolEngine.config is the location of the config instance
47
+ # Assign Defaults must get run first otherwise Lazily loaded objects will run into failure
48
+
49
+ # Run after Rails loads the initializes and environment files
50
+ # Ensures User has already set their desired config before we lock this down
51
+ initializer "my_cool_engine.config.instantiate", after: :load_config_initializers do |_app|
52
+ # ensure defaults are instantiated and all variables are assigned
53
+ MyCoolEngine.config.class_composer_assign_defaults!(children: true)
54
+
55
+ # Now that we can confirm all variables are defined, freeze all objects an their children
56
+ MyCoolEngine.config.class_composer_freeze_objects!(behavior: :raise, children: true)
57
+ end
58
+ ```
@@ -0,0 +1,74 @@
1
+ # Generating Initializer
2
+
3
+ Generating an initializer can help ensure that all users understand all potential configuration options without searching the codebase.
4
+
5
+ This generation will add both [Basic Composer Options](basic_composer.md) and [Composer Blocking Options](composer_blocking.md) to a configuration file.
6
+
7
+ The file output will show show assignment to all default values. Additionally all lines are commented out so the User can
8
+
9
+
10
+ ```ruby
11
+ class LoginStrategy
12
+ include ClassComposer::Generator
13
+
14
+ add_composer :password_regex, allowed: Regexp, default: /\A\w{6,20}\z/, desc: "Password must include valid characters between 6 and 20 in length"
15
+
16
+ add_composer :username_length, allowed: Integer, default: 10
17
+
18
+ add_composer :type, allowed: String, default: "plain_text"
19
+ end
20
+
21
+ class LockableStrategy
22
+ include ClassComposer::Generator
23
+
24
+ add_composer :enable, default: false, allowed: [TrueClass, FalseClass], desc: "By default Lockable Strategy is disabled."
25
+ add_composer :password_attempts, default: 10, allowed: Integer, desc: "Max password attempts before the account is locked"
26
+ end
27
+
28
+ class AppConfiguration
29
+ include ClassComposer::Generator
30
+
31
+ add_composer_blocking :login, composer_class: LoginStrategy, desc: "Login Strategy for my Application"
32
+
33
+ add_composer_blocking :lockable, composer_class: LockableStrategy, enable_attr: :enable, desc: "Lock Strategy for my Application. By default this is disabled"
34
+ end
35
+
36
+ puts AppConfiguration.composer_generate_config(wrapping: "MyApplication.configure")
37
+
38
+ ----
39
+
40
+ =begin
41
+ This configuration files lists all the configuration options available.
42
+ To change the default value, uncomment the line and change the value.
43
+ Please take note: Values set as `=` to a config variable are the current default values when none is assigned
44
+ =end
45
+
46
+ MyApplication.configure do |config|
47
+ # ### Block to configure Login ###
48
+ # Login Strategy for my Application
49
+ # config.with_login do |login_config|
50
+ # Password must include valid characters between 6 and 20 in length: [Regexp]
51
+ # login_config.password_regex = (?-mix:\A\w{6,20}\z)
52
+
53
+ # login_config.username_length = 10
54
+
55
+ # login_config.type = "plain_text"
56
+ # end
57
+
58
+ # ### Block to configure Lockable ###
59
+ # Lock Strategy for my Application. By default this is disabled
60
+ # When using the block, the enable flag will automatically get set to true
61
+ # config.with_lockable do |lockable_config|
62
+ # By default Lockable Strategy is disabled.: [TrueClass, FalseClass]
63
+ # lockable_config.enable = false
64
+
65
+ # Max password attempts before the account is locked: [Integer]
66
+ # lockable_config.password_attempts = 10
67
+ # end
68
+ end
69
+ ```
70
+
71
+ ## Usage Applications
72
+ ### Rails Generator
73
+ Are you building an Engine or a Gem that requires custom configuration. This code can easily help downstream users understand exactly what options are available to them to configure your Engine/Gem.
74
+
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClassComposer
4
+ class GenerateConfig
5
+ attr_reader :instance
6
+ NOTICE = <<~HEREDOC
7
+ =begin
8
+ This configuration files lists all the configuration options available.
9
+ To change the default value, uncomment the line and change the value.
10
+ Please take note: Values set as `=` to a config variable are the current default values when none is assigned
11
+ =end
12
+ HEREDOC
13
+
14
+ def initialize(instance:)
15
+ raise ArgumentError, ":instance class (#{instance}) must include ClassComposer::Generator. It does not" unless instance.include?(ClassComposer::Generator)
16
+
17
+ @instance = instance
18
+ end
19
+
20
+ def execute(wrapping:, require_file:, space_count: 1, config_name: "config")
21
+ mapping = instance.composer_mapping
22
+ generated_config = generate(mapping:, space_count:, demeters_deep:[config_name])
23
+
24
+ stringified = ""
25
+ stringified += "require \"#{require_file}\"\n\n" if require_file
26
+ stringified += NOTICE
27
+ stringified += "\n"
28
+ stringified += "#{wrapping} do |#{config_name}|\n"
29
+ flattened_config = generated_config.flatten(1).map { _1.join(" ") }
30
+ flattened_config.pop if flattened_config[-1] == ""
31
+
32
+ stringified += flattened_config.join("\n")
33
+ stringified += "\nend"
34
+ stringified
35
+ end
36
+
37
+ private
38
+
39
+ def generate(mapping:, space_count:, demeters_deep:)
40
+ mapping.map do |key, metadata|
41
+ if blocking_attributes = metadata[:blocking_attributes]
42
+ if children = metadata[:children]
43
+ do_block = "#{key}_config"
44
+ blocking(key:, do_block:, metadata:, space_count:, demeters_deep:, blocking_attributes:) do
45
+ generate(mapping: children.first, space_count: space_count + 2, demeters_deep: [do_block])
46
+ end
47
+ else
48
+ []
49
+ end
50
+ elsif children = metadata[:children]
51
+ config_prepend = demeters_deep + [key]
52
+ children_config = []
53
+ if desc = metadata[:desc]
54
+ children_config << spec_child_description(space_count:, desc:, key:)
55
+ end
56
+
57
+ children.each do |child|
58
+ children_config += generate(mapping: child, space_count:, demeters_deep: config_prepend)
59
+ end
60
+ # binding.pry
61
+
62
+ children_config.flatten(1)
63
+ else
64
+ spec(key:, metadata:, space_count:, demeters_deep:)
65
+ end
66
+ end
67
+ end
68
+
69
+ def spec_child_description(space_count:, desc:, key:)
70
+ base = "#########"
71
+ length = base.length * 2 + 4 + key.capitalize.length
72
+
73
+ [
74
+ [prepending(space_count),"#" * length],
75
+ [prepending(space_count),"##{" " * (length - 2)}#" ],
76
+ [prepending(space_count), "#{base} #{key.capitalize} #{base}"],
77
+ [prepending(space_count),"##{" " * (length - 2)}#" ],
78
+ [prepending(space_count),"#" * length],
79
+ [prepending(space_count), "## #{desc}"],
80
+ [],
81
+ ]
82
+ end
83
+
84
+ def blocking(key:, do_block:, metadata:, space_count:, demeters_deep:, blocking_attributes:)
85
+ config = concat_demeter_with_key(blocking_attributes[:block_name], demeters_deep)
86
+ values = [
87
+ [prepending(space_count), "### Block to configure #{key.to_s.split("_").map {_1.capitalize}.join(" ")} ###"],
88
+ [prepending(space_count), metadata[:desc]],
89
+ ]
90
+ values << [prepending(space_count), "When using the block, the #{blocking_attributes[:enable_attr]} flag will automatically get set to true"] if blocking_attributes[:enable_attr]
91
+ values << [prepending(space_count), config, "do", "|#{do_block}|"]
92
+
93
+ values += yield.flatten(1)
94
+ values.pop if values[-1] == [""]
95
+ values << [prepending(space_count), "end"]
96
+
97
+ values << [""]
98
+ end
99
+
100
+ def spec(key:, metadata:, space_count:, demeters_deep:)
101
+ config = concat_demeter_with_key(key, demeters_deep)
102
+
103
+ if metadata[:allowed].include?(String)
104
+ default = "\"#{metadata[:default]}\""
105
+ else
106
+ default = metadata[:default]
107
+ end
108
+ arr = []
109
+
110
+ arr << [prepending(space_count), "#{metadata[:desc]}: #{(metadata[:allowed] - [ClassComposer::DefaultObject])}"] if metadata[:desc]
111
+ arr <<[prepending(space_count), config, "=", default]
112
+ arr << [""]
113
+
114
+ arr
115
+ end
116
+
117
+ def prepending(space_count)
118
+ "#{" " * space_count}#"
119
+ end
120
+
121
+ def concat_demeter_with_key(key, demeters_deep)
122
+ (demeters_deep + ["#{key}"]).join(".")
123
+ end
124
+ end
125
+ end
@@ -1,38 +1,170 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "class_composer/default_object"
4
+ require "class_composer/generate_config"
4
5
 
5
6
  module ClassComposer
7
+ FROZEN_TYPES = [
8
+ DEFAULT_FROZEN_TYPE = FROZEN_RAISE = :raise,
9
+ FROZEN_LOG_AND_ALLOW = :log_and_allow,
10
+ FROZEN_LOG_AND_SKIP = :log_and_skip,
11
+ ]
6
12
  module Generator
7
13
  def self.included(base)
8
14
  base.extend(ClassMethods)
15
+ base.include(InstanceMethods)
16
+ end
17
+
18
+ module InstanceMethods
19
+ def class_composer_frozen!(key)
20
+ # when nil, we allow changes to the instance methods
21
+ return if @class_composer_frozen.nil?
22
+
23
+ # When frozen is a proc, we let the user decide how to handle
24
+ # The return value decides if the value can be changed or not
25
+ if Proc === @class_composer_frozen
26
+ return @class_composer_frozen.(self, key)
27
+ end
28
+
29
+ msg = "#{self.class} instance methods are frozen. Attempted to change variable [#{key}]."
30
+ case @class_composer_frozen
31
+ when FROZEN_LOG_AND_ALLOW
32
+ msg += " This operation will proceed."
33
+ Kernel.warn(msg)
34
+ return true
35
+ when FROZEN_LOG_AND_SKIP
36
+ msg += " This operation will NOT proceed."
37
+ Kernel.warn(msg)
38
+ return false
39
+ when FROZEN_RAISE
40
+ raise Error, msg
41
+ end
42
+ end
43
+
44
+ def class_composer_assign_defaults!(children: false)
45
+ self.class.composer_mapping.each do |key, metadata|
46
+ assigned_value = method(:"#{key}").call
47
+ method(:"#{key}=").call(assigned_value)
48
+
49
+ if children && metadata[:children]
50
+ method(:"#{key}").call().class_composer_assign_defaults!(children: children)
51
+ end
52
+ end
53
+
54
+ nil
55
+ end
56
+
57
+ def class_composer_freeze_objects!(behavior: nil, children: false, &block)
58
+ if behavior && block
59
+ raise ArgumentError, "`behavior` and `block` can not both be present. Choose one"
60
+ end
61
+
62
+ if behavior.nil? && block.nil?
63
+ raise ArgumentError, "`behavior` or `block` must be present."
64
+ end
65
+
66
+ if block
67
+ @class_composer_frozen = block
68
+ else
69
+ if !FROZEN_TYPES.include?(behavior)
70
+ raise Error, "Unknown behavior [#{behavior}]. Expected one of #{FROZEN_TYPES}."
71
+ end
72
+ @class_composer_frozen = behavior
73
+ end
74
+
75
+ # If children is set, iterate the children, otherwise exit early
76
+ return if children == false
77
+
78
+ self.class.composer_mapping.each do |key, metadata|
79
+ next unless metadata[:children]
80
+
81
+ method(:"#{key}").call().class_composer_freeze_objects!(behavior:, children:, &block)
82
+ end
83
+ end
9
84
  end
10
85
 
11
86
  module ClassMethods
12
87
  COMPOSER_VALIDATE_METHOD_NAME = ->(name) { :"__composer_#{name}_is_valid__?" }
13
88
  COMPOSER_ASSIGNED_ATTR_NAME = ->(name) { :"@__composer_#{name}_value_assigned__" }
14
89
  COMPOSER_ASSIGNED_ARRAY_METHODS = ->(name) { :"@__composer_#{name}_array_methods_set__" }
90
+ COMPOSER_ALLOWED_FROZEN_TYPE_ARGS = [:raise, :log]
91
+
92
+ def add_composer_blocking(name, composer_class:, desc: nil, block_prepend: "with", enable_attr: nil)
93
+ unless composer_class.include?(ClassComposer::Generator)
94
+ raise ClassComposer::Error, ".add_composer_blocking passed `composer_class:` that does not include ClassComposer::Generator. Passed argument must include ClassComposer::Generator"
95
+ end
96
+
97
+ blocking_name = "#{block_prepend}_#{name}"
98
+ blocking_attributes = { block_name: blocking_name, enable_attr: enable_attr }
99
+ add_composer(name, allowed: composer_class, default: composer_class.new, desc: desc, blocking_attributes: blocking_attributes)
15
100
 
16
- def add_composer(name, allowed:, accessor: true, validator: ->(_) { true }, validation_error_klass: ::ClassComposer::ValidatorError, error_klass: ::ClassComposer::Error, **params)
101
+ define_method(blocking_name) do |&blk|
102
+ instance = public_send(:"#{name}")
103
+ instance.public_send(:"#{enable_attr}=", true) if enable_attr
104
+
105
+ blk.(instance) if blk
106
+
107
+ method(:"#{name}=").call(instance)
108
+ end
109
+
110
+ if enable_attr
111
+ define_method("#{name}?") do
112
+ public_send(:"#{name}").public_send(enable_attr)
113
+ end
114
+ end
115
+ end
116
+
117
+ def add_composer(name, allowed:, desc: nil, validator: ->(_) { true }, validation_error_klass: ::ClassComposer::ValidatorError, error_klass: ::ClassComposer::Error, blocking_attributes: nil, default_shown: nil, **params, &blk)
17
118
  default =
18
119
  if params.has_key?(:default)
19
120
  params[:default]
20
121
  else
21
- if allowed.is_a?(Array)
22
- allowed << ClassComposer::DefaultObject
23
- else
24
- allowed = [allowed, ClassComposer::DefaultObject]
25
- end
26
122
  ClassComposer::DefaultObject
27
123
  end
28
124
 
29
- allowed.include?(ClassComposer::DefaultObject)
125
+ if allowed.is_a?(Array)
126
+ allowed << ClassComposer::DefaultObject
127
+ else
128
+ allowed = [allowed, ClassComposer::DefaultObject]
129
+ end
130
+
131
+ if allowed.select { _1.include?(ClassComposer::Generator) }.count > 1
132
+ raise Error, "Allowed arguments has multiple classes that include ClassComposer::Generator. Max 1 is allowed"
133
+ end
134
+
30
135
  validate_proc = __composer_validator_proc__(validator: validator, allowed: allowed, name: name, error_klass: error_klass)
31
136
  __composer_validate_options__!(name: name, validate_proc: validate_proc, default: default, validation_error_klass: validation_error_klass, error_klass: error_klass)
32
137
 
33
138
  array_proc = __composer_array_proc__(name: name, validator: validator, allowed: allowed, params: params)
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)
139
+ __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, &blk)
35
140
  __composer_retrieval__(name: name, default: default, array_proc: array_proc)
141
+
142
+ # Add to mapping
143
+ __add_to_composer_mapping__(name: name, default: default, allowed: allowed, desc: desc, blocking_attributes: blocking_attributes, default_shown: default_shown)
144
+ end
145
+
146
+ def composer_mapping
147
+ @composer_mapping ||= {}
148
+ end
149
+
150
+ def composer_generate_config(wrapping:, require_file: nil, space_count: 2)
151
+ @composer_generate_config ||= GenerateConfig.new(instance: self)
152
+
153
+ @composer_generate_config.execute(wrapping:, require_file:, space_count:)
154
+ end
155
+
156
+ def __add_to_composer_mapping__(name:, default:, allowed:, desc:, blocking_attributes:, default_shown: nil)
157
+ children = Array(allowed).select { _1.include?(ClassComposer::Generator) }.map do |allowed_class|
158
+ allowed_class.composer_mapping
159
+ end
160
+
161
+ composer_mapping[name] = {
162
+ desc: desc,
163
+ children: children.empty? ? nil : children,
164
+ default: default_shown || (default.to_s.start_with?("#<") ? default.class : default),
165
+ blocking_attributes: blocking_attributes,
166
+ allowed: allowed,
167
+ }.compact
36
168
  end
37
169
 
38
170
  def __composer_validate_options__!(name:, validate_proc:, default:, params: {}, validation_error_klass:, error_klass:)
@@ -52,22 +184,27 @@ module ClassComposer
52
184
  end
53
185
 
54
186
  # create assignment method for the incoming name
55
- def __composer_assignment__(name:, params:, allowed:, validator:, array_proc:, validation_error_klass:, error_klass:)
187
+ def __composer_assignment__(name:, params:, allowed:, validator:, array_proc:, validation_error_klass:, error_klass:, &blk)
56
188
  define_method(:"#{name}=") do |value|
189
+ case class_composer_frozen!(name)
190
+ when false
191
+ # false is returned when the instance is frozen AND we do not allow the operation to proceed
192
+ return
193
+ when true
194
+ # true is returned when the instance is frozen AND we allow the operation to proceed
195
+ when nil
196
+ # nil is returned when the instance is not frozen
197
+ end
198
+
57
199
  is_valid = validator.(value)
58
200
 
59
201
  if is_valid
60
202
  instance_variable_set(COMPOSER_ASSIGNED_ATTR_NAME.(name), true)
61
203
  instance_variable_set(:"@#{name}", value)
62
204
  else
63
- message = ["#{self.class}.#{name} failed validation. #{name} is expected to be #{allowed}."]
205
+ message = ["#{self.class}.#{name} failed validation. #{name} is expected to be #{allowed}. Received [#{value}](#{value.class})"]
64
206
 
65
207
  message << (params[:invalid_message].is_a?(Proc) ? params[:invalid_message].(value) : params[:invalid_message].to_s)
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
208
  raise validation_error_klass, message.compact.join(" ")
72
209
  end
73
210
 
@@ -79,6 +216,10 @@ module ClassComposer
79
216
  value.instance_variable_set(COMPOSER_ASSIGNED_ARRAY_METHODS.(name), true)
80
217
  end
81
218
 
219
+ if blk
220
+ yield(name, value)
221
+ end
222
+
82
223
  value
83
224
  end
84
225
  end
@@ -120,7 +261,7 @@ module ClassComposer
120
261
  # Default object will likely raise an error if there is a custom validator
121
262
  (allowed.include?(ClassComposer::DefaultObject) && value == ClassComposer::DefaultObject) || (allow && validator.(value))
122
263
  rescue StandardError => e
123
- raise error_klass, "#{e} occured during validation for value [#{value}]. Check custom validator for #{name}"
264
+ raise error_klass, "#{e} occurred during validation for value [#{value}]. Check custom validator for #{name}"
124
265
  end
125
266
  end
126
267
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ClassComposer
4
- VERSION = "1.0.2"
4
+ VERSION = "2.0.0"
5
5
  end
metadata CHANGED
@@ -1,71 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: class_composer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Taylor
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2022-07-04 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: pry-byebug
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
- - !ruby/object:Gem::Dependency
28
- name: rake
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '12.0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '12.0'
41
- - !ruby/object:Gem::Dependency
42
- name: rspec
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '3.0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '3.0'
55
- - !ruby/object:Gem::Dependency
56
- name: simplecov
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: 0.17.0
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: 0.17.0
11
+ date: 2024-12-01 00:00:00.000000000 Z
12
+ dependencies: []
69
13
  description: Compose configurations for any class.
70
14
  email:
71
15
  - mattius.taylor@gmail.com
@@ -89,8 +33,15 @@ files:
89
33
  - bin/setup
90
34
  - class_composer.gemspec
91
35
  - docker-compose.yml
36
+ - docs/array_usage.md
37
+ - docs/basic_composer.md
38
+ - docs/basic_composer_example.md
39
+ - docs/composer_blocking.md
40
+ - docs/freezing.md
41
+ - docs/generating_initializer.md
92
42
  - lib/class_composer.rb
93
43
  - lib/class_composer/default_object.rb
44
+ - lib/class_composer/generate_config.rb
94
45
  - lib/class_composer/generator.rb
95
46
  - lib/class_composer/version.rb
96
47
  homepage: https://github.com/matt-taylor/class_composer
@@ -107,14 +58,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
107
58
  requirements:
108
59
  - - ">="
109
60
  - !ruby/object:Gem::Version
110
- version: '2.7'
61
+ version: '3.1'
111
62
  required_rubygems_version: !ruby/object:Gem::Requirement
112
63
  requirements:
113
64
  - - ">="
114
65
  - !ruby/object:Gem::Version
115
66
  version: '0'
116
67
  requirements: []
117
- rubygems_version: 3.3.11
68
+ rubygems_version: 3.5.9
118
69
  signing_key:
119
70
  specification_version: 4
120
71
  summary: Easily compose a class via inline code or passed in YAML config. Add instance