class_composer 2.0.0 → 2.1.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: 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: