propel_api 0.1.1

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 (30) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +59 -0
  3. data/LICENSE +21 -0
  4. data/README.md +320 -0
  5. data/Rakefile +36 -0
  6. data/lib/generators/propel_api/USAGE +8 -0
  7. data/lib/generators/propel_api/controller/controller_generator.rb +208 -0
  8. data/lib/generators/propel_api/core/base.rb +19 -0
  9. data/lib/generators/propel_api/core/configuration_methods.rb +187 -0
  10. data/lib/generators/propel_api/core/named_base.rb +457 -0
  11. data/lib/generators/propel_api/core/path_generation_methods.rb +45 -0
  12. data/lib/generators/propel_api/core/relationship_inferrer.rb +117 -0
  13. data/lib/generators/propel_api/install/install_generator.rb +343 -0
  14. data/lib/generators/propel_api/resource/resource_generator.rb +433 -0
  15. data/lib/generators/propel_api/templates/config/propel_api.rb.tt +149 -0
  16. data/lib/generators/propel_api/templates/controllers/api_controller_graphiti.rb +79 -0
  17. data/lib/generators/propel_api/templates/controllers/api_controller_propel_facets.rb +76 -0
  18. data/lib/generators/propel_api/templates/controllers/example_controller.rb.tt +96 -0
  19. data/lib/generators/propel_api/templates/scaffold/facet_controller_template.rb.tt +80 -0
  20. data/lib/generators/propel_api/templates/scaffold/facet_model_template.rb.tt +141 -0
  21. data/lib/generators/propel_api/templates/scaffold/graphiti_controller_template.rb.tt +82 -0
  22. data/lib/generators/propel_api/templates/scaffold/graphiti_model_template.rb.tt +32 -0
  23. data/lib/generators/propel_api/templates/seeds/seeds_template.rb.tt +493 -0
  24. data/lib/generators/propel_api/templates/tests/controller_test_template.rb.tt +485 -0
  25. data/lib/generators/propel_api/templates/tests/fixtures_template.yml.tt +250 -0
  26. data/lib/generators/propel_api/templates/tests/integration_test_template.rb.tt +487 -0
  27. data/lib/generators/propel_api/templates/tests/model_test_template.rb.tt +252 -0
  28. data/lib/generators/propel_api/unpack/unpack_generator.rb +304 -0
  29. data/lib/propel_api.rb +3 -0
  30. metadata +95 -0
@@ -0,0 +1,252 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class <%= class_name %>Test < ActiveSupport::TestCase
6
+
7
+ def setup
8
+ @organization = organizations(:one)
9
+ @user = users(:one)
10
+ @<%= singular_table_name %> = <%= table_name %>(:one)
11
+ end
12
+
13
+ # === ASSOCIATION TESTS ===
14
+ <% attributes.select { |attr| attr.type == :references }.each do |attribute| -%>
15
+
16
+ test "should belong to <%= attribute.name %>" do
17
+ assert_respond_to @<%= singular_table_name %>, :<%= attribute.name %>
18
+ assert_kind_of <%= attribute.name.camelize %>, @<%= singular_table_name %>.<%= attribute.name %>
19
+ end
20
+
21
+ test "should be invalid without <%= attribute.name %>" do
22
+ @<%= singular_table_name %>.<%= attribute.name %> = nil
23
+ assert_not @<%= singular_table_name %>.valid?
24
+ assert_includes @<%= singular_table_name %>.errors[:<%= attribute.name %>], "must exist"
25
+ end
26
+ <% end -%>
27
+
28
+ # === VALIDATION TESTS ===
29
+ <% attributes.reject { |attr| attr.type == :references }.each do |attribute| -%>
30
+ <% if attribute.required? -%>
31
+
32
+ test "should be invalid without <%= attribute.name %>" do
33
+ @<%= singular_table_name %>.<%= attribute.name %> = nil
34
+ assert_not @<%= singular_table_name %>.valid?
35
+ assert_includes @<%= singular_table_name %>.errors[:<%= attribute.name %>], "can't be blank"
36
+ end
37
+ <% end -%>
38
+ <% if attribute.name.to_s.match?(/\A(email|email_address)\z/i) -%>
39
+
40
+ test "should validate <%= attribute.name %> format" do
41
+ @<%= singular_table_name %>.<%= attribute.name %> = "invalid_email"
42
+ assert_not @<%= singular_table_name %>.valid?
43
+ assert_includes @<%= singular_table_name %>.errors[:<%= attribute.name %>], "is invalid"
44
+
45
+ @<%= singular_table_name %>.<%= attribute.name %> = "valid@example.com"
46
+ assert @<%= singular_table_name %>.valid?
47
+ end
48
+ <% end -%>
49
+ <% if attribute.type == :integer -%>
50
+
51
+ test "should validate <%= attribute.name %> is an integer" do
52
+ @<%= singular_table_name %>.<%= attribute.name %> = "not_a_number"
53
+ assert_not @<%= singular_table_name %>.valid?
54
+ assert_includes @<%= singular_table_name %>.errors[:<%= attribute.name %>], "is not a number"
55
+ end
56
+ <% end -%>
57
+ <% end -%>
58
+
59
+ # === CREATION TESTS ===
60
+
61
+ test "should create valid <%= singular_table_name %>" do
62
+ <%= singular_table_name %> = <%= class_name %>.new(
63
+ <% attributes.each_with_index do |attribute, index| -%>
64
+ <% if attribute.type == :references -%>
65
+ <%= attribute.name %>: @<%= attribute.name %><%= ',' if index < attributes.length - 1 %>
66
+ <% elsif attribute.type == :string -%>
67
+ <%= attribute.name %>: "Test <%= attribute.name.humanize %>"<%= ',' if index < attributes.length - 1 %>
68
+ <% elsif attribute.type == :text -%>
69
+ <%= attribute.name %>: "Test <%= attribute.name.humanize %> content"<%= ',' if index < attributes.length - 1 %>
70
+ <% elsif attribute.type == :integer -%>
71
+ <%= attribute.name %>: <%= index + 1 %><%= ',' if index < attributes.length - 1 %>
72
+ <% elsif attribute.type == :boolean -%>
73
+ <%= attribute.name %>: true<%= ',' if index < attributes.length - 1 %>
74
+ <% elsif attribute.type == :decimal || attribute.type == :float -%>
75
+ <%= attribute.name %>: <%= (index + 1) * 10.5 %><%= ',' if index < attributes.length - 1 %>
76
+ <% else -%>
77
+ <%= attribute.name %>: "test_value"<%= ',' if index < attributes.length - 1 %>
78
+ <% end -%>
79
+ <% end -%>
80
+ )
81
+
82
+ assert <%= singular_table_name %>.valid?, "<%= class_name %> should be valid with all required attributes"
83
+ assert <%= singular_table_name %>.save, "Should save valid <%= singular_table_name %>"
84
+ end
85
+
86
+ test "should create <%= singular_table_name %> count change" do
87
+ assert_difference('<%= class_name %>.count', 1) do
88
+ <%= class_name %>.create!(
89
+ <% attributes.each_with_index do |attribute, index| -%>
90
+ <% if attribute.type == :references -%>
91
+ <%= attribute.name %>: @<%= attribute.name %><%= ',' if index < attributes.length - 1 %>
92
+ <% elsif attribute.type == :string -%>
93
+ <%= attribute.name %>: "New Test <%= attribute.name.humanize %>"<%= ',' if index < attributes.length - 1 %>
94
+ <% elsif attribute.type == :text -%>
95
+ <%= attribute.name %>: "New test <%= attribute.name.humanize %> content"<%= ',' if index < attributes.length - 1 %>
96
+ <% elsif attribute.type == :integer -%>
97
+ <%= attribute.name %>: 999<%= ',' if index < attributes.length - 1 %>
98
+ <% elsif attribute.type == :boolean -%>
99
+ <%= attribute.name %>: false<%= ',' if index < attributes.length - 1 %>
100
+ <% elsif attribute.type == :decimal || attribute.type == :float -%>
101
+ <%= attribute.name %>: 99.99<%= ',' if index < attributes.length - 1 %>
102
+ <% else -%>
103
+ <%= attribute.name %>: "new_test_value"<%= ',' if index < attributes.length - 1 %>
104
+ <% end -%>
105
+ <% end -%>
106
+ )
107
+ end
108
+ end
109
+
110
+ # === JSON FACET TESTS ===
111
+
112
+ test "should have short facet with correct fields" do
113
+ short_json = @<%= singular_table_name %>.as_json(facet: :short)
114
+
115
+ # Should always include id
116
+ assert_includes short_json.keys, 'id'
117
+ assert_equal @<%= singular_table_name %>.id, short_json['id']
118
+
119
+ # Should include identifying fields
120
+ <%
121
+ short_attributes = attributes.select do |attr|
122
+ identifying_fields = %w[name title label slug username email status state active published visible enabled]
123
+ simple_types = [:string, :integer, :boolean, :decimal, :float]
124
+ excluded_patterns = /\A(description|content|body|notes|comment|bio|about|summary|created_at|updated_at|deleted_at|password|digest|token|secret|key|salt|encrypted|confirmation|unlock|reset|api_key|access_token|refresh_token)\z/i
125
+ security_patterns = /(password|digest|token|secret|key|salt|encrypted|confirmation|unlock|reset|api_key)/i
126
+
127
+ identifying_fields.include?(attr.name.to_s) ||
128
+ (simple_types.include?(attr.type) &&
129
+ attr.name.to_s !~ excluded_patterns &&
130
+ attr.name.to_s !~ security_patterns &&
131
+ attr.name.to_s.length < 20)
132
+ end
133
+ short_attributes.each do |attribute| -%>
134
+ assert_includes short_json.keys, '<%= attribute.name %>'
135
+ <% end -%>
136
+ end
137
+
138
+ test "should have details facet with correct fields" do
139
+ details_json = @<%= singular_table_name %>.as_json(facet: :details)
140
+
141
+ # Should include id
142
+ assert_includes details_json.keys, 'id'
143
+ assert_equal @<%= singular_table_name %>.id, details_json['id']
144
+
145
+ # Should include most fields except security-sensitive ones
146
+ <%
147
+ detail_attributes = attributes.reject do |attr|
148
+ excluded_patterns = /\A(created_at|updated_at|deleted_at|password_digest|reset_password_token|confirmation_token|unlock_token)\z/i
149
+ security_patterns = /(password|digest|token|secret|key|salt|encrypted|confirmation|unlock|reset|api_key|access_token|refresh_token)/i
150
+ excluded_types = [:binary]
151
+
152
+ excluded_patterns.match?(attr.name.to_s) ||
153
+ security_patterns.match?(attr.name.to_s) ||
154
+ excluded_types.include?(attr.type)
155
+ end
156
+ detail_attributes.each do |attribute| -%>
157
+ assert_includes details_json.keys, '<%= attribute.name %>'
158
+ <% end -%>
159
+ end
160
+
161
+ test "short facet should not include large content fields" do
162
+ short_json = @<%= singular_table_name %>.as_json(facet: :short)
163
+
164
+ # Should not include large content fields in short facet
165
+ <% attributes.each do |attribute| -%>
166
+ <% if attribute.name.to_s.match?(/\A(description|content|body|notes|comment|bio|about|summary)\z/i) -%>
167
+ assert_not_includes short_json.keys, '<%= attribute.name %>', "Short facet should not include <%= attribute.name %>"
168
+ <% end -%>
169
+ <% end -%>
170
+ end
171
+
172
+ # === BUSINESS LOGIC TESTS ===
173
+
174
+ test "should have appropriate string representation" do
175
+ # Test that the model has a reasonable string representation
176
+ string_repr = @<%= singular_table_name %>.to_s
177
+ assert_not_nil string_repr
178
+ assert string_repr.length > 0
179
+ end
180
+
181
+ <% if attributes.any? { |attr| attr.name.to_s.match?(/\A(active|enabled|published|visible|status)\z/i) } -%>
182
+ # === STATE MANAGEMENT TESTS ===
183
+ <% attributes.each do |attribute| -%>
184
+ <% if attribute.name.to_s.match?(/\A(active|enabled|published|visible)\z/i) && attribute.type == :boolean -%>
185
+
186
+ test "should toggle <%= attribute.name %> state" do
187
+ original_state = @<%= singular_table_name %>.<%= attribute.name %>
188
+ @<%= singular_table_name %>.<%= attribute.name %> = !original_state
189
+ assert @<%= singular_table_name %>.save
190
+
191
+ @<%= singular_table_name %>.reload
192
+ assert_equal !original_state, @<%= singular_table_name %>.<%= attribute.name %>
193
+ end
194
+ <% end -%>
195
+ <% end -%>
196
+ <% end -%>
197
+
198
+ # === EDGE CASE TESTS ===
199
+
200
+ test "should handle nil values appropriately for optional fields" do
201
+ <% attributes.reject { |attr| attr.type == :references || attr.required? }.each do |attribute| -%>
202
+ @<%= singular_table_name %>.<%= attribute.name %> = nil
203
+ <% end -%>
204
+
205
+ # Should be valid if only optional fields are nil
206
+ assert @<%= singular_table_name %>.valid?, "Should be valid with nil optional fields"
207
+ end
208
+
209
+ test "should maintain data integrity" do
210
+ # Test that saved data matches what was set
211
+ original_<%= singular_table_name %> = <%= class_name %>.create!(
212
+ <% attributes.each_with_index do |attribute, index| -%>
213
+ <% if attribute.type == :references -%>
214
+ <%= attribute.name %>: @<%= attribute.name %><%= ',' if index < attributes.length - 1 %>
215
+ <% elsif attribute.type == :string -%>
216
+ <%= attribute.name %>: "Integrity Test <%= attribute.name.humanize %>"<%= ',' if index < attributes.length - 1 %>
217
+ <% elsif attribute.type == :text -%>
218
+ <%= attribute.name %>: "Integrity test <%= attribute.name.humanize %> content"<%= ',' if index < attributes.length - 1 %>
219
+ <% elsif attribute.type == :integer -%>
220
+ <%= attribute.name %>: 42<%= ',' if index < attributes.length - 1 %>
221
+ <% elsif attribute.type == :boolean -%>
222
+ <%= attribute.name %>: true<%= ',' if index < attributes.length - 1 %>
223
+ <% elsif attribute.type == :decimal || attribute.type == :float -%>
224
+ <%= attribute.name %>: 123.45<%= ',' if index < attributes.length - 1 %>
225
+ <% else -%>
226
+ <%= attribute.name %>: "integrity_test"<%= ',' if index < attributes.length - 1 %>
227
+ <% end -%>
228
+ <% end -%>
229
+ )
230
+
231
+ # Reload from database and verify data integrity
232
+ reloaded_<%= singular_table_name %> = <%= class_name %>.find(original_<%= singular_table_name %>.id)
233
+
234
+ <% attributes.each do |attribute| -%>
235
+ <% if attribute.type == :references -%>
236
+ assert_equal original_<%= singular_table_name %>.<%= attribute.name %>_id, reloaded_<%= singular_table_name %>.<%= attribute.name %>_id
237
+ <% elsif attribute.type == :string -%>
238
+ assert_equal "Integrity Test <%= attribute.name.humanize %>", reloaded_<%= singular_table_name %>.<%= attribute.name %>
239
+ <% elsif attribute.type == :text -%>
240
+ assert_equal "Integrity test <%= attribute.name.humanize %> content", reloaded_<%= singular_table_name %>.<%= attribute.name %>
241
+ <% elsif attribute.type == :integer -%>
242
+ assert_equal 42, reloaded_<%= singular_table_name %>.<%= attribute.name %>
243
+ <% elsif attribute.type == :boolean -%>
244
+ assert_equal true, reloaded_<%= singular_table_name %>.<%= attribute.name %>
245
+ <% elsif attribute.type == :decimal || attribute.type == :float -%>
246
+ assert_equal 123.45, reloaded_<%= singular_table_name %>.<%= attribute.name %>
247
+ <% else -%>
248
+ assert_equal "integrity_test", reloaded_<%= singular_table_name %>.<%= attribute.name %>
249
+ <% end -%>
250
+ <% end -%>
251
+ end
252
+ end
@@ -0,0 +1,304 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # PropelApi unpacker that extracts the API GENERATOR into the host application
5
+ #
6
+ # Usage:
7
+ # rails generate propel_api:unpack # Unpack entire generator (code + templates)
8
+ # rails generate propel_api:unpack --force # Overwrite existing generator files
9
+ # rails generate propel_api:unpack --templates-only # Only copy templates, not generator logic
10
+ # rails generate propel_api:unpack --controllers-only # Only copy controller templates
11
+ # rails generate propel_api:unpack --scaffolds-only # Only copy scaffold templates
12
+ # rails generate propel_api:unpack --tests-only # Only copy test templates
13
+ #
14
+ # This extracts the PropelApi generator from the gem into lib/generators/propel_api/
15
+ # so you can customize the generator logic and templates for your specific needs.
16
+ #
17
+ module PropelApi
18
+ class UnpackGenerator < Rails::Generators::Base
19
+ source_root File.expand_path("../../templates", __dir__)
20
+
21
+ desc "Extract PropelApi generator code from gem to lib/generators/propel_api/ for full customization"
22
+
23
+ class_option :force,
24
+ type: :boolean,
25
+ default: false,
26
+ desc: "Overwrite existing generator files"
27
+
28
+ class_option :templates_only,
29
+ type: :boolean,
30
+ default: false,
31
+ desc: "Extract only generator templates, not generator logic"
32
+
33
+ class_option :controllers_only,
34
+ type: :boolean,
35
+ default: false,
36
+ desc: "Extract only controller generator templates"
37
+
38
+ class_option :scaffolds_only,
39
+ type: :boolean,
40
+ default: false,
41
+ desc: "Extract only scaffold generator templates"
42
+
43
+ class_option :tests_only,
44
+ type: :boolean,
45
+ default: false,
46
+ desc: "Extract only test generator templates"
47
+
48
+ class_option :seeds_only,
49
+ type: :boolean,
50
+ default: false,
51
+ desc: "Only unpack seed templates"
52
+
53
+ class_option :config_only,
54
+ type: :boolean,
55
+ default: false,
56
+ desc: "Extract only config generator templates"
57
+
58
+ class_option :generator_only,
59
+ type: :boolean,
60
+ default: false,
61
+ desc: "Extract only generator logic code"
62
+
63
+ def unpack_propel_api_generator
64
+ show_welcome_message
65
+
66
+ components_to_unpack = determine_components
67
+ destination_path = "lib/generators/propel_api"
68
+
69
+ if File.exist?(destination_path) && !options[:force]
70
+ say "⚠️ #{destination_path} already exists (use --force to overwrite)", :yellow
71
+ return
72
+ end
73
+
74
+ say "📦 Extracting PropelApi generator components: #{components_to_unpack.join(', ')}", :green
75
+ say ""
76
+
77
+ unpack_components(components_to_unpack, destination_path)
78
+ show_completion_message(destination_path)
79
+ end
80
+
81
+ private
82
+
83
+ def show_welcome_message
84
+ say ""
85
+ say "╔══════════════════════════════════════╗", :cyan
86
+ say "║ PROPEL API GENERATOR UNPACKER ║", :cyan
87
+ say "╚══════════════════════════════════════╝", :cyan
88
+ say ""
89
+ say "This extracts the PropelApi GENERATOR from the gem into your application", :blue
90
+ say "at lib/generators/propel_api/ so you can customize generator logic and templates.", :blue
91
+ say ""
92
+ say "📦 FROM: Gem-based generator (black box)", :yellow
93
+ say "📁 TO: Local generator in lib/generators/propel_api/ (fully customizable)", :green
94
+ say ""
95
+ end
96
+
97
+ def determine_components
98
+ if options[:controllers_only]
99
+ return %w[controllers]
100
+ elsif options[:scaffolds_only]
101
+ return %w[scaffolds]
102
+ elsif options[:tests_only]
103
+ return %w[tests]
104
+ elsif options[:seeds_only]
105
+ return %w[seeds]
106
+ elsif options[:config_only]
107
+ return %w[config]
108
+ elsif options[:generator_only]
109
+ return %w[generator_logic]
110
+ elsif options[:templates_only]
111
+ return %w[controllers scaffolds tests seeds config]
112
+ else
113
+ # Default: everything including generator logic
114
+ return %w[controllers scaffolds tests seeds config generator_logic]
115
+ end
116
+ end
117
+
118
+ def unpack_components(components, destination_path)
119
+ FileUtils.mkdir_p(destination_path)
120
+
121
+ components.each do |component|
122
+ unpack_component(component, destination_path)
123
+ end
124
+ end
125
+
126
+ def unpack_component(component, destination_path)
127
+ case component
128
+ when 'controllers'
129
+ copy_controller_templates(destination_path)
130
+ when 'scaffolds'
131
+ copy_scaffold_templates(destination_path)
132
+ when 'tests'
133
+ copy_test_templates(destination_path)
134
+ when 'seeds'
135
+ copy_seed_templates(destination_path)
136
+ when 'config'
137
+ copy_config_templates(destination_path)
138
+ when 'generator_logic'
139
+ copy_generator_logic(destination_path)
140
+ else
141
+ say " ⚠️ Unknown component: #{component}", :red
142
+ end
143
+ end
144
+
145
+ def copy_controller_templates(destination_path)
146
+ say "📂 Extracting controller generator templates...", :blue
147
+
148
+ copy_directory_templates("controllers", destination_path)
149
+
150
+ say " ✅ Controller generator templates extracted", :green
151
+ end
152
+
153
+ def copy_scaffold_templates(destination_path)
154
+ say "📂 Extracting scaffold generator templates...", :blue
155
+
156
+ copy_directory_templates("scaffold", destination_path)
157
+
158
+ say " ✅ Scaffold generator templates extracted", :green
159
+ end
160
+
161
+ def copy_test_templates(destination_path)
162
+ say "📂 Extracting test generator templates...", :blue
163
+
164
+ copy_directory_templates("tests", destination_path)
165
+
166
+ say " ✅ Test generator templates extracted", :green
167
+ end
168
+
169
+ def copy_seed_templates(destination_path)
170
+ say "📂 Extracting seed generator templates...", :blue
171
+
172
+ copy_directory_templates("seeds", destination_path)
173
+
174
+ say " ✅ Seed generator templates extracted", :green
175
+ end
176
+
177
+ def copy_config_templates(destination_path)
178
+ say "📂 Extracting config generator templates...", :blue
179
+
180
+ copy_directory_templates("config", destination_path)
181
+
182
+ say " ✅ Config generator templates extracted", :green
183
+ end
184
+
185
+ def copy_generator_logic(destination_path)
186
+ say "📂 Extracting generator logic code...", :blue
187
+
188
+ # Copy the main generator files with proper Rails registration
189
+ generator_files = {
190
+ 'install_generator.rb' => 'install_generator.rb',
191
+ 'propel_api_generator.rb' => 'propel_api_generator.rb',
192
+ 'relationship_inferrer.rb' => 'relationship_inferrer.rb'
193
+ }
194
+
195
+ copied_count = 0
196
+ generator_files.each do |source_name, dest_name|
197
+ source_file = File.join(File.dirname(__dir__), source_name)
198
+ dest_file = File.join(destination_path, dest_name)
199
+
200
+ if File.exist?(source_file)
201
+ if source_name == 'install_generator.rb'
202
+ # Copy install generator with proper Rails namespace for host app
203
+ source_content = File.read(source_file)
204
+
205
+ # Ensure proper module structure for Rails generator registration
206
+ modified_content = source_content.gsub(
207
+ /^class PropelApi::InstallGenerator/,
208
+ "module PropelApi\n class InstallGenerator"
209
+ )
210
+
211
+ # Add module end if we added module start
212
+ if modified_content != source_content
213
+ modified_content += "\nend" unless modified_content.end_with?("end\n") || modified_content.end_with?("end")
214
+ end
215
+
216
+ FileUtils.mkdir_p(File.dirname(dest_file))
217
+ File.write(dest_file, modified_content)
218
+ else
219
+ # Copy other files normally
220
+ FileUtils.cp(source_file, dest_file)
221
+ end
222
+ copied_count += 1
223
+ else
224
+ say " ⚠️ Generator file not found: #{source_name}", :yellow
225
+ end
226
+ end
227
+
228
+ if copied_count > 0
229
+ say " ✅ Generator logic code extracted with proper Rails registration (#{copied_count} files)", :green
230
+ else
231
+ say " ⚠️ No generator logic files found", :yellow
232
+ end
233
+ end
234
+
235
+ def copy_directory_templates(directory, destination_path)
236
+ source_dir = File.join(self.class.source_root, directory)
237
+ dest_dir = File.join(destination_path, "templates", directory)
238
+
239
+ if Dir.exist?(source_dir)
240
+ FileUtils.mkdir_p(dest_dir)
241
+ copy_directory_recursively(source_dir, dest_dir)
242
+ else
243
+ say " ⚠️ Directory not found: #{directory}", :yellow
244
+ end
245
+ end
246
+
247
+ def copy_directory_recursively(source, destination)
248
+ Dir.glob("#{source}/**/*", File::FNM_DOTMATCH).each do |source_file|
249
+ next if File.directory?(source_file)
250
+ next if File.basename(source_file).start_with?('.')
251
+
252
+ relative_path = source_file.sub("#{source}/", '')
253
+ dest_file = File.join(destination, relative_path)
254
+
255
+ FileUtils.mkdir_p(File.dirname(dest_file))
256
+ FileUtils.cp(source_file, dest_file)
257
+ end
258
+ end
259
+
260
+ def show_completion_message(destination_path)
261
+ say ""
262
+ say "🎉 PropelApi GENERATOR unpacked successfully!", :green
263
+ say ""
264
+ say "📁 Generator extracted to: #{destination_path}/", :bold
265
+ say "📂 Templates available at: #{destination_path}/templates/", :bold
266
+ say ""
267
+
268
+ say "🔧 GENERATOR customization guide:", :bold
269
+ say " ⚙️ Install generator: #{destination_path}/install_generator.rb"
270
+ say " 🏗️ Main generator: #{destination_path}/propel_api_generator.rb"
271
+ say " 🔗 Relationship logic: #{destination_path}/relationship_inferrer.rb"
272
+ say " 🎮 Controller templates: #{destination_path}/templates/controllers/"
273
+ say " 🚀 Scaffold templates: #{destination_path}/templates/scaffold/"
274
+ say " �� Test templates: #{destination_path}/templates/tests/"
275
+ say " 🌱 Seed templates: #{destination_path}/templates/seeds/"
276
+ say " ⚙️ Config templates: #{destination_path}/templates/config/"
277
+ say ""
278
+
279
+ say "⚠️ Important: You now have a LOCAL GENERATOR, not gem generator:", :yellow
280
+ say " • Your local generator overrides the gem version completely"
281
+ say " • Customize any part: logic, templates, options, behavior"
282
+ say " • Commit generator code to version control"
283
+ say " • Update manually when PropelApi gem is updated"
284
+ say ""
285
+
286
+ say "💡 Test your customized generator:", :blue
287
+ say " # Your local generator will be used instead of the gem:"
288
+ say " rails generate propel_api:install"
289
+ say " rails generate propel_api User name:string email:string"
290
+ say ""
291
+
292
+ say "🔄 To use gem generator again:", :cyan
293
+ say " rm -rf #{destination_path} # Removes local generator, falls back to gem"
294
+ say ""
295
+
296
+ say "🚀 Now you can:", :green
297
+ say " • Modify API controller templates for your organization's standards"
298
+ say " • Customize scaffold generation logic and output"
299
+ say " • Add new serialization adapters beyond propel_facets/graphiti"
300
+ say " • Change relationship inference rules in relationship_inferrer.rb"
301
+ say " • Create company-specific API patterns and conventions"
302
+ end
303
+ end
304
+ end
data/lib/propel_api.rb ADDED
@@ -0,0 +1,3 @@
1
+ module PropelApi
2
+ VERSION = "0.1.1"
3
+ end
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: propel_api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Ryan Martin, Rafael Pivato, Chi Putera
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-07-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '7.0'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '9.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '7.0'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '9.0'
33
+ description: A comprehensive Rails generator that creates complete API resources with
34
+ models, controllers, tests, and realistic seed data using Faker. Supports both JSON
35
+ Facet and Graphiti serialization engines.
36
+ email:
37
+ - admin@propelhq.dev
38
+ executables: []
39
+ extensions: []
40
+ extra_rdoc_files: []
41
+ files:
42
+ - CHANGELOG.md
43
+ - LICENSE
44
+ - README.md
45
+ - Rakefile
46
+ - lib/generators/propel_api/USAGE
47
+ - lib/generators/propel_api/controller/controller_generator.rb
48
+ - lib/generators/propel_api/core/base.rb
49
+ - lib/generators/propel_api/core/configuration_methods.rb
50
+ - lib/generators/propel_api/core/named_base.rb
51
+ - lib/generators/propel_api/core/path_generation_methods.rb
52
+ - lib/generators/propel_api/core/relationship_inferrer.rb
53
+ - lib/generators/propel_api/install/install_generator.rb
54
+ - lib/generators/propel_api/resource/resource_generator.rb
55
+ - lib/generators/propel_api/templates/config/propel_api.rb.tt
56
+ - lib/generators/propel_api/templates/controllers/api_controller_graphiti.rb
57
+ - lib/generators/propel_api/templates/controllers/api_controller_propel_facets.rb
58
+ - lib/generators/propel_api/templates/controllers/example_controller.rb.tt
59
+ - lib/generators/propel_api/templates/scaffold/facet_controller_template.rb.tt
60
+ - lib/generators/propel_api/templates/scaffold/facet_model_template.rb.tt
61
+ - lib/generators/propel_api/templates/scaffold/graphiti_controller_template.rb.tt
62
+ - lib/generators/propel_api/templates/scaffold/graphiti_model_template.rb.tt
63
+ - lib/generators/propel_api/templates/seeds/seeds_template.rb.tt
64
+ - lib/generators/propel_api/templates/tests/controller_test_template.rb.tt
65
+ - lib/generators/propel_api/templates/tests/fixtures_template.yml.tt
66
+ - lib/generators/propel_api/templates/tests/integration_test_template.rb.tt
67
+ - lib/generators/propel_api/templates/tests/model_test_template.rb.tt
68
+ - lib/generators/propel_api/unpack/unpack_generator.rb
69
+ - lib/propel_api.rb
70
+ homepage: https://github.com/propel-hq/propel_rails.git
71
+ licenses:
72
+ - MIT
73
+ metadata:
74
+ homepage_uri: https://github.com/propel-hq/propel_rails.git
75
+ rubygems_mfa_required: 'true'
76
+ post_install_message:
77
+ rdoc_options: []
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: 3.2.0
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubygems_version: 3.4.19
92
+ signing_key:
93
+ specification_version: 4
94
+ summary: Rails generator for API resources with JSON Facet and Graphiti support
95
+ test_files: []