glue_gun_dsl 0.1.16 → 0.1.18

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: cc6aaef2a48bc81e0383a4b88f0afc691a3f99097e12877579c7d579953cfea7
4
- data.tar.gz: ceb228b7381a0fbc7e2b3c07fe650cc05d89524dcec7199490fafb8813117bcc
3
+ metadata.gz: 1cf96906f1af3fd62ba4c0e7aa061d9d109aee45ced7284e7776cb74d7dca313
4
+ data.tar.gz: 47f2454a802ce7a9bd335e7413fd2851551eae2437d4de9bebb0f1f31ad82b53
5
5
  SHA512:
6
- metadata.gz: 7624649374c85a5822bb6a74096e50c69947db98bd1097ec020e3e82edde37c35a9057905534271da63385ef1c37d43b479712d0442247837151c0ab23ef9cbc
7
- data.tar.gz: 1f18555d5938dc3fb16bed600e3216502601e0b97c8a7c0998b22dd636cea40263f5ec7225767232e873778690c5ca0d752786c4e5017b371c35b17d191277aa
6
+ metadata.gz: 8f4be572fe72494ab001d7ea71ec0f35064d1914a7982ab026ffd12f40126dd01051c1594e9baf78ae3a6415b3bfb44d7524f512986af53dcaa0db3ab5924ba3
7
+ data.tar.gz: fcf9a586af6236a3595b6a215daca49239a5e7407ab4086626dbcc105f755a76a82ae45de536047a7a8464be0035a89b81e8c4ca5bb11dd1c7aca31eae049b97
data/lib/glue_gun/dsl.rb CHANGED
@@ -106,16 +106,15 @@ module GlueGun
106
106
  factory_class = options
107
107
  options = {}
108
108
  end
109
- is_array = options[:array] || false
110
- is_hash = options[:hash] || false
109
+
110
+ dependency_builder = DependencyBuilder.new(component_type)
111
111
 
112
112
  if factory_class.present?
113
- dependency_definitions[component_type] = { factory_class: factory_class, array: is_array, hash: is_hash }
113
+ dependency_builder.set_factory_class(factory_class)
114
114
  else
115
- dependency_builder = DependencyBuilder.new(component_type)
116
115
  dependency_builder.instance_eval(&block)
117
- dependency_definitions[component_type] = { builder: dependency_builder, array: is_array, hash: is_hash }
118
116
  end
117
+ dependency_definitions[component_type] = dependency_builder
119
118
 
120
119
  # Define singleton method to allow hardcoding dependencies in subclasses
121
120
  define_singleton_method component_type do |option = nil, options = {}|
@@ -178,29 +177,33 @@ module GlueGun
178
177
  end
179
178
  end
180
179
 
181
- def initialize_dependency(component_type, init_args = {}, definition = nil)
180
+ def initialize_dependency(component_type, init_args = nil, definition = nil)
182
181
  definition ||= self.class.dependency_definitions[component_type]
183
- is_array = definition[:array]
184
- is_hash = definition[:hash]
182
+ is_array = init_args.is_a?(Array)
183
+ is_hash = definition.is_hash?(init_args)
184
+
185
+ return nil if init_args.nil? && definition.default_option_name.nil?
185
186
 
186
187
  if is_array
187
188
  dep = []
188
189
  config = []
189
190
  Array(init_args).each do |args|
190
- d, c = initialize_single_dependency(component_type, args, definition)
191
+ d, c = definition.initialize_single_dependency(args, self)
191
192
  dep.push(d)
192
193
  config.push(c)
193
194
  end
194
195
  elsif is_hash
195
196
  dep = {}
196
197
  config = {}
198
+ definition.validate_hash_dependencies(init_args)
199
+
197
200
  init_args.each do |key, args|
198
- d, c = initialize_single_dependency(component_type, args, definition)
201
+ d, c = definition.initialize_single_dependency(args, self)
199
202
  dep[key] = d
200
203
  config[key] = c
201
204
  end
202
205
  else
203
- dep, config = initialize_single_dependency(component_type, init_args, definition)
206
+ dep, config = definition.initialize_single_dependency(init_args, self)
204
207
  end
205
208
 
206
209
  dependencies[component_type] = {
@@ -211,126 +214,6 @@ module GlueGun
211
214
  dep
212
215
  end
213
216
 
214
- def initialize_factory_dependency(component_type, init_args, definition)
215
- factory_instance = definition[:factory_class].new
216
-
217
- # Pass the parent instance to the factory
218
- factory_instance.instance_variable_set(:@parent, self)
219
-
220
- dep_defs = factory_instance.dependency_definitions
221
- definition = dep_defs[dep_defs.keys.first]
222
-
223
- if dep_defs.key?(component_type)
224
- factory_instance.send(:initialize_single_dependency, component_type, init_args, definition)
225
- elsif dep_defs.keys.one?
226
- factory_instance.send(:initialize_single_dependency, dep_defs.keys.first, init_args, definition)
227
- else
228
- raise ArgumentError,
229
- "Don't know how to use Factory #{factory_instance.class} to build dependency '#{component_type}'"
230
- end
231
- end
232
-
233
- def initialize_builder_dependency(component_type, init_args, definition)
234
- dependency_builder = definition[:builder]
235
-
236
- if init_args && init_args.is_a?(Hash) && init_args.key?(:option_name)
237
- option_name = init_args[:option_name]
238
- init_args = init_args[:value]
239
- else
240
- option_name, init_args = determine_option_name(component_type, init_args)
241
- end
242
-
243
- option_config = dependency_builder.option_configs[option_name]
244
-
245
- raise ArgumentError, "Unknown #{component_type} option '#{option_name}'" unless option_config
246
-
247
- [instantiate_dependency(option_config, init_args), option_config]
248
- end
249
-
250
- def initialize_single_dependency(component_type, init_args, definition)
251
- if dependency_injected?(component_type, init_args)
252
- dep = init_args
253
- option_config = injected_dependency(component_type, init_args)
254
- elsif definition[:factory_class]
255
- dep, option_config = initialize_factory_dependency(component_type, init_args, definition)
256
- else
257
- dep, option_config = initialize_builder_dependency(component_type, init_args, definition)
258
- end
259
-
260
- [dep, option_config]
261
- end
262
-
263
- def build_dependency_attributes(option_config, dep_attributes)
264
- option_config.attributes.each do |attr_name, attr_config|
265
- if dep_attributes.key?(attr_name)
266
- value = dep_attributes[attr_name]
267
- else
268
- value = if attr_config.source && respond_to?(attr_config.source)
269
- send(attr_config.source)
270
- elsif respond_to?(attr_name)
271
- send(attr_name)
272
- elsif instance_variable_defined?(:@parent) && @parent.respond_to?(attr_name)
273
- @parent.send(attr_name)
274
- else
275
- attr_config.default
276
- end
277
- value = attr_config.process_value(value, self) if attr_config.respond_to?(:process_value)
278
- dep_attributes[attr_name] = value
279
- end
280
- end
281
-
282
- dep_attributes
283
- end
284
-
285
- def determine_option_name(component_type, init_args)
286
- dependency_builder = self.class.dependency_definitions[component_type][:builder]
287
-
288
- option_name = nil
289
-
290
- # Use when block if defined
291
- if dependency_builder.when_block
292
- result = instance_exec(init_args, &dependency_builder.when_block)
293
- if result.is_a?(Hash) && result[:option]
294
- option_name = result[:option]
295
- as_attr = result[:as]
296
- init_args = { as_attr => init_args } if as_attr && init_args
297
- end
298
- end
299
-
300
- # Detect option from user input
301
- if option_name.nil? && (init_args.is_a?(Hash) && init_args.keys.size == 1)
302
- if dependency_builder.option_configs.key?(init_args.keys.first)
303
- option_name = init_args.keys.first
304
- init_args = init_args[option_name] # Extract the inner value
305
- else
306
- default_option = dependency_builder.get_option(dependency_builder.default_option_name)
307
- raise ArgumentError, "Unknown #{component_type} option: #{init_args.keys.first}." unless default_option.only?
308
- unless default_option.attributes.keys.include?(init_args.keys.first)
309
- raise ArgumentError, "#{default_option.class_name} does not respond to #{init_args.keys.first}"
310
- end
311
- end
312
- end
313
-
314
- # Use default option if none determined
315
- option_name ||= dependency_builder.default_option_name
316
-
317
- [option_name, init_args]
318
- end
319
-
320
- def instantiate_dependency(option_config, init_args)
321
- dep_attributes = init_args.is_a?(Hash) ? init_args : {}
322
-
323
- # Build dependency attributes, including sourcing from parent
324
- dep_attributes = build_dependency_attributes(option_config, dep_attributes)
325
-
326
- if dep_attributes.key?(:id)
327
- raise ArgumentError,
328
- "cannot bind attribute 'id' between #{self.class.name} and #{option_config.class_name}. ID is reserved for primary keys in Ruby on Rails"
329
- end
330
- dependency_class = option_config.class_name
331
- dependency_class.new(dep_attributes)
332
- end
333
-
334
217
  def propagate_changes
335
218
  changed_attributes.each do |attr_name, _old_value|
336
219
  new_value = read_attribute(attr_name)
@@ -380,26 +263,6 @@ module GlueGun
380
263
  end
381
264
  end
382
265
 
383
- def injected_dependency(component_type, value)
384
- definition = self.class.dependency_definitions[component_type]
385
- builder = definition[:builder]
386
- factory = definition[:factory_class]
387
-
388
- option_configs = if builder
389
- builder.option_configs
390
- else
391
- factory.dependency_definitions.values.first.values.first.option_configs
392
- end
393
- option_configs.values.select do |option|
394
- option_class = option.class_name
395
- value.is_a?(option_class)
396
- end
397
- end
398
-
399
- def dependency_injected?(component_type, value)
400
- injected_dependency(component_type, value).any?
401
- end
402
-
403
266
  def dependencies
404
267
  @dependencies ||= {}
405
268
  end
@@ -449,7 +312,7 @@ module GlueGun
449
312
  end
450
313
 
451
314
  class DependencyBuilder
452
- attr_reader :component_type, :option_configs, :when_block, :is_only
315
+ attr_reader :component_type, :option_configs, :when_block, :is_only, :factory_class
453
316
 
454
317
  def initialize(component_type)
455
318
  @component_type = component_type
@@ -459,6 +322,180 @@ module GlueGun
459
322
  @is_only = false
460
323
  end
461
324
 
325
+ def set_factory_class(factory_class)
326
+ @factory_class = factory_class
327
+ end
328
+
329
+ def builder
330
+ if factory?
331
+ factory_instance = factory_class.new
332
+ dep_defs = factory_instance.dependency_definitions
333
+ dep_defs[dep_defs.keys.first]
334
+ else
335
+ self
336
+ end
337
+ end
338
+
339
+ def get_option_configs
340
+ builder.option_configs
341
+ end
342
+
343
+ def allowed_configurations
344
+ get_option_configs.keys
345
+ end
346
+
347
+ def allowed_classes
348
+ get_option_configs.values
349
+ end
350
+
351
+ def factory?
352
+ @factory_class.present?
353
+ end
354
+
355
+ def builder?
356
+ !factory?
357
+ end
358
+
359
+ def is_hash?(init_args)
360
+ return false unless init_args.is_a?(Hash)
361
+
362
+ allowed_configs = allowed_configurations
363
+ return false if allowed_configs.count == 1 && allowed_configs == [:default]
364
+
365
+ if init_args.key?(:option_name)
366
+ allowed_configs.exclude?(init_args[:option_name])
367
+ else
368
+ init_args.keys.none? { |k| allowed_configs.include?(k) }
369
+ end
370
+ end
371
+
372
+ def initialize_factory_dependency(init_args, parent)
373
+ builder.initialize_single_dependency(init_args, parent)
374
+ end
375
+
376
+ def initialize_builder_dependency(init_args, parent)
377
+ if init_args && init_args.is_a?(Hash) && init_args.key?(:option_name)
378
+ option_name = init_args[:option_name]
379
+ init_args = init_args[:value]
380
+ else
381
+ option_name, init_args = determine_option_name(init_args)
382
+ end
383
+
384
+ option_config = option_configs[option_name]
385
+
386
+ raise ArgumentError, "Unknown #{component_type} option '#{option_name}'" unless option_config
387
+
388
+ [instantiate_dependency(option_config, init_args, parent), option_config]
389
+ end
390
+
391
+ def initialize_single_dependency(init_args, parent)
392
+ if dependency_injected?(init_args)
393
+ dep = init_args
394
+ option_config = injected_dependency(init_args)
395
+ elsif factory?
396
+ dep, option_config = initialize_factory_dependency(init_args, parent)
397
+ else
398
+ dep, option_config = initialize_builder_dependency(init_args, parent)
399
+ end
400
+
401
+ [dep, option_config]
402
+ end
403
+
404
+ def build_dependency_attributes(option_config, dep_attributes, parent)
405
+ option_config.attributes.each do |attr_name, attr_config|
406
+ if dep_attributes.key?(attr_name)
407
+ value = dep_attributes[attr_name]
408
+ else
409
+ value = if attr_config.source && parent.respond_to?(attr_config.source)
410
+ parent.send(attr_config.source)
411
+ elsif parent.respond_to?(attr_name)
412
+ parent.send(attr_name)
413
+ else
414
+ attr_config.default
415
+ end
416
+ value = attr_config.process_value(value, self) if attr_config.respond_to?(:process_value)
417
+ dep_attributes[attr_name] = value
418
+ end
419
+ end
420
+
421
+ dep_attributes
422
+ end
423
+
424
+ def determine_option_name(init_args)
425
+ option_name = nil
426
+
427
+ # Use when block if defined
428
+ if when_block
429
+ result = instance_exec(init_args, &when_block)
430
+ if result.is_a?(Hash) && result[:option]
431
+ option_name = result[:option]
432
+ as_attr = result[:as]
433
+ init_args = { as_attr => init_args } if as_attr && init_args
434
+ end
435
+ end
436
+
437
+ # Detect option from user input
438
+ if option_name.nil? && (init_args.is_a?(Hash) && init_args.keys.size == 1)
439
+ if option_configs.key?(init_args.keys.first)
440
+ option_name = init_args.keys.first
441
+ init_args = init_args[option_name] # Extract the inner value
442
+ else
443
+ default_option = get_option(default_option_name)
444
+ unless default_option.only?
445
+ raise ArgumentError,
446
+ "Unknown #{component_type} option: #{init_args.keys.first}."
447
+ end
448
+ unless default_option.attributes.keys.include?(init_args.keys.first)
449
+ raise ArgumentError, "#{default_option.class_name} does not respond to #{init_args.keys.first}"
450
+ end
451
+ end
452
+ end
453
+
454
+ # Use default option if none determined
455
+ option_name ||= default_option_name
456
+
457
+ [option_name, init_args]
458
+ end
459
+
460
+ def instantiate_dependency(option_config, init_args, parent)
461
+ dep_attributes = init_args.is_a?(Hash) ? init_args : {}
462
+
463
+ # Build dependency attributes, including sourcing from parent
464
+ dep_attributes = build_dependency_attributes(option_config, dep_attributes, parent)
465
+
466
+ if dep_attributes.key?(:id)
467
+ raise ArgumentError,
468
+ "cannot bind attribute 'id' between #{parent.class.name} and #{option_config.class_name}. ID is reserved for primary keys in Ruby on Rails"
469
+ end
470
+ dependency_class = option_config.class_name
471
+ dependency_class.new(dep_attributes)
472
+ end
473
+
474
+ def injected_dependency(value)
475
+ allowed_classes.detect do |option|
476
+ option_class = option.class_name
477
+ value.is_a?(option_class)
478
+ end
479
+ end
480
+
481
+ def dependency_injected?(value)
482
+ injected_dependency(value).present?
483
+ end
484
+
485
+ def validate_hash_dependencies(init_args)
486
+ allowed_configs = allowed_configurations
487
+
488
+ init_args.each do |_named_key, configuration|
489
+ next unless configuration.is_a?(Hash)
490
+
491
+ key = configuration.keys.first
492
+ if key.nil? || allowed_configs.exclude?(key)
493
+ raise ArgumentError,
494
+ "Unknown #{component_type} option: #{init_args.keys.first}."
495
+ end
496
+ end
497
+ end
498
+
462
499
  # Support set_class and attribute for single-option dependencies
463
500
  def set_class(class_name)
464
501
  single_option.set_class(class_name)
@@ -482,7 +519,11 @@ module GlueGun
482
519
  end
483
520
 
484
521
  def default_option_name
485
- @default_option_name || (@single_option ? :default : nil)
522
+ if factory?
523
+ builder.default_option_name
524
+ else
525
+ @default_option_name || (@single_option ? :default : nil)
526
+ end
486
527
  end
487
528
 
488
529
  def when(&block)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GlueGun
4
- VERSION = "0.1.16"
4
+ VERSION = "0.1.18"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glue_gun_dsl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.16
4
+ version: 0.1.18
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brett Shollenberger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-16 00:00:00.000000000 Z
11
+ date: 2024-10-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel