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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +59 -0
- data/LICENSE +21 -0
- data/README.md +320 -0
- data/Rakefile +36 -0
- data/lib/generators/propel_api/USAGE +8 -0
- data/lib/generators/propel_api/controller/controller_generator.rb +208 -0
- data/lib/generators/propel_api/core/base.rb +19 -0
- data/lib/generators/propel_api/core/configuration_methods.rb +187 -0
- data/lib/generators/propel_api/core/named_base.rb +457 -0
- data/lib/generators/propel_api/core/path_generation_methods.rb +45 -0
- data/lib/generators/propel_api/core/relationship_inferrer.rb +117 -0
- data/lib/generators/propel_api/install/install_generator.rb +343 -0
- data/lib/generators/propel_api/resource/resource_generator.rb +433 -0
- data/lib/generators/propel_api/templates/config/propel_api.rb.tt +149 -0
- data/lib/generators/propel_api/templates/controllers/api_controller_graphiti.rb +79 -0
- data/lib/generators/propel_api/templates/controllers/api_controller_propel_facets.rb +76 -0
- data/lib/generators/propel_api/templates/controllers/example_controller.rb.tt +96 -0
- data/lib/generators/propel_api/templates/scaffold/facet_controller_template.rb.tt +80 -0
- data/lib/generators/propel_api/templates/scaffold/facet_model_template.rb.tt +141 -0
- data/lib/generators/propel_api/templates/scaffold/graphiti_controller_template.rb.tt +82 -0
- data/lib/generators/propel_api/templates/scaffold/graphiti_model_template.rb.tt +32 -0
- data/lib/generators/propel_api/templates/seeds/seeds_template.rb.tt +493 -0
- data/lib/generators/propel_api/templates/tests/controller_test_template.rb.tt +485 -0
- data/lib/generators/propel_api/templates/tests/fixtures_template.yml.tt +250 -0
- data/lib/generators/propel_api/templates/tests/integration_test_template.rb.tt +487 -0
- data/lib/generators/propel_api/templates/tests/model_test_template.rb.tt +252 -0
- data/lib/generators/propel_api/unpack/unpack_generator.rb +304 -0
- data/lib/propel_api.rb +3 -0
- 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
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: []
|