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,493 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Seeds for <%= class_name %> model
4
+ # This file generates realistic test data for development and testing
5
+ # First loads fixture data (same as tests use), then adds rich Faker data
6
+
7
+ require 'faker'
8
+
9
+ puts "๐ŸŒฑ Seeding <%= table_name %>..."
10
+
11
+ # Load fixture data first to ensure tests and development have the same baseline
12
+ puts "๐Ÿ“‹ Loading fixture data for consistent test/dev experience..."
13
+
14
+ # Only load fixtures if not already in development environment with existing data
15
+ fixture_count = 0
16
+ <% unless options[:skip_tenancy] -%>
17
+ if Rails.env.development? && (Organization.count > 0 || User.count > 0)
18
+ <% else -%>
19
+ <% if attributes.any? { |attr| attr.type == :references && attr.name.to_s.match?(/user/) } -%>
20
+ if Rails.env.development? && User.count > 0
21
+ <% else -%>
22
+ if Rails.env.development? && <%= class_name %>.count > 0
23
+ <% end -%>
24
+ <% end -%>
25
+ puts "โ„น๏ธ Development environment already has data, skipping fixture loading"
26
+ puts "๐Ÿ’ก Fixtures are automatically loaded during tests - they'll be available there"
27
+ puts "๐Ÿ’ก To use fixture data in development: rails db:reset && rails db:seed"
28
+ else
29
+ # Load fixtures in clean environment
30
+ puts "๐Ÿ“ฅ Loading fixtures for clean test/dev baseline..."
31
+ begin
32
+ # Load all necessary fixtures together to handle dependencies
33
+ fixture_files = ['<%= table_name %>']
34
+ <% unless options[:skip_tenancy] -%>
35
+ fixture_files << 'organizations'
36
+ fixture_files << 'agencies'
37
+ <% end -%>
38
+ <% if attributes.any? { |attr| attr.type == :references && attr.name.to_s.match?(/user/) } -%>
39
+ fixture_files << 'users'
40
+ <% end -%>
41
+
42
+ ActiveRecord::FixtureSet.create_fixtures(
43
+ Rails.root.join('test/fixtures'),
44
+ fixture_files
45
+ )
46
+ fixture_count = <%= class_name %>.count
47
+ puts "โœ… Loaded #{fixture_count} fixture <%= table_name %> (same as test data)"
48
+ rescue => e
49
+ puts "โš ๏ธ Could not load fixtures: #{e.message}"
50
+ puts "๐Ÿ’ก This is normal if database already has data or constraints conflict"
51
+ fixture_count = 0
52
+ end
53
+ end
54
+
55
+ # Clear existing <%= table_name %> generated by this script to avoid duplicates
56
+ puts "๐Ÿงน Clearing any existing generated <%= table_name %>..."
57
+ # Only destroy <%= table_name %> that were created by Faker (preserve fixtures)
58
+ if fixture_count > 0
59
+ # Keep fixture data, only remove Faker-generated records
60
+ <%= class_name %>.where.not(id: <%= class_name %>.limit(fixture_count).pluck(:id)).destroy_all
61
+ else
62
+ # No fixtures loaded, safe to clear all
63
+ <%= class_name %>.destroy_all
64
+ end
65
+
66
+ # Now add rich, realistic Faker data for development experience
67
+ puts "๐ŸŽจ Generating additional realistic <%= table_name %> with Faker..."
68
+
69
+ # Use existing organizations, agencies, and users (from fixtures + any additional ones)
70
+ <% unless options[:skip_tenancy] -%>
71
+ organizations = Organization.all
72
+ agencies = Agency.all
73
+
74
+ # If we still don't have enough orgs, create some with Faker
75
+ if organizations.count < 3
76
+ puts "โž• Creating additional organizations..."
77
+ (3 - organizations.count).times do
78
+ Organization.create!(name: Faker::Company.name)
79
+ end
80
+ organizations = Organization.all
81
+ end
82
+
83
+ # If we still don't have enough agencies, create some with Faker
84
+ if agencies.count < 5
85
+ puts "โž• Creating additional agencies..."
86
+ organizations.each do |org|
87
+ next if agencies.where(organization: org).count >= 2
88
+
89
+ (2 - agencies.where(organization: org).count).times do
90
+ Agency.create!(
91
+ name: Faker::Company.name + " #{['Department', 'Division', 'Unit', 'Team'].sample}",
92
+ organization: org
93
+ )
94
+ end
95
+ end
96
+ agencies = Agency.all
97
+ end
98
+ <% end -%>
99
+
100
+ <% if attributes.any? { |attr| attr.type == :references && attr.name.to_s.match?(/user/) } -%>
101
+ users = User.all
102
+
103
+ if users.count < 6
104
+ puts "โž• Creating additional users..."
105
+ <% unless options[:skip_tenancy] -%>
106
+ organizations.each do |org|
107
+ next if users.where(organization: org).count >= 2
108
+
109
+ (2 - users.where(organization: org).count).times do
110
+ User.create!(
111
+ email_address: Faker::Internet.unique.email,
112
+ username: Faker::Internet.unique.username,
113
+ first_name: Faker::Name.first_name,
114
+ last_name: Faker::Name.last_name,
115
+ password: "password123",
116
+ organization: org
117
+ )
118
+ end
119
+ end
120
+ <% else -%>
121
+ 6.times do
122
+ User.create!(
123
+ email_address: Faker::Internet.unique.email,
124
+ username: Faker::Internet.unique.username,
125
+ first_name: Faker::Name.first_name,
126
+ last_name: Faker::Name.last_name,
127
+ password: "password123"
128
+ )
129
+ end
130
+ <% end -%>
131
+ users = User.all
132
+ end
133
+ <% end -%>
134
+
135
+ <%= table_name %>_data = []
136
+
137
+ # Generate varied, realistic data based on the model attributes
138
+ 30.times do |i|
139
+ <% unless options[:skip_tenancy] -%>
140
+ organization = organizations.sample
141
+ agency = agencies.where(organization: organization).sample || agencies.sample
142
+ <% end -%>
143
+ <% if attributes.any? { |attr| attr.type == :references && attr.name.to_s.match?(/user/) } -%>
144
+ <% unless options[:skip_tenancy] -%>
145
+ user = users.where(organization: organization).sample || users.sample
146
+ <% else -%>
147
+ user = users.sample
148
+ <% end -%>
149
+ <% end -%>
150
+
151
+ <%= singular_table_name %>_attributes = {}
152
+
153
+ <% unless options[:skip_tenancy] -%>
154
+ # Multi-tenancy: Always assign organization (required) and agency (optional)
155
+ <%= singular_table_name %>_attributes[:organization] = organization
156
+ <%= singular_table_name %>_attributes[:agency] = [agency, nil].sample # 50% chance of having agency
157
+
158
+ <% end -%>
159
+ # Assign additional reference attributes dynamically
160
+ <% attributes.select { |attr| attr.type == :references }.each do |reference| -%>
161
+ <% # Skip organization and agency if tenancy is included (they're already assigned above) -%>
162
+ <% unless !options[:skip_tenancy] && (reference.name == 'organization' || reference.name == 'agency') -%>
163
+ <% if reference.name.to_s.match?(/user/) -%>
164
+ <%= singular_table_name %>_attributes[:<%= reference.name %>] = user
165
+ <% else -%>
166
+ # Handle other reference types - you may need to customize this
167
+ # <%= singular_table_name %>_attributes[:<%= reference.name %>] = # TODO: Assign appropriate value
168
+ <% end -%>
169
+ <% end -%>
170
+ <% end -%>
171
+
172
+ # Generate realistic data for each attribute
173
+ <% attributes.reject { |attr| attr.type == :references }.each do |attribute| -%>
174
+ <% if attribute.type == :string -%>
175
+ <% if attribute.name.to_s.match?(/\A(name|title|label)\z/i) -%>
176
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = Faker::Lorem.words(number: rand(2..4)).map(&:capitalize).join(' ')
177
+ <% elsif attribute.name.to_s.match?(/\A(email|email_address)\z/i) -%>
178
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = Faker::Internet.unique.email
179
+ <% elsif attribute.name.to_s.match?(/\A(username)\z/i) -%>
180
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = Faker::Internet.unique.username
181
+ <% elsif attribute.name.to_s.match?(/\A(phone|phone_number)\z/i) -%>
182
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = Faker::PhoneNumber.phone_number
183
+ <% elsif attribute.name.to_s.match?(/\A(url|website|web_address)\z/i) -%>
184
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = Faker::Internet.url
185
+ <% elsif attribute.name.to_s.match?(/\A(slug)\z/i) -%>
186
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = Faker::Internet.slug
187
+ <% elsif attribute.name.to_s.match?(/\A(status|state)\z/i) -%>
188
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = ['active', 'inactive', 'pending', 'archived', 'draft'].sample
189
+ <% elsif attribute.name.to_s.match?(/\A(priority)\z/i) -%>
190
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = ['low', 'medium', 'high', 'urgent'].sample
191
+ <% elsif attribute.name.to_s.match?(/\A(category|type)\z/i) -%>
192
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = Faker::Lorem.word.capitalize
193
+ <% elsif attribute.name.to_s.match?(/\A(code|sku|identifier)\z/i) -%>
194
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = Faker::Alphanumeric.alphanumeric(number: 8).upcase
195
+ <% elsif attribute.name.to_s.match?(/\A(color|colour)\z/i) -%>
196
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = Faker::Color.color_name
197
+ <% elsif attribute.name.to_s.match?(/\A(address)\z/i) -%>
198
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = Faker::Address.street_address
199
+ <% elsif attribute.name.to_s.match?(/\A(city)\z/i) -%>
200
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = Faker::Address.city
201
+ <% elsif attribute.name.to_s.match?(/\A(country)\z/i) -%>
202
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = Faker::Address.country
203
+ <% elsif attribute.name.to_s.match?(/\A(currency)\z/i) -%>
204
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = Faker::Currency.code
205
+ <% else -%>
206
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = Faker::Lorem.words(number: rand(1..3)).join(' ').titleize
207
+ <% end -%>
208
+ <% elsif attribute.type == :text -%>
209
+ <% if attribute.name.to_s.match?(/\A(description|summary|notes)\z/i) -%>
210
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = Faker::Lorem.paragraphs(number: rand(1..3)).join("\n\n")
211
+ <% elsif attribute.name.to_s.match?(/\A(content|body)\z/i) -%>
212
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = Faker::Lorem.paragraphs(number: rand(3..6)).join("\n\n")
213
+ <% elsif attribute.name.to_s.match?(/\A(bio|about)\z/i) -%>
214
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = Faker::Lorem.paragraph(sentence_count: rand(3..8))
215
+ <% elsif attribute.name.to_s.match?(/\A(comment|review)\z/i) -%>
216
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = Faker::Lorem.paragraph(sentence_count: rand(2..4))
217
+ <% else -%>
218
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = Faker::Lorem.paragraphs(number: rand(2..4)).join("\n\n")
219
+ <% end -%>
220
+ <% elsif attribute.type == :integer -%>
221
+ <% if attribute.name.to_s.match?(/\A(age)\z/i) -%>
222
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = rand(18..80)
223
+ <% elsif attribute.name.to_s.match?(/\A(count|quantity|amount|number)\z/i) -%>
224
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = rand(1..100)
225
+ <% elsif attribute.name.to_s.match?(/\A(priority|order|position|rank)\z/i) -%>
226
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = rand(1..10)
227
+ <% elsif attribute.name.to_s.match?(/\A(year)\z/i) -%>
228
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = rand(2020..2025)
229
+ <% elsif attribute.name.to_s.match?(/\A(month)\z/i) -%>
230
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = rand(1..12)
231
+ <% elsif attribute.name.to_s.match?(/\A(day)\z/i) -%>
232
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = rand(1..28)
233
+ <% elsif attribute.name.to_s.match?(/\A(rating|score)\z/i) -%>
234
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = rand(1..5)
235
+ <% elsif attribute.name.to_s.match?(/\A(views|visits|clicks)\z/i) -%>
236
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = rand(0..10000)
237
+ <% else -%>
238
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = rand(1..1000)
239
+ <% end -%>
240
+ <% elsif attribute.type == :decimal || attribute.type == :float -%>
241
+ <% if attribute.name.to_s.match?(/\A(price|cost|amount|total|fee)\z/i) -%>
242
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = Faker::Commerce.price(range: 0.99..999.99)
243
+ <% elsif attribute.name.to_s.match?(/\A(latitude)\z/i) -%>
244
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = Faker::Address.latitude
245
+ <% elsif attribute.name.to_s.match?(/\A(longitude)\z/i) -%>
246
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = Faker::Address.longitude
247
+ <% elsif attribute.name.to_s.match?(/\A(rating|score)\z/i) -%>
248
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = rand(1.0..5.0).round(1)
249
+ <% elsif attribute.name.to_s.match?(/\A(percentage|percent)\z/i) -%>
250
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = rand(0.0..100.0).round(2)
251
+ <% elsif attribute.name.to_s.match?(/\A(weight)\z/i) -%>
252
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = rand(0.1..100.0).round(2)
253
+ <% elsif attribute.name.to_s.match?(/\A(height|width|length|depth)\z/i) -%>
254
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = rand(1.0..500.0).round(2)
255
+ <% else -%>
256
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = rand(1.0..100.0).round(2)
257
+ <% end -%>
258
+ <% elsif attribute.type == :boolean -%>
259
+ <% if attribute.name.to_s.match?(/\A(active|enabled|published|visible|confirmed|verified|featured)\z/i) -%>
260
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = [true, true, true, false].sample # 75% true
261
+ <% elsif attribute.name.to_s.match?(/\A(archived|deleted|disabled|hidden|locked|suspended)\z/i) -%>
262
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = [false, false, false, true].sample # 25% true
263
+ <% else -%>
264
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = [true, false].sample
265
+ <% end -%>
266
+ <% elsif attribute.type == :date -%>
267
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = Faker::Date.between(from: 2.years.ago, to: 1.year.from_now)
268
+ <% elsif attribute.type == :datetime || attribute.type == :timestamp -%>
269
+ <% if attribute.name.to_s.match?(/\A(published_at|released_at|launched_at)\z/i) -%>
270
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = Faker::Time.between(from: 6.months.ago, to: Time.current)
271
+ <% elsif attribute.name.to_s.match?(/\A(expires_at|ends_at|deadline)\z/i) -%>
272
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = Faker::Time.between(from: Time.current, to: 1.year.from_now)
273
+ <% else -%>
274
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = Faker::Time.between(from: 1.year.ago, to: Time.current)
275
+ <% end -%>
276
+ <% elsif attribute.type == :time -%>
277
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = Faker::Time.between(from: Date.current.beginning_of_day, to: Date.current.end_of_day).strftime("%H:%M:%S")
278
+ <% else -%>
279
+ <%= singular_table_name %>_attributes[:<%= attribute.name %>] = Faker::Lorem.word
280
+ <% end -%>
281
+ <% end -%>
282
+
283
+ <%= table_name %>_data << <%= singular_table_name %>_attributes
284
+ end
285
+
286
+ # Create <%= table_name %> in batches for better performance
287
+ <%= table_name %>_data.each_slice(10) do |batch|
288
+ batch.each do |attributes|
289
+ begin
290
+ <%= class_name %>.create!(attributes)
291
+ rescue ActiveRecord::RecordInvalid => e
292
+ puts "โš ๏ธ Skipped invalid <%= singular_table_name %>: #{e.message}"
293
+ rescue => e
294
+ puts "โŒ Error creating <%= singular_table_name %>: #{e.message}"
295
+ end
296
+ end
297
+ print "."
298
+ end
299
+
300
+ puts "\nโœ… Created #{<%= class_name %>.count} <%= table_name %>"
301
+
302
+ # Create some specific test scenarios for better test coverage
303
+ puts "๐ŸŽฏ Creating specific test scenarios..."
304
+
305
+ # Create some <%= table_name %> with edge case data
306
+ <% unless options[:skip_tenancy] -%>
307
+ organizations.each do |org|
308
+ agency = agencies.where(organization: org).sample
309
+ <% if attributes.any? { |attr| attr.type == :references && attr.name.to_s.match?(/user/) } -%>
310
+ user = users.where(organization: org).sample
311
+ <% end -%>
312
+
313
+ # Create a minimal <%= singular_table_name %> (only required fields)
314
+ minimal_<%= singular_table_name %> = {}
315
+
316
+ # Multi-tenancy: Always assign organization (required) and agency (optional)
317
+ minimal_<%= singular_table_name %>[:organization] = org
318
+ minimal_<%= singular_table_name %>[:agency] = agency
319
+
320
+ # Assign additional reference attributes dynamically
321
+ <% attributes.select { |attr| attr.type == :references }.each do |reference| -%>
322
+ <% # Skip organization and agency if tenancy is included (they're already assigned above) -%>
323
+ <% unless reference.name == 'organization' || reference.name == 'agency' -%>
324
+ <% if reference.name.to_s.match?(/user/) -%>
325
+ minimal_<%= singular_table_name %>[:<%= reference.name %>] = user
326
+ <% else -%>
327
+ # Handle other reference types as needed
328
+ # minimal_<%= singular_table_name %>[:<%= reference.name %>] = # TODO: Assign appropriate value
329
+ <% end -%>
330
+ <% end -%>
331
+ <% end -%>
332
+
333
+ <% attributes.reject { |attr| attr.type == :references }.each do |attribute| -%>
334
+ <% if attribute.required? -%>
335
+ <% if attribute.type == :string -%>
336
+ minimal_<%= singular_table_name %>[:<%= attribute.name %>] = "Minimal <%= attribute.name.humanize %>"
337
+ <% elsif attribute.type == :text -%>
338
+ minimal_<%= singular_table_name %>[:<%= attribute.name %>] = "Minimal <%= attribute.name.humanize %> content"
339
+ <% elsif attribute.type == :integer -%>
340
+ minimal_<%= singular_table_name %>[:<%= attribute.name %>] = 1
341
+ <% elsif attribute.type == :decimal || attribute.type == :float -%>
342
+ minimal_<%= singular_table_name %>[:<%= attribute.name %>] = 1.0
343
+ <% elsif attribute.type == :boolean -%>
344
+ minimal_<%= singular_table_name %>[:<%= attribute.name %>] = true
345
+ <% elsif attribute.type == :date -%>
346
+ minimal_<%= singular_table_name %>[:<%= attribute.name %>] = Date.current
347
+ <% elsif attribute.type == :datetime || attribute.type == :timestamp -%>
348
+ minimal_<%= singular_table_name %>[:<%= attribute.name %>] = Time.current
349
+ <% elsif attribute.type == :time -%>
350
+ minimal_<%= singular_table_name %>[:<%= attribute.name %>] = Time.current.strftime("%H:%M:%S")
351
+ <% else -%>
352
+ minimal_<%= singular_table_name %>[:<%= attribute.name %>] = "minimal"
353
+ <% end -%>
354
+ <% end -%>
355
+ <% end -%>
356
+
357
+ begin
358
+ <%= class_name %>.create!(minimal_<%= singular_table_name %>)
359
+ rescue ActiveRecord::RecordInvalid => e
360
+ puts "โš ๏ธ Skipped minimal <%= singular_table_name %>: #{e.message}"
361
+ end
362
+
363
+ <% if attributes.any? { |attr| attr.name.to_s.match?(/\A(active|enabled|published|visible|status|state)\z/i) } -%>
364
+ # Create <%= table_name %> with different states for testing filters
365
+ <% attributes.each do |attribute| -%>
366
+ <% if attribute.name.to_s.match?(/\A(status|state)\z/i) && attribute.type == :string -%>
367
+ %w[active inactive pending archived draft].each do |status|
368
+ status_<%= singular_table_name %> = minimal_<%= singular_table_name %>.dup
369
+ status_<%= singular_table_name %>[:<%= attribute.name %>] = status
370
+ <%= class_name %>.create!(status_<%= singular_table_name %>) rescue nil
371
+ end
372
+ <% elsif attribute.name.to_s.match?(/\A(active|enabled|published|visible)\z/i) && attribute.type == :boolean -%>
373
+ [true, false].each do |state|
374
+ state_<%= singular_table_name %> = minimal_<%= singular_table_name %>.dup
375
+ state_<%= singular_table_name %>[:<%= attribute.name %>] = state
376
+ <%= class_name %>.create!(state_<%= singular_table_name %>) rescue nil
377
+ end
378
+ <% end -%>
379
+ <% end -%>
380
+ <% end -%>
381
+ end
382
+ <% else -%>
383
+ 3.times do
384
+ # Create a minimal <%= singular_table_name %> (only required fields)
385
+ minimal_<%= singular_table_name %> = {}
386
+
387
+ # Assign reference attributes dynamically
388
+ <% attributes.select { |attr| attr.type == :references }.each do |reference| -%>
389
+ <% if reference.name.to_s.match?(/user/) -%>
390
+ minimal_<%= singular_table_name %>[:<%= reference.name %>] = users.sample if users.any?
391
+ <% else -%>
392
+ # Handle other reference types as needed
393
+ # minimal_<%= singular_table_name %>[:<%= reference.name %>] = # TODO: Assign appropriate value
394
+ <% end -%>
395
+ <% end -%>
396
+
397
+ <% attributes.reject { |attr| attr.type == :references }.each do |attribute| -%>
398
+ <% if attribute.required? -%>
399
+ <% if attribute.type == :string -%>
400
+ minimal_<%= singular_table_name %>[:<%= attribute.name %>] = "Minimal <%= attribute.name.humanize %>"
401
+ <% elsif attribute.type == :text -%>
402
+ minimal_<%= singular_table_name %>[:<%= attribute.name %>] = "Minimal <%= attribute.name.humanize %> content"
403
+ <% elsif attribute.type == :integer -%>
404
+ minimal_<%= singular_table_name %>[:<%= attribute.name %>] = 1
405
+ <% elsif attribute.type == :decimal || attribute.type == :float -%>
406
+ minimal_<%= singular_table_name %>[:<%= attribute.name %>] = 1.0
407
+ <% elsif attribute.type == :boolean -%>
408
+ minimal_<%= singular_table_name %>[:<%= attribute.name %>] = true
409
+ <% elsif attribute.type == :date -%>
410
+ minimal_<%= singular_table_name %>[:<%= attribute.name %>] = Date.current
411
+ <% elsif attribute.type == :datetime || attribute.type == :timestamp -%>
412
+ minimal_<%= singular_table_name %>[:<%= attribute.name %>] = Time.current
413
+ <% elsif attribute.type == :time -%>
414
+ minimal_<%= singular_table_name %>[:<%= attribute.name %>] = Time.current.strftime("%H:%M:%S")
415
+ <% else -%>
416
+ minimal_<%= singular_table_name %>[:<%= attribute.name %>] = "minimal"
417
+ <% end -%>
418
+ <% end -%>
419
+ <% end -%>
420
+
421
+ begin
422
+ <%= class_name %>.create!(minimal_<%= singular_table_name %>)
423
+ rescue ActiveRecord::RecordInvalid => e
424
+ puts "โš ๏ธ Skipped minimal <%= singular_table_name %>: #{e.message}"
425
+ end
426
+
427
+ <% if attributes.any? { |attr| attr.name.to_s.match?(/\A(active|enabled|published|visible|status|state)\z/i) } -%>
428
+ # Create <%= table_name %> with different states for testing filters
429
+ <% attributes.each do |attribute| -%>
430
+ <% if attribute.name.to_s.match?(/\A(status|state)\z/i) && attribute.type == :string -%>
431
+ %w[active inactive pending archived draft].each do |status|
432
+ status_<%= singular_table_name %> = minimal_<%= singular_table_name %>.dup
433
+ status_<%= singular_table_name %>[:<%= attribute.name %>] = status
434
+ <%= class_name %>.create!(status_<%= singular_table_name %>) rescue nil
435
+ end
436
+ <% elsif attribute.name.to_s.match?(/\A(active|enabled|published|visible)\z/i) && attribute.type == :boolean -%>
437
+ [true, false].each do |state|
438
+ state_<%= singular_table_name %> = minimal_<%= singular_table_name %>.dup
439
+ state_<%= singular_table_name %>[:<%= attribute.name %>] = state
440
+ <%= class_name %>.create!(state_<%= singular_table_name %>) rescue nil
441
+ end
442
+ <% end -%>
443
+ <% end -%>
444
+ <% end -%>
445
+ end
446
+ <% end -%>
447
+
448
+ puts "โœ… Created test scenario <%= table_name %>"
449
+
450
+ # Summary
451
+ total_<%= table_name %> = <%= class_name %>.count
452
+ fixture_<%= table_name %> = fixture_count || 0
453
+ faker_<%= table_name %> = total_<%= table_name %> - fixture_<%= table_name %>
454
+
455
+ puts "\n๐ŸŽ‰ Seeding complete!"
456
+ puts "๐Ÿ“Š Summary:"
457
+ puts " โ€ข Total <%= table_name %>: #{total_<%= table_name %>}"
458
+ puts " โ€ข โ””โ”€โ”€ Fixture <%= table_name %>: #{fixture_<%= table_name %>} (same as test data)"
459
+ puts " โ€ข โ””โ”€โ”€ Faker <%= table_name %>: #{faker_<%= table_name %>} (realistic dev data)"
460
+ <% unless options[:skip_tenancy] -%>
461
+ puts " โ€ข Organizations: #{Organization.count}"
462
+ puts " โ€ข Agencies: #{Agency.count}"
463
+ puts " โ€ข <%= table_name %> per organization: #{(total_<%= table_name %>.to_f / Organization.count).round(1)} avg"
464
+ <% end -%>
465
+ <% if attributes.any? { |attr| attr.type == :references && attr.name.to_s.match?(/user/) } -%>
466
+ puts " โ€ข Users: #{User.count}"
467
+ <% end -%>
468
+
469
+ puts "\n๐Ÿ’ก Development Benefits:"
470
+ puts " โ€ข Tests and development use the same baseline fixture data"
471
+ puts " โ€ข Rich Faker data provides realistic content for evaluation"
472
+ puts " โ€ข Fixture data is predictable and referenceable in tests"
473
+
474
+ <% if attributes.any? { |attr| attr.name.to_s.match?(/\A(status|state|active|enabled|published|visible)\z/i) } -%>
475
+ # Show distribution for better understanding
476
+ <% attributes.each do |attribute| -%>
477
+ <% if attribute.name.to_s.match?(/\A(status|state)\z/i) && attribute.type == :string -%>
478
+ puts "\n๐Ÿ“ˆ <%= attribute.name.humanize %> distribution:"
479
+ <%= class_name %>.group(:<%= attribute.name %>).count.each do |status, count|
480
+ percentage = (count.to_f / total_<%= table_name %> * 100).round(1)
481
+ puts " โ€ข #{status}: #{count} (#{percentage}%)"
482
+ end
483
+ <% elsif attribute.name.to_s.match?(/\A(active|enabled|published|visible)\z/i) && attribute.type == :boolean -%>
484
+ puts "\n๐Ÿ“ˆ <%= attribute.name.humanize %> distribution:"
485
+ <%= class_name %>.group(:<%= attribute.name %>).count.each do |state, count|
486
+ percentage = (count.to_f / total_<%= table_name %> * 100).round(1)
487
+ puts " โ€ข #{state ? 'Yes' : 'No'}: #{count} (#{percentage}%)"
488
+ end
489
+ <% end -%>
490
+ <% end -%>
491
+ <% end -%>
492
+
493
+ puts "\n๐ŸŒฑ <%= class_name %> seeding complete!"