propel_api 0.2.1 → 0.3.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 +4 -4
- data/CHANGELOG.md +97 -0
- data/README.md +239 -4
- data/lib/generators/propel_api/controller/controller_generator.rb +26 -0
- data/lib/generators/propel_api/core/named_base.rb +97 -1
- data/lib/generators/propel_api/core/relationship_inferrer.rb +4 -11
- data/lib/generators/propel_api/install/install_generator.rb +39 -2
- data/lib/generators/propel_api/resource/resource_generator.rb +205 -63
- data/lib/generators/propel_api/templates/config/propel_api.rb.tt +26 -1
- data/lib/generators/propel_api/templates/controllers/api_base_controller.rb +75 -0
- data/lib/generators/propel_api/templates/controllers/api_controller_graphiti.rb +3 -3
- data/lib/generators/propel_api/templates/controllers/api_controller_propel_facets.rb +22 -12
- data/lib/generators/propel_api/templates/controllers/example_controller.rb.tt +2 -2
- data/lib/generators/propel_api/templates/errors/propel_api_csrf_error.rb +19 -0
- data/lib/generators/propel_api/templates/scaffold/facet_controller_template.rb.tt +41 -8
- data/lib/generators/propel_api/templates/scaffold/facet_model_template.rb.tt +43 -10
- data/lib/generators/propel_api/templates/scaffold/graphiti_controller_template.rb.tt +1 -1
- data/lib/generators/propel_api/templates/scaffold/graphiti_resource_template.rb.tt +2 -2
- data/lib/generators/propel_api/templates/seeds/seeds_template.rb.tt +65 -17
- data/lib/generators/propel_api/templates/tests/controller_test_template.rb.tt +40 -6
- data/lib/generators/propel_api/templates/tests/fixtures_template.yml.tt +61 -18
- data/lib/generators/propel_api/templates/tests/integration_test_template.rb.tt +154 -42
- data/lib/generators/propel_api/templates/tests/model_test_template.rb.tt +20 -0
- data/lib/propel_api.rb +1 -1
- metadata +24 -2
@@ -10,15 +10,48 @@ class <%= controller_class_name_with_namespace %>Controller < <%= api_controller
|
|
10
10
|
# Define which parameters are allowed for <%= class_name %> creation/updates
|
11
11
|
# This uses the StrongParamsHelper concern from the base controller
|
12
12
|
<% end -%>
|
13
|
-
permitted_params
|
14
|
-
#
|
15
|
-
|
16
|
-
|
17
|
-
|
13
|
+
permitted_params <%
|
14
|
+
# Generate permitted parameters using shared polymorphic support
|
15
|
+
regular_params = []
|
16
|
+
json_params = []
|
17
|
+
|
18
|
+
# Add attribute-based params using polymorphic support
|
19
|
+
attributes.each do |attr|
|
20
|
+
if attr.type == :json
|
21
|
+
json_params << "#{attr.name}: {}"
|
22
|
+
elsif attr.type == :references
|
23
|
+
# Use Rails' built-in polymorphic detection
|
24
|
+
if attr.respond_to?(:polymorphic?) && attr.polymorphic?
|
25
|
+
regular_params << ":#{attr.name}_id"
|
26
|
+
regular_params << ":#{attr.name}_type"
|
27
|
+
else
|
28
|
+
regular_params << ":#{attr.name}_id"
|
29
|
+
end
|
30
|
+
else
|
31
|
+
regular_params << ":#{attr.name}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Add multi-tenancy params unless skipped
|
36
|
+
if has_organization_reference?
|
37
|
+
regular_params += [":organization_id"]
|
38
|
+
end
|
39
|
+
|
40
|
+
if has_agency_reference?
|
41
|
+
regular_params += [":agency_id"]
|
42
|
+
end
|
43
|
+
|
44
|
+
# Remove organization/agency duplicates if they were user-specified
|
45
|
+
regular_params.uniq!
|
46
|
+
|
47
|
+
# Combine regular and JSON params properly
|
48
|
+
if json_params.any?
|
49
|
+
all_params = regular_params + json_params
|
18
50
|
else
|
19
|
-
|
51
|
+
all_params = regular_params
|
20
52
|
end
|
21
|
-
|
53
|
+
|
54
|
+
-%><%= all_params.join(', ') %>
|
22
55
|
|
23
56
|
<% if options[:with_comments] -%>
|
24
57
|
# Connect facets to actions - customize as needed for your <%= class_name.downcase %> model
|
@@ -84,7 +117,7 @@ class <%= controller_class_name_with_namespace %>Controller < <%= api_controller
|
|
84
117
|
# if @resource.save
|
85
118
|
# render json: { data: resource_json(@resource) }, status: :created
|
86
119
|
# else
|
87
|
-
# render json: { errors: @resource.errors }, status: :
|
120
|
+
# render json: { errors: @resource.errors }, status: :unprocessable_content
|
88
121
|
# end
|
89
122
|
# end
|
90
123
|
#
|
@@ -3,7 +3,7 @@
|
|
3
3
|
class <%= class_name %> < ApplicationRecord
|
4
4
|
|
5
5
|
# Validations
|
6
|
-
<%
|
6
|
+
<% if has_organization_reference? -%>
|
7
7
|
# Multi-tenancy: Organization is always required
|
8
8
|
validates :organization, presence: true
|
9
9
|
|
@@ -24,7 +24,7 @@ class <%= class_name %> < ApplicationRecord
|
|
24
24
|
<% if attribute.name.to_s.match?(/\A(phone|phone_number)\z/i) -%>
|
25
25
|
validates :<%= attribute.name %>, format: { with: /\A\+?[\d\s-\(\)]+\z/ }, allow_nil: <%= !attribute.required? %>
|
26
26
|
<% end -%>
|
27
|
-
<% if attribute.name.to_s.match?(/\A(url|website|web_address)\z/i) -%>
|
27
|
+
<% if attribute.name.to_s.match?(/\A(url|website|web_address|domain|domain_name)\z/i) -%>
|
28
28
|
validates :<%= attribute.name %>, format: { with: URI::DEFAULT_PARSER.make_regexp(%w[http https]) }, allow_nil: <%= !attribute.required? %>
|
29
29
|
<% end -%>
|
30
30
|
<% if attribute.name.to_s.match?(/\Alatitude\z/i) -%>
|
@@ -45,32 +45,40 @@ class <%= class_name %> < ApplicationRecord
|
|
45
45
|
<% end -%>
|
46
46
|
|
47
47
|
# Associations
|
48
|
-
<%
|
48
|
+
<% if has_organization_reference? -%>
|
49
49
|
# Multi-tenancy: Always include organization and agency for data isolation
|
50
50
|
# User/Agent associations are only added when explicitly specified as generator arguments
|
51
51
|
belongs_to :organization
|
52
|
+
<% end -%>
|
53
|
+
<% if has_agency_reference? -%>
|
52
54
|
belongs_to :agency
|
55
|
+
<% end -%>
|
56
|
+
<% if has_organization_reference? || has_agency_reference? -%>
|
53
57
|
|
54
58
|
<% end -%>
|
55
59
|
<% if defined?(relationship_inferrer) && relationship_inferrer.should_generate_associations? -%>
|
56
60
|
<% relationship_inferrer.belongs_to_relationships.each do |relationship| -%>
|
57
61
|
<% # Skip organization and agency if tenancy is included (they're already added above) -%>
|
58
|
-
<% unless
|
62
|
+
<% unless (has_organization_reference? && relationship.include?('organization')) || (has_agency_reference? && relationship.include?('agency')) -%>
|
59
63
|
<%= relationship %>
|
60
64
|
<% end -%>
|
61
65
|
<% end -%>
|
62
66
|
<% elsif !options[:skip_associations] -%>
|
63
67
|
<% attributes.select { |attr| attr.type == :references }.each do |attribute| -%>
|
64
68
|
<% # Skip organization and agency if tenancy is included (they're already added above) -%>
|
65
|
-
<% unless
|
69
|
+
<% unless (has_organization_reference? && attribute.name == 'organization') || (has_agency_reference? && attribute.name == 'agency') -%>
|
66
70
|
<% if attribute.name == 'organization' -%>
|
67
71
|
belongs_to :organization
|
72
|
+
<% else -%>
|
73
|
+
<% if attribute.respond_to?(:polymorphic?) && attribute.polymorphic? -%>
|
74
|
+
belongs_to :<%= attribute.name %>, polymorphic: true
|
68
75
|
<% else -%>
|
69
76
|
belongs_to :<%= attribute.name %> # Add 'optional: true' if this should be optional
|
70
77
|
<% end -%>
|
71
78
|
<% end -%>
|
72
79
|
<% end -%>
|
73
80
|
<% end -%>
|
81
|
+
<% end -%>
|
74
82
|
# Add additional associations here
|
75
83
|
# has_many :comments, dependent: :destroy
|
76
84
|
|
@@ -105,7 +113,13 @@ json_facet :short, fields: [:id<%
|
|
105
113
|
attr.name.to_s !~ security_patterns &&
|
106
114
|
attr.name.to_s.length < 20)
|
107
115
|
end
|
108
|
-
|
116
|
+
|
117
|
+
# Add polymorphic type fields to short facet (important for frontend handling)
|
118
|
+
polymorphic_type_fields = attributes.select { |attr| attr.name.end_with?('_type') && attr.type == :string }
|
119
|
+
# Also include polymorphic associations (which generate _type fields)
|
120
|
+
polymorphic_associations = attributes.select { |attr| attr.type == :references && attr.respond_to?(:polymorphic?) && attr.polymorphic? }
|
121
|
+
|
122
|
+
short_attributes.each do |attribute| -%>, :<%= attribute.name %><% end %><% polymorphic_type_fields.each do |type_field| -%>, :<%= type_field.name %><% end %><% polymorphic_associations.each do |poly_attr| -%>, :<%= poly_attr.name %>_type<% end %>]
|
109
123
|
json_facet :details, fields: [:id<%
|
110
124
|
# Include most fields except timestamps, security-sensitive, and internal Rails fields for details facet
|
111
125
|
detail_attributes = attributes.reject do |attr|
|
@@ -124,6 +138,11 @@ json_facet :details, fields: [:id<%
|
|
124
138
|
# Collect all field names, avoiding duplicates
|
125
139
|
all_fields = Set.new
|
126
140
|
|
141
|
+
# Find polymorphic type fields
|
142
|
+
polymorphic_type_fields = attributes.select { |attr| attr.name.end_with?('_type') && attr.type == :string }
|
143
|
+
# Also find polymorphic associations (which generate _type fields)
|
144
|
+
polymorphic_associations = attributes.select { |attr| attr.type == :references && attr.respond_to?(:polymorphic?) && attr.polymorphic? }
|
145
|
+
|
127
146
|
# Add non-reference attributes
|
128
147
|
detail_attributes.reject { |attr| attr.type == :references }.each do |attribute|
|
129
148
|
all_fields << attribute.name.to_sym
|
@@ -134,19 +153,33 @@ json_facet :details, fields: [:id<%
|
|
134
153
|
all_fields << "#{reference.name}_id".to_sym
|
135
154
|
end
|
136
155
|
|
137
|
-
# Add tenancy references if
|
138
|
-
|
156
|
+
# Add tenancy references if present
|
157
|
+
if has_organization_reference?
|
139
158
|
all_fields << :organization_id
|
159
|
+
end
|
160
|
+
if has_agency_reference?
|
140
161
|
all_fields << :agency_id
|
141
162
|
end
|
142
163
|
|
164
|
+
# Add polymorphic type fields to details facet (important for frontend handling)
|
165
|
+
polymorphic_type_fields.each do |type_field|
|
166
|
+
all_fields << type_field.name.to_sym
|
167
|
+
end
|
168
|
+
# Also add polymorphic association type fields
|
169
|
+
polymorphic_associations.each do |poly_attr|
|
170
|
+
all_fields << "#{poly_attr.name}_type".to_sym
|
171
|
+
end
|
172
|
+
|
143
173
|
# Output the unique fields
|
144
174
|
all_fields.to_a.sort.each do |field| -%>, :<%= field %><% end -%>]<%
|
145
175
|
|
146
176
|
# Generate include for nested objects (reference associations)
|
147
177
|
reference_associations = detail_attributes.select { |attr| attr.type == :references }.map(&:name)
|
148
|
-
|
149
|
-
reference_associations += %w[organization
|
178
|
+
if has_organization_reference?
|
179
|
+
reference_associations += %w[organization]
|
180
|
+
end
|
181
|
+
if has_agency_reference?
|
182
|
+
reference_associations += %w[agency]
|
150
183
|
end
|
151
184
|
reference_associations.uniq!
|
152
185
|
|
@@ -55,7 +55,7 @@ class <%= controller_class_name_with_namespace %>Controller < <%= api_controller
|
|
55
55
|
# if resource_instance.save
|
56
56
|
# respond_with(resource_instance, status: :created)
|
57
57
|
# else
|
58
|
-
# respond_with(resource_instance.errors, status: :
|
58
|
+
# respond_with(resource_instance.errors, status: :unprocessable_content)
|
59
59
|
# end
|
60
60
|
# end
|
61
61
|
#
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
<%
|
3
|
+
<% if has_organization_reference? || has_agency_reference? -%>
|
4
4
|
require_relative '../concerns/tenancy'
|
5
5
|
<% end -%>
|
6
6
|
|
@@ -20,7 +20,7 @@ require_relative '../concerns/tenancy'
|
|
20
20
|
# @see https://www.graphiti.dev/guides/concepts/resources
|
21
21
|
#
|
22
22
|
class <%= class_name %>Resource < ApplicationResource
|
23
|
-
<%
|
23
|
+
<% if has_organization_reference? || has_agency_reference? -%>
|
24
24
|
include Tenancy
|
25
25
|
<% end -%>
|
26
26
|
|
@@ -13,7 +13,7 @@ puts "📋 Loading fixture data for consistent test/dev experience..."
|
|
13
13
|
|
14
14
|
# Only load fixtures if not already in development environment with existing data
|
15
15
|
fixture_count = 0
|
16
|
-
<%
|
16
|
+
<% if has_organization_reference? || has_agency_reference? -%>
|
17
17
|
if Rails.env.development? && (Organization.count > 0 || User.count > 0)
|
18
18
|
<% else -%>
|
19
19
|
<% if attributes.any? { |attr| attr.type == :references && attr.name.to_s.match?(/user/) } -%>
|
@@ -31,8 +31,10 @@ else
|
|
31
31
|
begin
|
32
32
|
# Load all necessary fixtures together to handle dependencies
|
33
33
|
fixture_files = ['<%= table_name %>']
|
34
|
-
<%
|
34
|
+
<% if has_organization_reference? -%>
|
35
35
|
fixture_files << 'organizations'
|
36
|
+
<% end -%>
|
37
|
+
<% if has_agency_reference? -%>
|
36
38
|
fixture_files << 'agencies'
|
37
39
|
<% end -%>
|
38
40
|
<% if attributes.any? { |attr| attr.type == :references && attr.name.to_s.match?(/user/) } -%>
|
@@ -67,8 +69,10 @@ end
|
|
67
69
|
puts "🎨 Generating additional realistic <%= table_name %> with Faker..."
|
68
70
|
|
69
71
|
# Use existing organizations, agencies, and users (from fixtures + any additional ones)
|
70
|
-
<%
|
72
|
+
<% if has_organization_reference? -%>
|
71
73
|
organizations = Organization.all
|
74
|
+
<% end -%>
|
75
|
+
<% if has_agency_reference? -%>
|
72
76
|
agencies = Agency.all
|
73
77
|
|
74
78
|
# If we still don't have enough orgs, create some with Faker
|
@@ -102,7 +106,7 @@ users = User.all
|
|
102
106
|
|
103
107
|
if users.count < 6
|
104
108
|
puts "➕ Creating additional users..."
|
105
|
-
<%
|
109
|
+
<% if has_organization_reference? -%>
|
106
110
|
organizations.each do |org|
|
107
111
|
next if users.where(organization: org).count >= 2
|
108
112
|
|
@@ -122,7 +126,7 @@ if users.count < 6
|
|
122
126
|
if org_agencies.any?
|
123
127
|
# Assign to random agency within organization
|
124
128
|
agency = org_agencies.sample
|
125
|
-
Agent.create!(user: new_user, agency: agency, role: 'member')
|
129
|
+
Agent.create!(user: new_user, agency: agency, organization: org, role: 'member')
|
126
130
|
end
|
127
131
|
end
|
128
132
|
end
|
@@ -145,12 +149,14 @@ end
|
|
145
149
|
|
146
150
|
# Generate varied, realistic data based on the model attributes
|
147
151
|
30.times do |i|
|
148
|
-
<%
|
152
|
+
<% if has_organization_reference? -%>
|
149
153
|
organization = organizations.sample
|
154
|
+
<% end -%>
|
155
|
+
<% if has_agency_reference? -%>
|
150
156
|
agency = agencies.where(organization: organization).sample || agencies.sample
|
151
157
|
<% end -%>
|
152
158
|
<% if attributes.any? { |attr| attr.type == :references && attr.name.to_s.match?(/user/) } -%>
|
153
|
-
<%
|
159
|
+
<% if has_organization_reference? -%>
|
154
160
|
user = users.where(organization: organization).sample || users.sample
|
155
161
|
<% else -%>
|
156
162
|
user = users.sample
|
@@ -159,16 +165,32 @@ end
|
|
159
165
|
|
160
166
|
<%= singular_table_name %>_attributes = {}
|
161
167
|
|
162
|
-
<%
|
163
|
-
# Multi-tenancy:
|
168
|
+
<% if has_organization_reference? -%>
|
169
|
+
# Multi-tenancy: Assign organization
|
164
170
|
<%= singular_table_name %>_attributes[:organization] = organization
|
165
|
-
|
171
|
+
<% end -%>
|
172
|
+
<% if has_agency_reference? -%>
|
173
|
+
# Multi-tenancy: Assign agency
|
174
|
+
<%= singular_table_name %>_attributes[:agency] = agency # Always assign agency for valid records
|
175
|
+
<% end -%>
|
176
|
+
<% if has_organization_reference? || has_agency_reference? -%>
|
166
177
|
|
167
178
|
<% end -%>
|
179
|
+
# Handle polymorphic associations
|
180
|
+
<% polymorphic_associations.each do |poly_assoc| -%>
|
181
|
+
# Assign polymorphic association: <%= poly_assoc[:field_name] %>
|
182
|
+
available_parents = <%= poly_assoc[:parent_types].map { |type| "#{type}.limit(5).to_a" }.join(' + ') %>
|
183
|
+
if available_parents.any?
|
184
|
+
selected_parent = available_parents.sample
|
185
|
+
<%= singular_table_name %>_attributes[:<%= poly_assoc[:id_field] %>] = selected_parent.id
|
186
|
+
<%= singular_table_name %>_attributes[:<%= poly_assoc[:type_field] %>] = selected_parent.class.name
|
187
|
+
end
|
188
|
+
<% end -%>
|
189
|
+
|
168
190
|
# Assign additional reference attributes dynamically
|
169
|
-
<% attributes.select { |attr| attr.type == :references }.each do |reference| -%>
|
191
|
+
<% attributes.select { |attr| attr.type == :references && !(attr.respond_to?(:polymorphic?) && attr.polymorphic?) }.each do |reference| -%>
|
170
192
|
<% # Skip organization and agency if tenancy is included (they're already assigned above) -%>
|
171
|
-
<% unless
|
193
|
+
<% unless (has_organization_reference? && reference.name == 'organization') || (has_agency_reference? && reference.name == 'agency') -%>
|
172
194
|
<% if reference.name.to_s.match?(/user/) -%>
|
173
195
|
<%= singular_table_name %>_attributes[:<%= reference.name %>] = user
|
174
196
|
<% else -%>
|
@@ -189,7 +211,7 @@ end
|
|
189
211
|
<%= singular_table_name %>_attributes[:<%= attribute.name %>] = Faker::Internet.unique.username
|
190
212
|
<% elsif attribute.name.to_s.match?(/\A(phone|phone_number)\z/i) -%>
|
191
213
|
<%= singular_table_name %>_attributes[:<%= attribute.name %>] = Faker::PhoneNumber.phone_number
|
192
|
-
<% elsif attribute.name.to_s.match?(/\A(url|website|web_address)\z/i) -%>
|
214
|
+
<% elsif attribute.name.to_s.match?(/\A(url|website|web_address|domain|domain_name)\z/i) -%>
|
193
215
|
<%= singular_table_name %>_attributes[:<%= attribute.name %>] = Faker::Internet.url
|
194
216
|
<% elsif attribute.name.to_s.match?(/\A(slug)\z/i) -%>
|
195
217
|
<%= singular_table_name %>_attributes[:<%= attribute.name %>] = Faker::Internet.slug
|
@@ -312,9 +334,11 @@ puts "\n✅ Created #{<%= class_name %>.count} <%= table_name %>"
|
|
312
334
|
puts "🎯 Creating specific test scenarios..."
|
313
335
|
|
314
336
|
# Create some <%= table_name %> with edge case data
|
315
|
-
<%
|
337
|
+
<% if has_organization_reference? -%>
|
316
338
|
organizations.each do |org|
|
339
|
+
<% if has_agency_reference? -%>
|
317
340
|
agency = agencies.where(organization: org).sample
|
341
|
+
<% end -%>
|
318
342
|
<% if attributes.any? { |attr| attr.type == :references && attr.name.to_s.match?(/user/) } -%>
|
319
343
|
user = users.where(organization: org).sample
|
320
344
|
<% end -%>
|
@@ -326,8 +350,19 @@ organizations.each do |org|
|
|
326
350
|
minimal_<%= singular_table_name %>[:organization] = org
|
327
351
|
minimal_<%= singular_table_name %>[:agency] = agency
|
328
352
|
|
353
|
+
# Handle polymorphic associations
|
354
|
+
<% polymorphic_associations.each do |poly_assoc| -%>
|
355
|
+
# Assign polymorphic association: <%= poly_assoc[:field_name] %>
|
356
|
+
available_parents = <%= poly_assoc[:parent_types].map { |type| "#{type}.limit(5).to_a" }.join(' + ') %>
|
357
|
+
if available_parents.any?
|
358
|
+
selected_parent = available_parents.sample
|
359
|
+
minimal_<%= singular_table_name %>[:<%= poly_assoc[:id_field] %>] = selected_parent.id
|
360
|
+
minimal_<%= singular_table_name %>[:<%= poly_assoc[:type_field] %>] = selected_parent.class.name
|
361
|
+
end
|
362
|
+
<% end -%>
|
363
|
+
|
329
364
|
# Assign additional reference attributes dynamically
|
330
|
-
<% attributes.select { |attr| attr.type == :references }.each do |reference| -%>
|
365
|
+
<% attributes.select { |attr| attr.type == :references && !(attr.respond_to?(:polymorphic?) && attr.polymorphic?) }.each do |reference| -%>
|
331
366
|
<% # Skip organization and agency if tenancy is included (they're already assigned above) -%>
|
332
367
|
<% unless reference.name == 'organization' || reference.name == 'agency' -%>
|
333
368
|
<% if reference.name.to_s.match?(/user/) -%>
|
@@ -393,8 +428,19 @@ end
|
|
393
428
|
# Create a minimal <%= singular_table_name %> (only required fields)
|
394
429
|
minimal_<%= singular_table_name %> = {}
|
395
430
|
|
431
|
+
# Handle polymorphic associations
|
432
|
+
<% polymorphic_associations.each do |poly_assoc| -%>
|
433
|
+
# Assign polymorphic association: <%= poly_assoc[:field_name] %>
|
434
|
+
available_parents = <%= poly_assoc[:parent_types].map { |type| "#{type}.limit(5).to_a" }.join(' + ') %>
|
435
|
+
if available_parents.any?
|
436
|
+
selected_parent = available_parents.sample
|
437
|
+
minimal_<%= singular_table_name %>[:<%= poly_assoc[:id_field] %>] = selected_parent.id
|
438
|
+
minimal_<%= singular_table_name %>[:<%= poly_assoc[:type_field] %>] = selected_parent.class.name
|
439
|
+
end
|
440
|
+
<% end -%>
|
441
|
+
|
396
442
|
# Assign reference attributes dynamically
|
397
|
-
<% attributes.select { |attr| attr.type == :references }.each do |reference| -%>
|
443
|
+
<% attributes.select { |attr| attr.type == :references && !(attr.respond_to?(:polymorphic?) && attr.polymorphic?) }.each do |reference| -%>
|
398
444
|
<% if reference.name.to_s.match?(/user/) -%>
|
399
445
|
minimal_<%= singular_table_name %>[:<%= reference.name %>] = users.sample if users.any?
|
400
446
|
<% else -%>
|
@@ -466,8 +512,10 @@ puts "📊 Summary:"
|
|
466
512
|
puts " • Total <%= table_name %>: #{total_<%= table_name %>}"
|
467
513
|
puts " • └── Fixture <%= table_name %>: #{fixture_<%= table_name %>} (same as test data)"
|
468
514
|
puts " • └── Faker <%= table_name %>: #{faker_<%= table_name %>} (realistic dev data)"
|
469
|
-
<%
|
515
|
+
<% if has_organization_reference? -%>
|
470
516
|
puts " • Organizations: #{Organization.count}"
|
517
|
+
<% end -%>
|
518
|
+
<% if has_agency_reference? -%>
|
471
519
|
puts " • Agencies: #{Agency.count}"
|
472
520
|
puts " • <%= table_name %> per organization: #{(total_<%= table_name %>.to_f / Organization.count).round(1)} avg"
|
473
521
|
<% end -%>
|
@@ -8,6 +8,18 @@ class <%= controller_class_name_with_namespace %>ControllerTest < ActionDispatch
|
|
8
8
|
@organization = organizations(:acme_org)
|
9
9
|
@user = users(:john_user)
|
10
10
|
@agency = agencies(:marketing_agency)
|
11
|
+
<% # Set up polymorphic associations using --parents specification -%>
|
12
|
+
<% polymorphic_associations.each do |assoc| -%>
|
13
|
+
<% if assoc[:parent_types] && assoc[:parent_types].any? -%>
|
14
|
+
<% first_parent = assoc[:parent_types].first -%>
|
15
|
+
# Set up polymorphic association for <%= assoc[:field_name] %> using specified parents
|
16
|
+
@<%= first_parent.underscore %> = <%= first_parent.underscore.pluralize %>(<%= case first_parent.underscore; when 'agency'; ':marketing_agency'; when 'user'; ':john_user'; when 'organization'; ':acme_org'; else; ':one'; end %>)
|
17
|
+
@<%= assoc[:field_name] %> = @<%= first_parent.underscore %> # Use first specified parent type
|
18
|
+
<% assoc[:parent_types][1..-1].each do |parent_type| -%>
|
19
|
+
@<%= parent_type.underscore %> = <%= parent_type.underscore.pluralize %>(<%= case parent_type.underscore; when 'agency'; ':marketing_agency'; when 'user'; ':john_user'; when 'organization'; ':acme_org'; else; ':one'; end %>)
|
20
|
+
<% end -%>
|
21
|
+
<% end -%>
|
22
|
+
<% end -%>
|
11
23
|
<% if singular_table_name == 'organization' -%>
|
12
24
|
@<%= singular_table_name %> = <%= table_name %>(:acme_org)
|
13
25
|
<% elsif singular_table_name == 'user' -%>
|
@@ -21,7 +33,7 @@ class <%= controller_class_name_with_namespace %>ControllerTest < ActionDispatch
|
|
21
33
|
@auth_headers = { 'Authorization' => "Bearer #{@token}" }
|
22
34
|
|
23
35
|
# Ensure test <%= singular_table_name %> belongs to test user's organization
|
24
|
-
<%
|
36
|
+
<% if has_organization_reference? -%>
|
25
37
|
@<%= singular_table_name %>.update!(organization: @organization)
|
26
38
|
<% end -%>
|
27
39
|
end
|
@@ -98,6 +110,7 @@ class <%= controller_class_name_with_namespace %>ControllerTest < ActionDispatch
|
|
98
110
|
assert_includes pagination.keys, 'prev_page'
|
99
111
|
end
|
100
112
|
|
113
|
+
<% if has_organization_reference? -%>
|
101
114
|
test "should only show records for user's organization" do
|
102
115
|
# Create record for different organization
|
103
116
|
other_org = Organization.create!(name: "Other Organization")
|
@@ -148,6 +161,7 @@ class <%= controller_class_name_with_namespace %>ControllerTest < ActionDispatch
|
|
148
161
|
# Should include own organization's record
|
149
162
|
assert_includes <%= table_name %>_ids, @<%= singular_table_name %>.id
|
150
163
|
end
|
164
|
+
<% end -%>
|
151
165
|
|
152
166
|
# === SHOW TESTS ===
|
153
167
|
|
@@ -167,6 +181,7 @@ class <%= controller_class_name_with_namespace %>ControllerTest < ActionDispatch
|
|
167
181
|
assert_response :not_found
|
168
182
|
end
|
169
183
|
|
184
|
+
<% if has_organization_reference? -%>
|
170
185
|
test "should not show <%= singular_table_name %> from different organization" do
|
171
186
|
other_org = Organization.create!(name: "Other Organization")
|
172
187
|
other_<%= singular_table_name %> = <%= class_name %>.create!(
|
@@ -207,6 +222,7 @@ class <%= controller_class_name_with_namespace %>ControllerTest < ActionDispatch
|
|
207
222
|
get <%= api_singular_route_helper %>_url(other_<%= singular_table_name %>), headers: @auth_headers
|
208
223
|
assert_response :not_found
|
209
224
|
end
|
225
|
+
<% end -%>
|
210
226
|
|
211
227
|
# === CREATE TESTS ===
|
212
228
|
|
@@ -242,7 +258,7 @@ class <%= controller_class_name_with_namespace %>ControllerTest < ActionDispatch
|
|
242
258
|
created_<%= singular_table_name %> = response_body['data']
|
243
259
|
assert_not_nil created_<%= singular_table_name %>, "Response should contain <%= singular_table_name %> data"
|
244
260
|
|
245
|
-
<% if
|
261
|
+
<% if has_organization_reference? -%>
|
246
262
|
# Verify organization reference (handle both nested and flat formats)
|
247
263
|
if created_<%= singular_table_name %>['organization']
|
248
264
|
assert_equal @organization.id, created_<%= singular_table_name %>['organization']['id']
|
@@ -284,7 +300,7 @@ class <%= controller_class_name_with_namespace %>ControllerTest < ActionDispatch
|
|
284
300
|
headers: @auth_headers
|
285
301
|
end
|
286
302
|
|
287
|
-
assert_response :
|
303
|
+
assert_response :unprocessable_content
|
288
304
|
|
289
305
|
response_body = JSON.parse(response.body)
|
290
306
|
assert_includes response_body.keys, 'errors'
|
@@ -317,7 +333,9 @@ class <%= controller_class_name_with_namespace %>ControllerTest < ActionDispatch
|
|
317
333
|
-%>
|
318
334
|
|
319
335
|
params = valid_<%= singular_table_name %>_params
|
336
|
+
<% if has_organization_reference? -%>
|
320
337
|
params.delete(:organization_id) # Remove organization_id to test behavior
|
338
|
+
<% end -%>
|
321
339
|
<% if has_user_id -%>
|
322
340
|
params.delete(:user_id) # Remove user_id to test behavior
|
323
341
|
<% end -%>
|
@@ -333,15 +351,17 @@ class <%= controller_class_name_with_namespace %>ControllerTest < ActionDispatch
|
|
333
351
|
headers: @auth_headers
|
334
352
|
end
|
335
353
|
|
336
|
-
assert_response :
|
354
|
+
assert_response :unprocessable_content
|
337
355
|
|
338
356
|
# Should return validation errors for missing tenancy
|
339
357
|
error_response = JSON.parse(response.body)
|
340
358
|
|
359
|
+
<% if has_organization_reference? -%>
|
341
360
|
if require_org_id
|
342
361
|
assert_includes error_response['errors'].keys, 'organization_id',
|
343
362
|
"Should require organization_id when require_organization_id = true"
|
344
363
|
end
|
364
|
+
<% end -%>
|
345
365
|
<% if has_user_id -%>
|
346
366
|
if require_user_id
|
347
367
|
assert_includes error_response['errors'].keys, 'user_id',
|
@@ -367,7 +387,7 @@ class <%= controller_class_name_with_namespace %>ControllerTest < ActionDispatch
|
|
367
387
|
created_response = JSON.parse(response.body)
|
368
388
|
created_<%= singular_table_name %> = created_response['data']
|
369
389
|
|
370
|
-
<%
|
390
|
+
<% if has_organization_reference? -%>
|
371
391
|
# Verify organization_id was auto-assigned
|
372
392
|
if created_<%= singular_table_name %>['organization']
|
373
393
|
assert_equal @user.organization.id, created_<%= singular_table_name %>['organization']['id'],
|
@@ -423,12 +443,13 @@ class <%= controller_class_name_with_namespace %>ControllerTest < ActionDispatch
|
|
423
443
|
params: { data: invalid_<%= singular_table_name %>_params },
|
424
444
|
headers: @auth_headers
|
425
445
|
|
426
|
-
assert_response :
|
446
|
+
assert_response :unprocessable_content
|
427
447
|
|
428
448
|
response_body = JSON.parse(response.body)
|
429
449
|
assert_includes response_body.keys, 'errors'
|
430
450
|
end
|
431
451
|
|
452
|
+
<% if has_organization_reference? -%>
|
432
453
|
test "should not update <%= singular_table_name %> from different organization" do
|
433
454
|
other_org = Organization.create!(name: "Other Organization")
|
434
455
|
other_<%= singular_table_name %> = <%= class_name %>.create!(
|
@@ -483,6 +504,9 @@ class <%= controller_class_name_with_namespace %>ControllerTest < ActionDispatch
|
|
483
504
|
assert_response :no_content
|
484
505
|
end
|
485
506
|
|
507
|
+
<% end -%>
|
508
|
+
|
509
|
+
<% if has_organization_reference? -%>
|
486
510
|
test "should not destroy <%= singular_table_name %> from different organization" do
|
487
511
|
other_org = Organization.create!(name: "Other Organization")
|
488
512
|
other_<%= singular_table_name %> = <%= class_name %>.create!(
|
@@ -526,6 +550,7 @@ class <%= controller_class_name_with_namespace %>ControllerTest < ActionDispatch
|
|
526
550
|
|
527
551
|
assert_response :not_found
|
528
552
|
end
|
553
|
+
<% end -%>
|
529
554
|
|
530
555
|
# === PARAMETER HANDLING TESTS ===
|
531
556
|
|
@@ -664,12 +689,21 @@ class <%= controller_class_name_with_namespace %>ControllerTest < ActionDispatch
|
|
664
689
|
|
665
690
|
<% attributes.each_with_index do |attribute, index| -%>
|
666
691
|
<% if attribute.type == :references -%>
|
692
|
+
<% if attribute.respond_to?(:polymorphic?) && attribute.polymorphic? -%>
|
693
|
+
<%= attribute.name %>_id: @<%= attribute.name %>.id,
|
694
|
+
<%= attribute.name %>_type: @<%= attribute.name %>.class.name<%= ',' if index < attributes.length - 1 %>
|
695
|
+
<% else -%>
|
667
696
|
<%= attribute.name %>_id: @<%= attribute.name %>.id<%= ',' if index < attributes.length - 1 %>
|
697
|
+
<% end -%>
|
668
698
|
<% elsif attribute.type == :string && !reference_names.include?(attribute.name) -%>
|
669
699
|
<% if attribute.name.to_s.match?(/email/) -%>
|
670
700
|
<%= attribute.name %>: "test@example.com"<%= ',' if index < attributes.length - 1 %>
|
671
701
|
<% elsif attribute.name.to_s.match?(/password/) -%>
|
672
702
|
<%= attribute.name %>: "password123"<%= ',' if index < attributes.length - 1 %>
|
703
|
+
<% elsif attribute.name.to_s.match?(/\A(url|website|web_address|domain|domain_name)\z/i) -%>
|
704
|
+
<%= attribute.name %>: "https://example.com"<%= ',' if index < attributes.length - 1 %>
|
705
|
+
<% elsif attribute.name.to_s.end_with?('_type') -%>
|
706
|
+
<%= attribute.name %>: @<%= attribute.name.gsub('_type', '') %>.class.name<%= ',' if index < attributes.length - 1 %>
|
673
707
|
<% else -%>
|
674
708
|
<%= attribute.name %>: "Test <%= attribute.name.humanize %>"<%= ',' if index < attributes.length - 1 %>
|
675
709
|
<% end -%>
|