propel_api 0.3.1.6 → 0.3.2

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.
@@ -80,47 +80,11 @@ module PropelApi
80
80
  initialize_propel_api_settings
81
81
 
82
82
  if behavior == :revoke
83
- # Find the migration file
84
- migration_files = Dir[File.join(destination_root, "db/migrate/*_create_#{table_name}.rb")]
83
+ # Check for critical dependencies before destroying
84
+ check_for_critical_dependencies
85
85
 
86
- if migration_files.any?
87
- migration_file = migration_files.first
88
- migration_version = File.basename(migration_file).split('_').first
89
-
90
- # Check if migration was executed
91
- if migration_was_executed?(migration_version)
92
- say "\n" + "="*80, :red
93
- say "⚠️ MIGRATION ALREADY EXECUTED!", :red
94
- say "="*80, :red
95
- say "\n📋 Migration #{migration_version} (create_#{table_name}) has been executed.", :yellow
96
- say "\n🚨 Rails does not automatically rollback migrations for safety reasons.", :yellow
97
- say "\n✅ To safely destroy this resource:", :green
98
- say " 1. First rollback: rails db:rollback", :green
99
- say " 2. Then destroy: rails destroy propel_api #{class_name}", :green
100
- say "\n💡 Alternative approaches:", :cyan
101
- say " • Check migration status: rails db:migrate:status"
102
- say " • Rollback specific: rails db:migrate:down VERSION=#{migration_version}"
103
- say "\n" + "="*80, :red
104
- raise Thor::Error, "Please rollback the migration first, then re-run destroy command."
105
- else
106
- # Migration not executed, check if safe to delete
107
- if safe_to_delete_migration?(migration_version)
108
- say "Removing migration: #{File.basename(migration_file)}", :red
109
- File.delete(migration_file)
110
- else
111
- say "\n" + "="*80, :yellow
112
- say "⚠️ MIGRATION CLEANUP REQUIRED", :yellow
113
- say "="*80, :yellow
114
- say "\n📋 Found unexecuted migration: #{File.basename(migration_file)}", :cyan
115
- say "\n🚨 Cannot auto-delete due to intervening migrations.", :yellow
116
- say "💡 Please manually remove when safe:", :green
117
- say " rm #{migration_file}"
118
- say "\n" + "="*80, :yellow
119
- end
120
- end
121
- else
122
- say "No migration file found for #{table_name}", :yellow
123
- end
86
+ # Generate proper removal migration following Rails conventions
87
+ generate_removal_migration
124
88
  else
125
89
  # Check for tenancy and warn if missing (unless warnings are disabled)
126
90
  check_tenancy_attributes_and_warn unless options[:skip_tenancy]
@@ -154,32 +118,49 @@ module PropelApi
154
118
  validate_attributes_exist
155
119
  end
156
120
 
157
- case @adapter
158
- when 'propel_facets'
159
- template "scaffold/facet_model_template.rb.tt", "app/models/#{file_name}.rb"
160
- when 'graphiti'
161
- template "scaffold/graphiti_model_template.rb.tt", "app/models/#{file_name}.rb"
162
- else
163
- raise "Unknown adapter: #{@adapter}. Run 'rails generate propel_api:install' first."
121
+ # Note: Relationship cleanup is now handled by the comprehensive dependency auto-fix system
122
+ # No need for separate inverse relationship logic here
123
+
124
+ # Direct template call - Rails can reverse this automatically (unless skipped)!
125
+ unless behavior == :revoke && options[:skip_model]
126
+ case @adapter
127
+ when 'propel_facets'
128
+ template "scaffold/facet_model_template.rb.tt", "app/models/#{file_name}.rb"
129
+ when 'graphiti'
130
+ template "scaffold/graphiti_model_template.rb.tt", "app/models/#{file_name}.rb"
131
+ else
132
+ raise "Unknown adapter: #{@adapter}. Run 'rails generate propel_api:install' first."
133
+ end
164
134
  end
165
135
 
166
- # Apply inverse relationships to existing parent models
167
- apply_inverse_relationships
136
+ # Apply inverse relationships AFTER template creation
137
+ if behavior != :revoke
138
+ apply_inverse_relationships
139
+ end
168
140
  end
169
141
 
170
142
  def create_controller
171
- # Use shared method from Base class
172
- create_propel_controller
143
+ create_propel_controller_template
173
144
  end
174
145
 
175
- def create_routes
176
- # Use shared method from Base class
146
+ def add_routes
177
147
  create_propel_routes
178
148
  end
179
149
 
180
- def create_tests
181
- # Use shared method from Base class with all test types
182
- create_propel_tests(test_types: [:model, :controller, :integration, :fixtures])
150
+ def create_model_test
151
+ create_propel_model_test unless behavior == :revoke && options[:skip_tests]
152
+ end
153
+
154
+ def create_controller_test
155
+ create_propel_controller_test unless behavior == :revoke && options[:skip_tests]
156
+ end
157
+
158
+ def create_integration_test
159
+ create_propel_integration_test unless behavior == :revoke && options[:skip_tests]
160
+ end
161
+
162
+ def create_fixtures
163
+ create_propel_fixtures unless behavior == :revoke && options[:skip_tests]
183
164
  end
184
165
 
185
166
  def create_seeds
@@ -288,13 +269,20 @@ module PropelApi
288
269
  next_steps << "Customize facets in: app/models/#{file_name}.rb"
289
270
  end
290
271
 
291
- show_propel_completion_message(
292
- resource_type: "Resource",
293
- generated_files: generated_files,
294
- next_steps: next_steps
295
- )
272
+ if behavior == :revoke
273
+ # Use shared destroy completion message from named_base
274
+ show_destroy_completion_message
275
+ else
276
+ show_propel_completion_message(
277
+ resource_type: "Resource",
278
+ generated_files: generated_files,
279
+ next_steps: next_steps
280
+ )
281
+ end
296
282
  end
297
283
 
284
+
285
+
298
286
  private
299
287
 
300
288
  def relationship_inferrer
@@ -419,6 +407,130 @@ module PropelApi
419
407
  end
420
408
  end
421
409
 
410
+ def remove_inverse_relationships
411
+ # Use Rails model reflection - much cleaner approach!
412
+ begin
413
+ model_class = class_name.constantize
414
+
415
+ # Get all belongs_to associations using Rails reflection
416
+ belongs_to_associations = model_class.reflect_on_all_associations(:belongs_to)
417
+
418
+ belongs_to_associations.each do |association|
419
+ # Skip polymorphic associations (they don't get inverse relationships)
420
+ next if association.polymorphic?
421
+
422
+ parent_class_name = association.class_name
423
+ parent_model_file = "app/models/#{parent_class_name.underscore}.rb"
424
+ full_path = File.join(destination_root, parent_model_file)
425
+
426
+ # Skip if parent model doesn't exist
427
+ next unless File.exist?(full_path)
428
+
429
+ # The inverse relationship we need to remove
430
+ inverse_relationship = "has_many :#{table_name}, dependent: :destroy"
431
+
432
+ begin
433
+ remove_specific_relationship(full_path, inverse_relationship, parent_class_name)
434
+ rescue => e
435
+ say "Warning: Could not remove relationship from #{parent_model_file}: #{e.message}", :yellow
436
+ end
437
+ end
438
+
439
+ rescue NameError => e
440
+ # Fallback: Parse attributes from migration file if model class doesn't exist
441
+ migration_attributes = get_attributes_from_migration
442
+ belongs_to_attributes = migration_attributes.select { |attr| attr[:type] == :references }
443
+
444
+ belongs_to_attributes.each do |attr|
445
+ # Skip polymorphic references
446
+ next if attr[:polymorphic]
447
+
448
+ parent_class_name = attr[:name].classify
449
+ parent_model_file = "app/models/#{parent_class_name.underscore}.rb"
450
+ full_path = File.join(destination_root, parent_model_file)
451
+
452
+ # Skip if parent model doesn't exist
453
+ next unless File.exist?(full_path)
454
+
455
+ inverse_relationship = "has_many :#{table_name}, dependent: :destroy"
456
+
457
+ begin
458
+ remove_specific_relationship(full_path, inverse_relationship, parent_class_name)
459
+ rescue => e
460
+ say "Warning: Could not remove relationship from #{parent_model_file}: #{e.message}", :yellow
461
+ end
462
+ end
463
+ end
464
+ end
465
+
466
+ def get_attributes_from_migration
467
+ # Find the migration file (using same pattern as create_migration)
468
+ migration_files = Dir[File.join(destination_root, "db/migrate/*_create_#{table_name}.rb")]
469
+ return [] unless migration_files.any?
470
+
471
+ migration_file = migration_files.first
472
+ migration_content = File.read(migration_file)
473
+
474
+ # Parse t.references and t.string, t.text, etc. lines
475
+ parsed_attributes = []
476
+
477
+ # Match references with polymorphic option
478
+ migration_content.scan(/t\.references\s+:(\w+).*?(polymorphic:\s*true)?/) do |name, polymorphic|
479
+ parsed_attributes << {
480
+ name: name,
481
+ type: :references,
482
+ polymorphic: !polymorphic.nil?
483
+ }
484
+ end
485
+
486
+ # Match other attribute types
487
+ %w[string text integer decimal boolean datetime date json jsonb].each do |type|
488
+ migration_content.scan(/t\.#{type}\s+:(\w+)/) do |name|
489
+ parsed_attributes << {
490
+ name: name.first,
491
+ type: type.to_sym,
492
+ polymorphic: false
493
+ }
494
+ end
495
+ end
496
+
497
+ parsed_attributes
498
+ end
499
+
500
+ def remove_specific_relationship(model_file_path, relationship, parent_class_name)
501
+ content = File.read(model_file_path)
502
+ original_content = content.dup
503
+
504
+ # Remove the relationship line if it exists
505
+ relationship_pattern = /^\s*#{Regexp.escape(relationship)}\s*\n/
506
+ if content.match?(relationship_pattern)
507
+ content = content.gsub(relationship_pattern, '')
508
+ File.write(model_file_path, content)
509
+ say " ❌ Removed from #{parent_class_name}: #{relationship}", :red
510
+ else
511
+ say " ⚠️ Relationship not found in #{parent_class_name}: #{relationship}", :yellow
512
+ end
513
+ end
514
+
515
+ def remove_from_parent_model(model_file_path, relationships, parent_class_name)
516
+ content = File.read(model_file_path)
517
+ original_content = content.dup
518
+
519
+ relationships.each do |relationship|
520
+ # Remove the relationship line if it exists
521
+ relationship_pattern = /^\s*#{Regexp.escape(relationship)}\s*\n/
522
+ if content.match?(relationship_pattern)
523
+ content = content.gsub(relationship_pattern, '')
524
+ say " ❌ Removed from #{parent_class_name}: #{relationship}", :red
525
+ end
526
+ end
527
+
528
+ # Only write if content changed
529
+ if content != original_content
530
+ File.write(model_file_path, content)
531
+ end
532
+ end
533
+
422
534
  def update_parent_model(model_file_path, relationships, parent_class_name)
423
535
  content = File.read(model_file_path)
424
536
  original_content = content.dup
data/lib/propel_api.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module PropelApi
2
- VERSION = "0.3.1.6"
2
+ VERSION = "0.3.2"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: propel_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1.6
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Martin, Rafael Pivato, Chi Putera
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-09-13 00:00:00.000000000 Z
11
+ date: 2025-09-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails