class_composer 2.0.0 → 2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dd01814260b83eaa363cdbdff3d1f078ee85b7d6850841a847e7d89edcef6e29
4
- data.tar.gz: 43d27db959b1dfdbd364cc1be9af76d0debe4786031822d290d7dac6292afefd
3
+ metadata.gz: ce164acfb44d923b971f227a236fef5388cbeef8295f8419a46f815ac8bdaa0f
4
+ data.tar.gz: a0adb222a5704fd56916621669c9cce3112e07a27e338ca2c5e8b51e4a91d42f
5
5
  SHA512:
6
- metadata.gz: 2802e0cae4ad7be7d5f866436250e24e89e5ef032d1196d0951b517e18de28b7db6473f757277f239519c167de6de3f15f10a19853fdef9818ae2e400e13e3af
7
- data.tar.gz: 175613d8ea5f331652eabad44e6090fb16f50703dbcb099dfb72bf96a5a2cd564eb8b3c491ed411124e908adada075ec6603b6b85a41f64e9661195b665e9b00
6
+ metadata.gz: fee77c510173e27b695924ff5f5ddb050e00bfb3d26e4b8f5a124576fa72554f462ca06e0ecd31abbb150b6aca339cb22fef11d46724829e0bb0f11f937d4ac8
7
+ data.tar.gz: 5ce561d2dcc6b3e0e87efc4e55eec8ab243aff03ae758c069ce816674378a39c6f3e8e0c36e925c013e3d255b56d714647dd6ca4a831f3cd82dc0c3306ad6abd
data/CHANGELOG.md CHANGED
@@ -10,6 +10,11 @@
10
10
 
11
11
  # Release Notes
12
12
 
13
+ ## v2.1.0 (Dec 2024)
14
+ - Default Dynamic Configuration -- Allow config options to be defaulted to other config variables. Ability to provide a Symbol of an existing `composer` or a Proc to custom define from anything in the application
15
+ - Update how default_shown works when generating the config
16
+ - Extract `class_methods` and `instance_methods` to their own files
17
+
13
18
  ## v2.0.0 (Nov 2024)
14
19
  - Minimum Ruby Version bumped to v3.1
15
20
  - Initializer File Generator
@@ -39,6 +39,21 @@ add_composer :status, allowed: Symbol, default: :different_default
39
39
  add_composer :status, allowed: Symbol, default: "This type does not match Symbol. This default value will raise error"
40
40
  ```
41
41
 
42
+ ### Dynamic Default
43
+ - Required: False
44
+ - Description: This option allows you to set a dynamic default composed of other `add_composer` values. The `dynamic_default` is lazy loaded.
45
+ - Type: Expected value to be a Symbol mapping to another `add_composer` method or a Proc that takes an instance as an argument.
46
+ - Note: When using `dynamic_default`, validations will lazily get run on retrieval. This means that you could have a runtime error if the dynamic value is not of the correct `allowed` type.
47
+
48
+ Examples
49
+ ```ruby
50
+ add_composer :app_name, allowed: Symbol, default: "My Cool App Name"
51
+
52
+ add_composer :app_name_comms, allowed: String, dynamic_default: :app_name
53
+
54
+ add_composer :app_name_website, allowed: String, dynamic_default: ->(instance) { "#{instance.app_name} + #{instance.app_name_comms}" }
55
+ ```
56
+
42
57
  ### Description
43
58
  - Required: False (Recommend)
44
59
  - 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)
@@ -28,7 +28,7 @@ end
28
28
  class AppConfiguration
29
29
  include ClassComposer::Generator
30
30
 
31
- add_composer_blocking :login, composer_class: LoginStrategy, desc: "Login Strategy for my Application"
31
+ add_composer :login, allowed: LoginStrategy, default: LoginStrategy.new, desc: "Login Strategy for my Application"
32
32
 
33
33
  add_composer_blocking :lockable, composer_class: LockableStrategy, enable_attr: :enable, desc: "Lock Strategy for my Application. By default this is disabled"
34
34
  end
@@ -57,7 +57,6 @@ module ClassComposer
57
57
  children.each do |child|
58
58
  children_config += generate(mapping: child, space_count:, demeters_deep: config_prepend)
59
59
  end
60
- # binding.pry
61
60
 
62
61
  children_config.flatten(1)
63
62
  else
@@ -100,10 +99,18 @@ module ClassComposer
100
99
  def spec(key:, metadata:, space_count:, demeters_deep:)
101
100
  config = concat_demeter_with_key(key, demeters_deep)
102
101
 
103
- if metadata[:allowed].include?(String)
102
+ if metadata[:default_shown]
103
+ default = metadata[:default_shown]
104
+ elsif metadata[:dynamic_default]
105
+ if Symbol === metadata[:dynamic_default]
106
+ default = concat_demeter_with_key(metadata[:dynamic_default], demeters_deep)
107
+ else
108
+ default = " # Proc provided for :dynamic_default parameter. :default_shown parameter not provided"
109
+ end
110
+ elsif metadata[:allowed].include?(String)
104
111
  default = "\"#{metadata[:default]}\""
105
112
  else
106
- default = metadata[:default]
113
+ default = custom_case(metadata[:default])
107
114
  end
108
115
  arr = []
109
116
 
@@ -114,6 +121,17 @@ module ClassComposer
114
121
  arr
115
122
  end
116
123
 
124
+ def custom_case(default)
125
+ case default
126
+ when Symbol
127
+ default.inspect
128
+ when (ActiveSupport::Duration rescue NilClass)
129
+ default.inspect.gsub(" ", ".")
130
+ else
131
+ default
132
+ end
133
+ end
134
+
117
135
  def prepending(space_count)
118
136
  "#{" " * space_count}#"
119
137
  end
@@ -0,0 +1,233 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClassComposer
4
+ module Generator
5
+ module ClassMethods
6
+ COMPOSER_VALIDATE_METHOD_NAME = ->(name) { :"__composer_#{name}_is_valid__?" }
7
+ COMPOSER_ASSIGNED_ATTR_NAME = ->(name) { :"@__composer_#{name}_value_assigned__" }
8
+ COMPOSER_ASSIGNED_ARRAY_METHODS = ->(name) { :"@__composer_#{name}_array_methods_set__" }
9
+ COMPOSER_ALLOWED_FROZEN_TYPE_ARGS = [:raise, :log]
10
+
11
+ def add_composer_blocking(name, composer_class:, desc: nil, block_prepend: "with", enable_attr: nil)
12
+ unless composer_class.include?(ClassComposer::Generator)
13
+ raise ClassComposer::Error, ".add_composer_blocking passed `composer_class:` that does not include ClassComposer::Generator. Passed argument must include ClassComposer::Generator"
14
+ end
15
+
16
+ blocking_name = "#{block_prepend}_#{name}"
17
+ blocking_attributes = { block_name: blocking_name, enable_attr: enable_attr }
18
+ add_composer(name, allowed: composer_class, default: composer_class.new, desc: desc, blocking_attributes: blocking_attributes)
19
+
20
+ define_method(blocking_name) do |&blk|
21
+ instance = public_send(:"#{name}")
22
+ instance.public_send(:"#{enable_attr}=", true) if enable_attr
23
+
24
+ blk.(instance) if blk
25
+
26
+ method(:"#{name}=").call(instance)
27
+ end
28
+
29
+ if enable_attr
30
+ define_method("#{name}?") do
31
+ public_send(:"#{name}").public_send(enable_attr)
32
+ end
33
+ end
34
+ end
35
+
36
+ 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)
37
+ default =
38
+ if params.has_key?(:default)
39
+ params[:default]
40
+ else
41
+ ClassComposer::DefaultObject
42
+ end
43
+
44
+ if params[:default] && params[:dynamic_default]
45
+ raise Error, "Composer :#{name} had both the `:default` and `:dynamic_default` assigned. Only one allowed"
46
+ end
47
+
48
+ if dynamic_default = params[:dynamic_default]
49
+ if ![Proc, Symbol].include?(dynamic_default.class)
50
+ raise Error, "Composer :#{name} defined `:dynamic_default: #{dynamic_default}`. Expected value to be a Symbol mapped to a composer element or a Proc"
51
+ end
52
+
53
+ if Symbol === dynamic_default && composer_mapping[dynamic_default].nil?
54
+ raise Error, "Composer :#{name} defined `dynamic_default: #{dynamic_default}`. #{dynamic_default} is not defined. Please ensure that all dynamic_default's are defined before setting them"
55
+ end
56
+ end
57
+
58
+ if allowed.is_a?(Array)
59
+ allowed << ClassComposer::DefaultObject
60
+ else
61
+ allowed = [allowed, ClassComposer::DefaultObject]
62
+ end
63
+
64
+ if allowed.select { _1.include?(ClassComposer::Generator) }.count > 1
65
+ raise Error, "Allowed arguments has multiple classes that include ClassComposer::Generator. Max 1 is allowed"
66
+ end
67
+
68
+ validate_proc = __composer_validator_proc__(validator: validator, allowed: allowed, name: name, error_klass: error_klass)
69
+ __composer_validate_options__!(name: name, validate_proc: validate_proc, default: default, validation_error_klass: validation_error_klass, error_klass: error_klass)
70
+
71
+ array_proc = __composer_array_proc__(name: name, validator: validator, allowed: allowed, params: params)
72
+ __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)
73
+ __composer_retrieval__(name: name, allowed: allowed, default: default, array_proc: array_proc, params: params, validator: validate_proc, validation_error_klass: validation_error_klass)
74
+
75
+ # Add to mapping
76
+ __add_to_composer_mapping__(name: name, default: default, allowed: allowed, desc: desc, blocking_attributes: blocking_attributes, default_shown: default_shown, dynamic_default: params[:dynamic_default])
77
+ end
78
+
79
+ def composer_mapping
80
+ @composer_mapping ||= {}
81
+ end
82
+
83
+ def composer_generate_config(wrapping:, require_file: nil, space_count: 2)
84
+ @composer_generate_config ||= GenerateConfig.new(instance: self)
85
+
86
+ @composer_generate_config.execute(wrapping:, require_file:, space_count:)
87
+ end
88
+
89
+ def __add_to_composer_mapping__(name:, default:, allowed:, desc:, blocking_attributes:, default_shown: nil, dynamic_default: nil)
90
+ children = Array(allowed).select { _1.include?(ClassComposer::Generator) }.map do |allowed_class|
91
+ allowed_class.composer_mapping
92
+ end
93
+
94
+ composer_mapping[name] = {
95
+ desc: desc,
96
+ children: children.empty? ? nil : children,
97
+ dynamic_default: dynamic_default,
98
+ default_shown: default_shown,
99
+ default: (default.to_s.start_with?("#<") ? default.class : default),
100
+ blocking_attributes: blocking_attributes,
101
+ allowed: allowed,
102
+ }.compact
103
+ end
104
+
105
+ def __composer_validate_options__!(name:, validate_proc:, default:, params: {}, validation_error_klass:, error_klass:)
106
+ unless validate_proc.(default)
107
+ raise validation_error_klass, "Default value [#{default}] for #{self.class}.#{name} is not valid"
108
+ end
109
+
110
+ if instance_methods.include?(name.to_sym)
111
+ raise error_klass, "[#{name}] is already defined. Ensure composer names are all uniq and do not class with class instance methods"
112
+ end
113
+ end
114
+
115
+ def __composer_array_proc__(name:, validator:, allowed:, params:)
116
+ Proc.new do |value, _itself|
117
+ _itself.send(:"#{name}=", value)
118
+ end
119
+ end
120
+
121
+ # create assignment method for the incoming name
122
+ def __composer_assignment__(name:, params:, allowed:, validator:, array_proc:, validation_error_klass:, error_klass:, &blk)
123
+ define_method(:"#{name}=") do |value|
124
+ case class_composer_frozen!(name)
125
+ when false
126
+ # false is returned when the instance is frozen AND we do not allow the operation to proceed
127
+ return
128
+ when true
129
+ # true is returned when the instance is frozen AND we allow the operation to proceed
130
+ when nil
131
+ # nil is returned when the instance is not frozen
132
+ end
133
+
134
+ is_valid = self.class.__run_validation_item(name: name, validator: validator, allowed: allowed, value: value, params: params)
135
+
136
+ if is_valid[:valid]
137
+ instance_variable_set(COMPOSER_ASSIGNED_ATTR_NAME.(name), true)
138
+ instance_variable_set(:"@#{name}", value)
139
+ else
140
+ raise validation_error_klass, is_valid[:message].compact.join(" ")
141
+ end
142
+
143
+ if value.is_a?(Array) && !value.instance_variable_get(COMPOSER_ASSIGNED_ARRAY_METHODS.(name))
144
+ _itself = itself
145
+ value.define_singleton_method(:<<) do |val|
146
+ array_proc.(super(val), _itself)
147
+ end
148
+ value.instance_variable_set(COMPOSER_ASSIGNED_ARRAY_METHODS.(name), true)
149
+ end
150
+
151
+ if blk
152
+ yield(name, value)
153
+ end
154
+
155
+ value
156
+ end
157
+ end
158
+
159
+ def __run_validation_item(validator:, name:, value:, allowed:, params:)
160
+ if validator.(value)
161
+ return { valid: true }
162
+ end
163
+
164
+ message = ["#{self.class}.#{name} failed validation. #{name} is expected to be #{allowed}. Received [#{value}](#{value.class})"]
165
+ message << (params[:invalid_message].is_a?(Proc) ? params[:invalid_message].(value) : params[:invalid_message].to_s)
166
+
167
+ { valid: false, message: message }
168
+ end
169
+
170
+ # retrieve the value for the name -- Or return the default value
171
+ def __composer_retrieval__(name:, default:, array_proc:, allowed:, params:, validator:, validation_error_klass:)
172
+ define_method(:"#{name}") do
173
+ value = instance_variable_get(:"@#{name}")
174
+ return value if instance_variable_get(COMPOSER_ASSIGNED_ATTR_NAME.(name))
175
+
176
+ if dynamic_default = params[:dynamic_default]
177
+ if Proc === dynamic_default
178
+ value = dynamic_default.(self)
179
+ else
180
+ # We know the method exists because we already checked validity from within
181
+ # `compose_mapping` on add_composer creation
182
+ value = method(:"#{dynamic_default}").()
183
+ end
184
+ is_valid = self.class.__run_validation_item(name: name, validator: validator, allowed: allowed, value: value, params: params)
185
+
186
+ if is_valid[:valid]
187
+ instance_variable_set(COMPOSER_ASSIGNED_ATTR_NAME.(name), true)
188
+ instance_variable_set(:"@#{name}", value)
189
+ else
190
+ raise validation_error_klass, is_valid[:message].compact.join(" ")
191
+ end
192
+
193
+ return value
194
+ end
195
+
196
+ if default.is_a?(Array) && !default.instance_variable_get(COMPOSER_ASSIGNED_ARRAY_METHODS.(name))
197
+ _itself = itself
198
+ default.define_singleton_method(:<<) do |value|
199
+ array_proc.(super(value), _itself)
200
+ end
201
+ default.instance_variable_set(COMPOSER_ASSIGNED_ARRAY_METHODS.(name), true)
202
+ end
203
+
204
+ default == ClassComposer::DefaultObject ? ClassComposer::DefaultObject.value : default
205
+ end
206
+ end
207
+
208
+ # create validator method for incoming name
209
+ def __composer_validator_proc__(validator:, allowed:, name:, error_klass:)
210
+ if validator && !validator.is_a?(Proc)
211
+ raise error_klass, "Expected validator to be a Proc. Received [#{validator.class}]"
212
+ end
213
+
214
+ # Proc will validate the entire attribute -- Full assignment must occur before validate is called
215
+ Proc.new do |value|
216
+ begin
217
+ allow =
218
+ if allowed.is_a?(Array)
219
+ allowed.include?(value.class)
220
+ else
221
+ allowed == value.class
222
+ end
223
+ # order is important -- Do not run validator if it is the default object
224
+ # Default object will likely raise an error if there is a custom validator
225
+ (allowed.include?(ClassComposer::DefaultObject) && value == ClassComposer::DefaultObject) || (allow && validator.(value))
226
+ rescue StandardError => e
227
+ raise error_klass, "#{e} occurred during validation for value [#{value}]. Check custom validator for #{name}"
228
+ end
229
+ end
230
+ end
231
+ end
232
+ end
233
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClassComposer
4
+ module Generator
5
+ module InstanceMethods
6
+ def class_composer_frozen!(key)
7
+ # when nil, we allow changes to the instance methods
8
+ return if @class_composer_frozen.nil?
9
+
10
+ # When frozen is a proc, we let the user decide how to handle
11
+ # The return value decides if the value can be changed or not
12
+ if Proc === @class_composer_frozen
13
+ return @class_composer_frozen.(self, key)
14
+ end
15
+
16
+ msg = "#{self.class} instance methods are frozen. Attempted to change variable [#{key}]."
17
+ case @class_composer_frozen
18
+ when FROZEN_LOG_AND_ALLOW
19
+ msg += " This operation will proceed."
20
+ Kernel.warn(msg)
21
+ return true
22
+ when FROZEN_LOG_AND_SKIP
23
+ msg += " This operation will NOT proceed."
24
+ Kernel.warn(msg)
25
+ return false
26
+ when FROZEN_RAISE
27
+ raise Error, msg
28
+ end
29
+ end
30
+
31
+ def class_composer_assign_defaults!(children: false)
32
+ self.class.composer_mapping.each do |key, metadata|
33
+ assigned_value = method(:"#{key}").call
34
+ method(:"#{key}=").call(assigned_value)
35
+
36
+ if children && metadata[:children]
37
+ method(:"#{key}").call().class_composer_assign_defaults!(children: children)
38
+ end
39
+ end
40
+
41
+ nil
42
+ end
43
+
44
+ def class_composer_freeze_objects!(behavior: nil, children: false, &block)
45
+ if behavior && block
46
+ raise ArgumentError, "`behavior` and `block` can not both be present. Choose one"
47
+ end
48
+
49
+ if behavior.nil? && block.nil?
50
+ raise ArgumentError, "`behavior` or `block` must be present."
51
+ end
52
+
53
+ if block
54
+ @class_composer_frozen = block
55
+ else
56
+ if !FROZEN_TYPES.include?(behavior)
57
+ raise Error, "Unknown behavior [#{behavior}]. Expected one of #{FROZEN_TYPES}."
58
+ end
59
+ @class_composer_frozen = behavior
60
+ end
61
+
62
+ # If children is set, iterate the children, otherwise exit early
63
+ return if children == false
64
+
65
+ self.class.composer_mapping.each do |key, metadata|
66
+ next unless metadata[:children]
67
+
68
+ method(:"#{key}").call().class_composer_freeze_objects!(behavior:, children:, &block)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -2,6 +2,8 @@
2
2
 
3
3
  require "class_composer/default_object"
4
4
  require "class_composer/generate_config"
5
+ require "class_composer/generator/instance_methods"
6
+ require "class_composer/generator/class_methods"
5
7
 
6
8
  module ClassComposer
7
9
  FROZEN_TYPES = [
@@ -14,257 +16,5 @@ module ClassComposer
14
16
  base.extend(ClassMethods)
15
17
  base.include(InstanceMethods)
16
18
  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
84
- end
85
-
86
- module ClassMethods
87
- COMPOSER_VALIDATE_METHOD_NAME = ->(name) { :"__composer_#{name}_is_valid__?" }
88
- COMPOSER_ASSIGNED_ATTR_NAME = ->(name) { :"@__composer_#{name}_value_assigned__" }
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)
100
-
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)
118
- default =
119
- if params.has_key?(:default)
120
- params[:default]
121
- else
122
- ClassComposer::DefaultObject
123
- end
124
-
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
-
135
- validate_proc = __composer_validator_proc__(validator: validator, allowed: allowed, name: name, error_klass: error_klass)
136
- __composer_validate_options__!(name: name, validate_proc: validate_proc, default: default, validation_error_klass: validation_error_klass, error_klass: error_klass)
137
-
138
- array_proc = __composer_array_proc__(name: name, validator: validator, allowed: allowed, params: params)
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)
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
168
- end
169
-
170
- def __composer_validate_options__!(name:, validate_proc:, default:, params: {}, validation_error_klass:, error_klass:)
171
- unless validate_proc.(default)
172
- raise validation_error_klass, "Default value [#{default}] for #{self.class}.#{name} is not valid"
173
- end
174
-
175
- if instance_methods.include?(name.to_sym)
176
- raise error_klass, "[#{name}] is already defined. Ensure composer names are all uniq and do not class with class instance methods"
177
- end
178
- end
179
-
180
- def __composer_array_proc__(name:, validator:, allowed:, params:)
181
- Proc.new do |value, _itself|
182
- _itself.send(:"#{name}=", value)
183
- end
184
- end
185
-
186
- # create assignment method for the incoming name
187
- def __composer_assignment__(name:, params:, allowed:, validator:, array_proc:, validation_error_klass:, error_klass:, &blk)
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
-
199
- is_valid = validator.(value)
200
-
201
- if is_valid
202
- instance_variable_set(COMPOSER_ASSIGNED_ATTR_NAME.(name), true)
203
- instance_variable_set(:"@#{name}", value)
204
- else
205
- message = ["#{self.class}.#{name} failed validation. #{name} is expected to be #{allowed}. Received [#{value}](#{value.class})"]
206
-
207
- message << (params[:invalid_message].is_a?(Proc) ? params[:invalid_message].(value) : params[:invalid_message].to_s)
208
- raise validation_error_klass, message.compact.join(" ")
209
- end
210
-
211
- if value.is_a?(Array) && !value.instance_variable_get(COMPOSER_ASSIGNED_ARRAY_METHODS.(name))
212
- _itself = itself
213
- value.define_singleton_method(:<<) do |val|
214
- array_proc.(super(val), _itself)
215
- end
216
- value.instance_variable_set(COMPOSER_ASSIGNED_ARRAY_METHODS.(name), true)
217
- end
218
-
219
- if blk
220
- yield(name, value)
221
- end
222
-
223
- value
224
- end
225
- end
226
-
227
- # retrieve the value for the name -- Or return the default value
228
- def __composer_retrieval__(name:, default:, array_proc:)
229
- define_method(:"#{name}") do
230
- value = instance_variable_get(:"@#{name}")
231
- return value if instance_variable_get(COMPOSER_ASSIGNED_ATTR_NAME.(name))
232
-
233
- if default.is_a?(Array) && !default.instance_variable_get(COMPOSER_ASSIGNED_ARRAY_METHODS.(name))
234
- _itself = itself
235
- default.define_singleton_method(:<<) do |value|
236
- array_proc.(super(value), _itself)
237
- end
238
- default.instance_variable_set(COMPOSER_ASSIGNED_ARRAY_METHODS.(name), true)
239
- end
240
-
241
- default == ClassComposer::DefaultObject ? ClassComposer::DefaultObject.value : default
242
- end
243
- end
244
-
245
- # create validator method for incoming name
246
- def __composer_validator_proc__(validator:, allowed:, name:, error_klass:)
247
- if validator && !validator.is_a?(Proc)
248
- raise error_klass, "Expected validator to be a Proc. Received [#{validator.class}]"
249
- end
250
-
251
- # Proc will validate the entire attribute -- Full assignment must occur before validate is called
252
- Proc.new do |value|
253
- begin
254
- allow =
255
- if allowed.is_a?(Array)
256
- allowed.include?(value.class)
257
- else
258
- allowed == value.class
259
- end
260
- # order is important -- Do not run validator if it is the default object
261
- # Default object will likely raise an error if there is a custom validator
262
- (allowed.include?(ClassComposer::DefaultObject) && value == ClassComposer::DefaultObject) || (allow && validator.(value))
263
- rescue StandardError => e
264
- raise error_klass, "#{e} occurred during validation for value [#{value}]. Check custom validator for #{name}"
265
- end
266
- end
267
- end
268
- end
269
19
  end
270
20
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ClassComposer
4
- VERSION = "2.0.0"
4
+ VERSION = "2.1.0"
5
5
  end
@@ -6,5 +6,4 @@ require "class_composer/generator"
6
6
  module ClassComposer
7
7
  class Error < StandardError; end
8
8
  class ValidatorError < Error; end
9
- # Your code goes here...
10
9
  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: 2.0.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Taylor
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-12-01 00:00:00.000000000 Z
11
+ date: 2024-12-18 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Compose configurations for any class.
14
14
  email:
@@ -43,6 +43,8 @@ files:
43
43
  - lib/class_composer/default_object.rb
44
44
  - lib/class_composer/generate_config.rb
45
45
  - lib/class_composer/generator.rb
46
+ - lib/class_composer/generator/class_methods.rb
47
+ - lib/class_composer/generator/instance_methods.rb
46
48
  - lib/class_composer/version.rb
47
49
  homepage: https://github.com/matt-taylor/class_composer
48
50
  licenses: