hati-rails-api 0.1.0.beta1

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.
Files changed (32) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +237 -0
  4. data/hati-rails-api.gemspec +39 -0
  5. data/lib/generators/hati_rails_api/context_generator.rb +270 -0
  6. data/lib/hati_rails_api/context/configuration/configuration.rb +50 -0
  7. data/lib/hati_rails_api/context/configuration/domain_configuration.rb +55 -0
  8. data/lib/hati_rails_api/context/core/error_handler.rb +76 -0
  9. data/lib/hati_rails_api/context/core/loader.rb +54 -0
  10. data/lib/hati_rails_api/context/core/migration.rb +68 -0
  11. data/lib/hati_rails_api/context/core/public_api.rb +114 -0
  12. data/lib/hati_rails_api/context/core.rb +18 -0
  13. data/lib/hati_rails_api/context/extensions/string_extensions.rb +11 -0
  14. data/lib/hati_rails_api/context/generators/base_generator.rb +33 -0
  15. data/lib/hati_rails_api/context/generators/domain_generator.rb +219 -0
  16. data/lib/hati_rails_api/context/generators/file_generation.rb +72 -0
  17. data/lib/hati_rails_api/context/generators/generator.rb +212 -0
  18. data/lib/hati_rails_api/context/generators/layer_component_generator.rb +88 -0
  19. data/lib/hati_rails_api/context/generators/model_endpoint_generator.rb +97 -0
  20. data/lib/hati_rails_api/context/generators/operation_generator.rb +182 -0
  21. data/lib/hati_rails_api/context/layers/operation_layer.rb +45 -0
  22. data/lib/hati_rails_api/context/layers/standard_layer.rb +44 -0
  23. data/lib/hati_rails_api/context/managers/rollback_manager.rb +123 -0
  24. data/lib/hati_rails_api/context/shared/content_generators.rb +125 -0
  25. data/lib/hati_rails_api/context/shared/layer_factory.rb +37 -0
  26. data/lib/hati_rails_api/context.rb +30 -0
  27. data/lib/hati_rails_api/errors/unsupported_operation_error.rb +11 -0
  28. data/lib/hati_rails_api/macro/serializer_macro.rb +13 -0
  29. data/lib/hati_rails_api/response_handler.rb +71 -0
  30. data/lib/hati_rails_api/version.rb +5 -0
  31. data/lib/hati_rails_api.rb +15 -0
  32. metadata +121 -0
@@ -0,0 +1,212 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_generator'
4
+ require_relative 'domain_generator'
5
+ require_relative '../managers/rollback_manager'
6
+ require_relative '../shared/content_generators'
7
+
8
+ module HatiRailsApi
9
+ module Context
10
+ # Main orchestrator for the generation process
11
+ # Coordinates all generation activities with clean separation of concerns
12
+ class Generator < BaseGenerator
13
+ include ContentGenerators
14
+
15
+ # Default configurations for models and endpoints
16
+ DEFAULT_MODEL = { path: 'app/models', base: 'ApplicationRecord' }.freeze
17
+ DEFAULT_ENDPOINT = { path: 'app/controllers/api', base: 'ApplicationController' }.freeze
18
+
19
+ attr_reader :domains, :models, :endpoints
20
+
21
+ def initialize(config = nil, force: false)
22
+ super
23
+ @domains = {}
24
+ @models = []
25
+ @endpoints = []
26
+ end
27
+
28
+ # Register global models for generation
29
+ #
30
+ # @param names_or_options [String, Array, Hash] Model names or options
31
+ # @param options [Hash] Additional options
32
+ def model(names_or_options = nil, **options)
33
+ return unless names_or_options
34
+
35
+ names, opts = extract_names_and_options(names_or_options, options)
36
+ names.each { |name| @models << { name: name, options: opts } }
37
+ end
38
+
39
+ # Register global endpoints for generation
40
+ #
41
+ # @param names_or_options [String, Array, Hash] Endpoint names or options
42
+ # @param options [Hash] Additional options
43
+ def endpoint(names_or_options = nil, **options)
44
+ return unless names_or_options
45
+
46
+ names, opts = extract_names_and_options(names_or_options, options)
47
+ names.each { |name| @endpoints << { name: name, options: opts } }
48
+ end
49
+
50
+ # Define a domain with its configuration
51
+ #
52
+ # @param name [Symbol, String] Domain name
53
+ # @param options [Hash] Domain-specific options
54
+ # @param block [Proc] Domain configuration block
55
+ def domain(name, **options, &block)
56
+ domain_generator = create_domain_generator(name, options)
57
+ domain_generator.instance_eval(&block) if block_given?
58
+ @domains[name] = domain_generator
59
+ end
60
+
61
+ # Execute the complete generation process
62
+ #
63
+ # @return [Boolean] True if generation was successful
64
+ def execute
65
+ return false if nothing_to_generate?
66
+
67
+ generate_all_components
68
+ track_generation_results
69
+ true
70
+ end
71
+
72
+ private
73
+
74
+ # Extract names and options from flexible parameter format
75
+ def extract_names_and_options(names_or_options, options)
76
+ if names_or_options.is_a?(Hash)
77
+ [[], names_or_options]
78
+ else
79
+ names = Array(names_or_options)
80
+ [names, options]
81
+ end
82
+ end
83
+
84
+ # Create a properly configured domain generator
85
+ def create_domain_generator(name, options)
86
+ DomainGenerator.new(
87
+ name: name,
88
+ config: @config,
89
+ options: options,
90
+ generated_files: @generated_files,
91
+ force: @force,
92
+ override_all: method(:override_all_state)
93
+ ).tap do |generator|
94
+ generator.context_reference = self
95
+ end
96
+ end
97
+
98
+ # Check if there's anything to generate
99
+ def nothing_to_generate?
100
+ @models.empty? && @endpoints.empty? && @domains.empty?
101
+ end
102
+
103
+ # Generate all components in the correct order
104
+ def generate_all_components
105
+ generate_global_models
106
+ generate_global_endpoints
107
+ generate_domains
108
+ end
109
+
110
+ # Generate all registered global models
111
+ def generate_global_models
112
+ @models.each { |model_config| generate_single_model(model_config) }
113
+ end
114
+
115
+ # Generate all registered global endpoints
116
+ def generate_global_endpoints
117
+ @endpoints.each { |endpoint_config| generate_single_endpoint(endpoint_config) }
118
+ end
119
+
120
+ # Generate all defined domains
121
+ def generate_domains
122
+ @domains.each_value(&:generate)
123
+ end
124
+
125
+ # Generate a single model with error handling
126
+ def generate_single_model(model_config)
127
+ file_path = model_file_path(model_config)
128
+ content = model_content(model_config)
129
+
130
+ generate_file?(file_path, content)
131
+ end
132
+
133
+ # Generate a single endpoint with error handling
134
+ def generate_single_endpoint(endpoint_config)
135
+ file_path = endpoint_file_path(endpoint_config)
136
+ content = endpoint_content(endpoint_config)
137
+
138
+ generate_file?(file_path, content)
139
+ end
140
+
141
+ # Build model file path
142
+ def model_file_path(model_config)
143
+ path = resolve_model_path(model_config[:options])
144
+ File.join(path, "#{model_config[:name]}.rb")
145
+ end
146
+
147
+ # Build endpoint file path
148
+ def endpoint_file_path(endpoint_config)
149
+ path = resolve_endpoint_path(endpoint_config[:options])
150
+ File.join(path, "#{endpoint_config[:name]}_controller.rb")
151
+ end
152
+
153
+ # Generate model content
154
+ def model_content(model_config)
155
+ generate_model_content(
156
+ name: model_config[:name],
157
+ base_class: resolve_model_base_class,
158
+ timestamp: @timestamp
159
+ )
160
+ end
161
+
162
+ # Generate endpoint content
163
+ def endpoint_content(endpoint_config)
164
+ generate_controller_content(
165
+ name: endpoint_config[:name],
166
+ base_class: resolve_endpoint_base_class,
167
+ timestamp: @timestamp
168
+ )
169
+ end
170
+
171
+ # Resolve model path with fallbacks
172
+ def resolve_model_path(options)
173
+ options[:path] ||
174
+ @config&.model_config&.dig(:path) ||
175
+ DEFAULT_MODEL[:path]
176
+ end
177
+
178
+ # Resolve model base class with fallbacks
179
+ def resolve_model_base_class
180
+ @config&.model_config&.dig(:base) || DEFAULT_MODEL[:base]
181
+ end
182
+
183
+ # Resolve endpoint path with fallbacks
184
+ def resolve_endpoint_path(options)
185
+ options[:path] ||
186
+ @config&.endpoint_config&.dig(:path) ||
187
+ DEFAULT_ENDPOINT[:path]
188
+ end
189
+
190
+ # Resolve endpoint base class with fallbacks
191
+ def resolve_endpoint_base_class
192
+ @config&.endpoint_config&.dig(:base) || DEFAULT_ENDPOINT[:base]
193
+ end
194
+
195
+ # Track generation results for rollback functionality
196
+ def track_generation_results
197
+ return if @generated_files.empty?
198
+
199
+ RollbackManager.new.track_generation(@timestamp, @generated_files)
200
+ log_generation_summary
201
+ end
202
+
203
+ # Log a summary of the generation results
204
+ def log_generation_summary
205
+ puts "\nGeneration completed successfully!"
206
+ puts " Timestamp: #{@timestamp}"
207
+ puts " Files generated: #{@generated_files.length}"
208
+ puts " To rollback: rails generate hati_rails_api:context rollback --timestamp=#{@timestamp}"
209
+ end
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HatiRailsApi
4
+ module Context
5
+ # Concern for layer and component generation
6
+ # Follows Single Responsibility Principle - handles only layer/component generation
7
+ module LayerComponentGenerator
8
+ private
9
+
10
+ def generate_layer(base_path:, layer_name:, layer_config:)
11
+ layer_path = File.join(base_path, layer_name.to_s)
12
+ FileUtils.mkdir_p(layer_path)
13
+
14
+ layer_config.components.each do |component|
15
+ generate_component(
16
+ layer_path: layer_path,
17
+ component: component,
18
+ layer_config: layer_config
19
+ )
20
+ end
21
+ end
22
+
23
+ def generate_component(layer_path:, component:, layer_config:)
24
+ component_name = resolve_component_name(component: component, layer_config: layer_config)
25
+ file_path = File.join(layer_path, "#{component_name}.rb")
26
+ content = generate_component_content(component: component, layer_config: layer_config)
27
+
28
+ generate_file?(file_path, content)
29
+ end
30
+
31
+ def resolve_component_name(component:, layer_config:)
32
+ if component.is_a?(Hash)
33
+ name = component.keys.first.to_s
34
+ suffix = component.values.first[:suffix]
35
+ else
36
+ name = component == :domain ? @name.to_s : component.to_s
37
+ suffix = layer_config.default_suffix
38
+ end
39
+
40
+ suffix && suffix != false ? "#{name}_#{layer_config.layer_name}" : name
41
+ end
42
+
43
+ def generate_component_content(component:, layer_config:)
44
+ class_name = generate_class_name(component: component, layer_config: layer_config)
45
+
46
+ if layer_config.is_a?(OperationLayer)
47
+ generate_component_operation_content(
48
+ component: component,
49
+ class_name: class_name,
50
+ layer_config: layer_config
51
+ )
52
+ else
53
+ generate_standard_content(
54
+ component: component,
55
+ class_name: class_name,
56
+ layer_config: layer_config
57
+ )
58
+ end
59
+ end
60
+
61
+ def generate_class_name(component:, layer_config:)
62
+ if component.is_a?(Hash)
63
+ base_name = component.keys.first.to_s.camelize
64
+ suffix = component.values.first[:suffix]
65
+ else
66
+ name = component == :domain ? @name : component
67
+ base_name = name.to_s.camelize
68
+ suffix = layer_config.default_suffix
69
+ end
70
+
71
+ suffix && suffix != false ? "#{base_name}#{layer_config.layer_name.to_s.camelize}" : base_name
72
+ end
73
+
74
+ def generate_standard_content(component:, class_name:, layer_config:)
75
+ base_requirement = layer_config.base_class ? "require_relative '#{layer_config.base_class}'\n\n" : ''
76
+ base_class_inheritance = (" < #{layer_config.base_class.split('/').last.camelize}" if layer_config.base_class)
77
+
78
+ <<~RUBY
79
+ # frozen_string_literal: true
80
+ # Generated at #{Time.now.strftime('%Y%m%d%H%M%S')}
81
+ #{base_requirement}class #{class_name}#{base_class_inheritance}
82
+ # TODO: Implement #{class_name} logic
83
+ end
84
+ RUBY
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../shared/content_generators'
4
+
5
+ module HatiRailsApi
6
+ module Context
7
+ # Concern for model and endpoint generation
8
+ # Follows Single Responsibility Principle - handles only model/endpoint generation
9
+ module ModelEndpointGenerator
10
+ include ContentGenerators
11
+
12
+ DEFAULT_MODEL = {
13
+ path: 'app/models',
14
+ base: 'ApplicationRecord'
15
+ }.freeze
16
+
17
+ DEFAULT_ENDPOINT = {
18
+ path: 'app/controllers/api',
19
+ base: 'ApplicationController'
20
+ }.freeze
21
+
22
+ def generate_domain_model
23
+ options = @domain_model[:options] || {}
24
+
25
+ path = @config&.model_config&.dig(:path)
26
+ base_path = options[:path] || path || DEFAULT_MODEL[:path]
27
+
28
+ base = @config&.model_config&.dig(:base)
29
+ base_class = base || DEFAULT_MODEL[:base]
30
+
31
+ file_path = File.join(base_path, "#{@name}.rb")
32
+ content = generate_model_content(name: @name, base_class: base_class)
33
+
34
+ generate_file?(file_path, content)
35
+ end
36
+
37
+ def generate_domain_endpoint
38
+ options = @domain_endpoint[:options] || {}
39
+
40
+ path = @config&.endpoint_config&.dig(:path)
41
+ base_path = options[:path] || path || DEFAULT_ENDPOINT[:path]
42
+
43
+ base = @config&.endpoint_config&.dig(:base)
44
+ base_class = base || DEFAULT_ENDPOINT[:base]
45
+
46
+ file_path = File.join(base_path, "#{@name}_controller.rb")
47
+ content = generate_endpoint_content(base_class)
48
+
49
+ generate_file?(file_path, content)
50
+ end
51
+
52
+ def generate_endpoint_content(base_class)
53
+ # Get operation components to generate actions
54
+ operation_components = get_operation_components
55
+ actions_code = generate_controller_actions(operation_components)
56
+
57
+ generate_controller_content(
58
+ name: @name,
59
+ base_class: base_class,
60
+ actions_code: actions_code
61
+ )
62
+ end
63
+
64
+ def get_operation_components
65
+ # If explicit components are provided in endpoint declaration, use those
66
+ return @domain_endpoint[:explicit_components] if @domain_endpoint&.dig(:explicit_components)
67
+
68
+ # Otherwise, use operation layer components
69
+ operation_layer = @layers[:operation]
70
+ return [] unless operation_layer
71
+
72
+ operation_layer.components.reject { |comp| comp == :domain }
73
+ end
74
+
75
+ def generate_controller_actions(components)
76
+ return ' # TODO: Add controller actions here' if components.empty?
77
+
78
+ actions = components.map do |component|
79
+ action_name = component.to_s
80
+ operation_class = "#{action_name.camelize}Operation"
81
+
82
+ generate_custom_action(action_name, operation_class)
83
+ end
84
+
85
+ actions.join("\n\n")
86
+ end
87
+
88
+ def generate_custom_action(action_name, operation_class)
89
+ <<~RUBY.chomp
90
+ def #{action_name}
91
+ run_and_render #{operation_class}
92
+ end
93
+ RUBY
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,182 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../shared/content_generators'
4
+
5
+ module HatiRailsApi
6
+ module Context
7
+ # Concern for operation generation
8
+ # Follows Single Responsibility Principle - handles only operation generation
9
+ module OperationGenerator
10
+ include ContentGenerators
11
+
12
+ TIMESTAMP_FORMAT = '%Y%m%d%H%M%S'
13
+
14
+ def generate_component_operation_content(component:, class_name:, layer_config:)
15
+ base_class = resolve_base_class(layer_config)
16
+ action = extract_action_from_component(component)
17
+ timestamp = Time.now.strftime(TIMESTAMP_FORMAT)
18
+
19
+ # Check if we should generate step-based operations
20
+ if should_use_steps?(layer_config)
21
+ generate_step_based_operation_content(
22
+ class_name: class_name,
23
+ base_class: base_class,
24
+ action: action,
25
+ layer_config: layer_config,
26
+ timestamp: timestamp
27
+ )
28
+ else
29
+ generate_simple_operation_content(
30
+ class_name: class_name,
31
+ base_class: base_class,
32
+ action: action,
33
+ timestamp: timestamp
34
+ )
35
+ end
36
+ end
37
+
38
+ def generate_step_based_operation(class_name:, base_class:, action:, steps:)
39
+ step_declarations = generate_step_declarations(action, steps)
40
+ step_calls = generate_step_calls(steps)
41
+
42
+ generate_step_based_operation_content(
43
+ class_name: class_name,
44
+ base_class: base_class,
45
+ step_declarations: step_declarations,
46
+ step_calls: step_calls
47
+ )
48
+ end
49
+
50
+ def generate_standard_operation(class_name:, base_class:, action:, base_requirement:)
51
+ steps_content = generate_default_steps_for_action(action)
52
+ timestamp = Time.now.strftime(TIMESTAMP_FORMAT)
53
+
54
+ if base_requirement.blank?
55
+ generate_operation_content(
56
+ class_name: class_name,
57
+ base_class: base_class,
58
+ action: action,
59
+ steps_content: steps_content
60
+ )
61
+ return
62
+ end
63
+
64
+ <<~RUBY
65
+ # frozen_string_literal: true
66
+ # Generated at #{timestamp}
67
+
68
+ #{base_requirement}class #{class_name} < #{base_class}
69
+ # Operation configuration
70
+ #{generate_operation_macros(action)}
71
+
72
+ def call(params:)
73
+ #{steps_content}
74
+ end
75
+
76
+ private
77
+
78
+ #{generate_private_steps(action)}
79
+ end
80
+ RUBY
81
+ end
82
+
83
+ private
84
+
85
+ def should_use_steps?(layer_config)
86
+ # If operation layer has explicit steps, use those
87
+ return true if layer_config.respond_to?(:steps) && layer_config.steps.any?
88
+
89
+ # If step options include granular: true, use steps from other layers
90
+ return true if layer_config.respond_to?(:granular_steps?) && layer_config.granular_steps?
91
+
92
+ # If domain has multiple layers besides operation, use steps
93
+ other_layers = @layers.keys.reject { |name| name == :operation }
94
+ other_layers.any?
95
+ end
96
+
97
+ def generate_step_based_operation_content(class_name:, base_class:, action:, layer_config:, timestamp:)
98
+ steps = determine_steps_for_operation(layer_config)
99
+ step_declarations = generate_step_declarations(action, steps)
100
+
101
+ <<~RUBY
102
+ # frozen_string_literal: true
103
+ # Generated at #{timestamp}
104
+
105
+ class #{class_name} < #{base_class}
106
+ #{step_declarations}
107
+
108
+ def call(params:)
109
+ Success(params)
110
+ end
111
+ end
112
+ RUBY
113
+ end
114
+
115
+ def generate_simple_operation_content(class_name:, base_class:, action:, timestamp:)
116
+ <<~RUBY
117
+ # frozen_string_literal: true
118
+ # Generated at #{timestamp}
119
+
120
+ class #{class_name} < #{base_class}
121
+ def call(params:)
122
+ # TODO: Implement #{action} business logic
123
+ # Add your implementation here
124
+
125
+ Success(params)
126
+ end
127
+
128
+ private
129
+
130
+ # TODO: Add your private methods here
131
+ # Example:
132
+ # def validate_params(params)
133
+ # # validation logic
134
+ # end
135
+ end
136
+ RUBY
137
+ end
138
+
139
+ def determine_steps_for_operation(layer_config)
140
+ # If operation has explicit steps configured, use those
141
+ return layer_config.steps if layer_config.respond_to?(:steps) && layer_config.steps.any?
142
+
143
+ # If granular steps enabled, use other domain layers as steps
144
+ if layer_config.respond_to?(:granular_steps?) && layer_config.granular_steps?
145
+ return @layers.keys.reject { |name| name == :operation }
146
+ end
147
+
148
+ # Otherwise, use domain layers (excluding operation itself)
149
+ @layers.keys.reject { |name| name == :operation }
150
+ end
151
+
152
+ def generate_step_declarations(action, steps)
153
+ steps_arr = steps.map do |step_name|
154
+ service_class = "#{action.camelize}#{step_name.to_s.camelize}"
155
+ " step #{step_name}: \"#{service_class}\""
156
+ end
157
+
158
+ steps_arr.join("\n")
159
+ end
160
+
161
+ def generate_step_calls(steps)
162
+ step_calls = steps.map do |step_name|
163
+ " rez = #{step_name}.call(rez)"
164
+ end
165
+
166
+ ([' rez = params'] + step_calls).join("\n")
167
+ end
168
+
169
+ def resolve_base_class(layer_config)
170
+ if layer_config.base_class
171
+ layer_config.base_class.split('/').last.camelize
172
+ else
173
+ 'HatiOperation::Base'
174
+ end
175
+ end
176
+
177
+ def extract_action_from_component(component)
178
+ component.is_a?(Hash) ? component.keys.first.to_s : component.to_s
179
+ end
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'standard_layer'
4
+
5
+ module HatiRailsApi
6
+ module Context
7
+ # Operation layer configuration class
8
+ # Extends StandardLayer for operation-specific functionality
9
+ class OperationLayer < StandardLayer
10
+ attr_reader :step_options
11
+
12
+ def initialize
13
+ super(:operation)
14
+ @base_class = 'hati_operation/base'
15
+ @step_options = {}
16
+ end
17
+
18
+ def step(*step_names, **options)
19
+ if step_names.empty? && options.any?
20
+ # Handle: operation.step true, granular: true
21
+ @step_options.merge!(options)
22
+ elsif step_names.any?
23
+ # Handle: operation.step :validate, :encrypt, :store
24
+ @steps = step_names.flatten
25
+ @step_options.merge!(options) if options.any?
26
+ end
27
+ end
28
+
29
+ def steps
30
+ @steps || []
31
+ end
32
+
33
+ def granular_steps?
34
+ @step_options[:granular] == true
35
+ end
36
+
37
+ def dup
38
+ new_layer = super
39
+ new_layer.instance_variable_set(:@steps, @steps&.dup)
40
+ new_layer.instance_variable_set(:@step_options, @step_options.dup)
41
+ new_layer
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HatiRailsApi
4
+ module Context
5
+ # Standard layer configuration class
6
+ # Follows Single Responsibility Principle - handles standard layer configuration
7
+ class StandardLayer
8
+ attr_reader :layer_name, :base_class, :components, :default_suffix
9
+
10
+ def initialize(layer_name)
11
+ @layer_name = layer_name
12
+ @base_class = nil
13
+ @components = []
14
+ @default_suffix = true
15
+ end
16
+
17
+ def base(base_class)
18
+ @base_class = base_class
19
+ end
20
+
21
+ def component(*names, **options)
22
+ if names.empty?
23
+ @components = [:domain]
24
+ else
25
+ names.each do |name|
26
+ if name.is_a?(Array)
27
+ @components.concat(name)
28
+ else
29
+ @components << (options.empty? ? name : { name => options })
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ def dup
36
+ new_layer = self.class.new(@layer_name)
37
+ new_layer.instance_variable_set(:@base_class, @base_class)
38
+ new_layer.instance_variable_set(:@components, @components.dup)
39
+ new_layer.instance_variable_set(:@default_suffix, @default_suffix)
40
+ new_layer
41
+ end
42
+ end
43
+ end
44
+ end