anima-core 0.2.1 → 1.0.0
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/.reek.yml +27 -1
- data/CHANGELOG.md +19 -0
- data/README.md +213 -43
- data/agents/codebase-analyzer.md +88 -0
- data/agents/codebase-pattern-finder.md +83 -0
- data/agents/documentation-researcher.md +59 -0
- data/agents/thoughts-analyzer.md +102 -0
- data/agents/web-search-researcher.md +71 -0
- data/anima-core.gemspec +3 -0
- data/app/channels/session_channel.rb +195 -45
- data/app/decorators/user_message_decorator.rb +16 -5
- data/app/jobs/agent_request_job.rb +55 -2
- data/app/jobs/analytical_brain_job.rb +33 -0
- data/app/jobs/count_event_tokens_job.rb +15 -4
- data/app/models/concerns/event/broadcasting.rb +81 -0
- data/app/models/event.rb +20 -1
- data/app/models/goal.rb +91 -0
- data/app/models/session.rb +366 -21
- data/config/application.rb +2 -0
- data/config/initializers/event_subscribers.rb +0 -1
- data/config/routes.rb +0 -6
- data/db/migrate/20260313010000_add_status_to_events.rb +8 -0
- data/db/migrate/20260313020000_add_processing_to_sessions.rb +7 -0
- data/db/migrate/20260314075248_add_subagent_support_to_sessions.rb +6 -0
- data/db/migrate/20260314112417_add_granted_tools_to_sessions.rb +5 -0
- data/db/migrate/20260314140000_add_name_to_sessions.rb +7 -0
- data/db/migrate/20260314150000_add_viewport_event_ids_to_sessions.rb +7 -0
- data/db/migrate/20260315100000_add_active_skills_to_sessions.rb +7 -0
- data/db/migrate/20260315140843_create_goals.rb +16 -0
- data/db/migrate/20260315144837_add_completed_at_to_goals.rb +5 -0
- data/db/migrate/20260315191105_add_active_workflow_to_sessions.rb +5 -0
- data/lib/agent_loop.rb +65 -6
- data/lib/agents/definition.rb +116 -0
- data/lib/agents/registry.rb +106 -0
- data/lib/analytical_brain/runner.rb +276 -0
- data/lib/analytical_brain/tools/activate_skill.rb +52 -0
- data/lib/analytical_brain/tools/deactivate_skill.rb +43 -0
- data/lib/analytical_brain/tools/deactivate_workflow.rb +34 -0
- data/lib/analytical_brain/tools/everything_is_ready.rb +28 -0
- data/lib/analytical_brain/tools/finish_goal.rb +62 -0
- data/lib/analytical_brain/tools/read_workflow.rb +58 -0
- data/lib/analytical_brain/tools/rename_session.rb +63 -0
- data/lib/analytical_brain/tools/set_goal.rb +60 -0
- data/lib/analytical_brain/tools/update_goal.rb +60 -0
- data/lib/analytical_brain.rb +23 -0
- data/lib/anima/cli/mcp/secrets.rb +76 -0
- data/lib/anima/cli/mcp.rb +197 -0
- data/lib/anima/cli.rb +5 -40
- data/lib/anima/installer.rb +168 -0
- data/lib/anima/settings.rb +226 -0
- data/lib/anima/version.rb +1 -1
- data/lib/anima.rb +9 -0
- data/lib/credential_store.rb +103 -0
- data/lib/environment_probe.rb +232 -0
- data/lib/events/subscribers/persister.rb +1 -0
- data/lib/events/user_message.rb +17 -0
- data/lib/llm/client.rb +29 -10
- data/lib/mcp/client_manager.rb +86 -0
- data/lib/mcp/config.rb +213 -0
- data/lib/mcp/health_check.rb +77 -0
- data/lib/mcp/secrets.rb +73 -0
- data/lib/mcp/stdio_transport.rb +206 -0
- data/lib/providers/anthropic.rb +11 -20
- data/lib/shell_session.rb +11 -10
- data/lib/skills/definition.rb +97 -0
- data/lib/skills/registry.rb +105 -0
- data/lib/tools/edit.rb +226 -0
- data/lib/tools/mcp_tool.rb +114 -0
- data/lib/tools/read.rb +151 -0
- data/lib/tools/registry.rb +14 -12
- data/lib/tools/request_feature.rb +121 -0
- data/lib/tools/return_result.rb +81 -0
- data/lib/tools/spawn_specialist.rb +109 -0
- data/lib/tools/spawn_subagent.rb +111 -0
- data/lib/tools/subagent_prompts.rb +12 -0
- data/lib/tools/web_get.rb +8 -9
- data/lib/tools/write.rb +86 -0
- data/lib/tui/app.rb +985 -26
- data/lib/tui/cable_client.rb +69 -31
- data/lib/tui/message_store.rb +103 -8
- data/lib/tui/screens/chat.rb +293 -45
- data/lib/workflows/definition.rb +97 -0
- data/lib/workflows/registry.rb +89 -0
- data/skills/activerecord/SKILL.md +255 -0
- data/skills/activerecord/examples/associations/association_extensions.rb +298 -0
- data/skills/activerecord/examples/associations/basic_associations.rb +118 -0
- data/skills/activerecord/examples/associations/counter_caches.rb +215 -0
- data/skills/activerecord/examples/associations/polymorphic_associations.rb +217 -0
- data/skills/activerecord/examples/associations/self_referential.rb +302 -0
- data/skills/activerecord/examples/associations/through_associations.rb +203 -0
- data/skills/activerecord/examples/basics/crud_operations.rb +209 -0
- data/skills/activerecord/examples/basics/dirty_tracking.rb +218 -0
- data/skills/activerecord/examples/basics/inheritance.rb +377 -0
- data/skills/activerecord/examples/basics/type_casting.rb +317 -0
- data/skills/activerecord/examples/callbacks/alternatives_to_callbacks.rb +447 -0
- data/skills/activerecord/examples/callbacks/conditional_callbacks.rb +353 -0
- data/skills/activerecord/examples/callbacks/lifecycle_callbacks.rb +280 -0
- data/skills/activerecord/examples/callbacks/transaction_callbacks.rb +340 -0
- data/skills/activerecord/examples/migrations/indexes_and_constraints.rb +337 -0
- data/skills/activerecord/examples/migrations/reversible_patterns.rb +403 -0
- data/skills/activerecord/examples/migrations/safe_patterns.rb +420 -0
- data/skills/activerecord/examples/migrations/schema_changes.rb +277 -0
- data/skills/activerecord/examples/querying/batch_processing.rb +226 -0
- data/skills/activerecord/examples/querying/eager_loading.rb +259 -0
- data/skills/activerecord/examples/querying/finder_methods.rb +170 -0
- data/skills/activerecord/examples/querying/optimization.rb +275 -0
- data/skills/activerecord/examples/querying/scopes.rb +260 -0
- data/skills/activerecord/examples/validations/built_in_validators.rb +277 -0
- data/skills/activerecord/examples/validations/conditional_validations.rb +288 -0
- data/skills/activerecord/examples/validations/custom_validators.rb +381 -0
- data/skills/activerecord/examples/validations/database_constraints.rb +432 -0
- data/skills/activerecord/examples/validations/validation_contexts.rb +367 -0
- data/skills/activerecord/references/associations.md +709 -0
- data/skills/activerecord/references/basics.md +622 -0
- data/skills/activerecord/references/callbacks.md +738 -0
- data/skills/activerecord/references/migrations.md +657 -0
- data/skills/activerecord/references/querying.md +655 -0
- data/skills/activerecord/references/validations.md +596 -0
- data/skills/dragonruby/SKILL.md +250 -0
- data/skills/dragonruby/examples/audio/audio_events.rb +55 -0
- data/skills/dragonruby/examples/audio/background_music.rb +29 -0
- data/skills/dragonruby/examples/audio/crossfade.rb +51 -0
- data/skills/dragonruby/examples/audio/music_controls.rb +51 -0
- data/skills/dragonruby/examples/audio/sound_effects.rb +30 -0
- data/skills/dragonruby/examples/core/coordinate_system.rb +27 -0
- data/skills/dragonruby/examples/core/hello_world.rb +24 -0
- data/skills/dragonruby/examples/core/labels.rb +22 -0
- data/skills/dragonruby/examples/core/sprites.rb +35 -0
- data/skills/dragonruby/examples/core/state_management.rb +29 -0
- data/skills/dragonruby/examples/distribution/background_pause.rb +42 -0
- data/skills/dragonruby/examples/distribution/build_workflow.sh +26 -0
- data/skills/dragonruby/examples/distribution/cvars_production.txt +16 -0
- data/skills/dragonruby/examples/distribution/game_metadata_hd.txt +23 -0
- data/skills/dragonruby/examples/distribution/game_metadata_minimal.txt +9 -0
- data/skills/dragonruby/examples/distribution/game_metadata_mobile.txt +31 -0
- data/skills/dragonruby/examples/distribution/platform_detection.rb +36 -0
- data/skills/dragonruby/examples/distribution/steam_metadata.txt +19 -0
- data/skills/dragonruby/examples/entities/collision_detection.rb +43 -0
- data/skills/dragonruby/examples/entities/entity_lifecycle.rb +68 -0
- data/skills/dragonruby/examples/entities/entity_storage.rb +38 -0
- data/skills/dragonruby/examples/entities/factory_methods.rb +45 -0
- data/skills/dragonruby/examples/entities/random_spawning.rb +50 -0
- data/skills/dragonruby/examples/game-logic/reset_patterns.rb +98 -0
- data/skills/dragonruby/examples/game-logic/save_load.rb +101 -0
- data/skills/dragonruby/examples/game-logic/scoring.rb +104 -0
- data/skills/dragonruby/examples/game-logic/state_transitions.rb +103 -0
- data/skills/dragonruby/examples/game-logic/timers.rb +87 -0
- data/skills/dragonruby/examples/input/action_triggers.rb +36 -0
- data/skills/dragonruby/examples/input/analog_movement.rb +28 -0
- data/skills/dragonruby/examples/input/controller_input.rb +28 -0
- data/skills/dragonruby/examples/input/directional_input.rb +24 -0
- data/skills/dragonruby/examples/input/keyboard_input.rb +28 -0
- data/skills/dragonruby/examples/input/mouse_click.rb +26 -0
- data/skills/dragonruby/examples/input/movement_with_bounds.rb +22 -0
- data/skills/dragonruby/examples/input/normalized_movement.rb +32 -0
- data/skills/dragonruby/examples/rendering/frame_animation.rb +32 -0
- data/skills/dragonruby/examples/rendering/labels.rb +32 -0
- data/skills/dragonruby/examples/rendering/layering.rb +51 -0
- data/skills/dragonruby/examples/rendering/solids.rb +61 -0
- data/skills/dragonruby/examples/rendering/sprites.rb +33 -0
- data/skills/dragonruby/examples/rendering/spritesheet_animation.rb +39 -0
- data/skills/dragonruby/examples/scenes/case_dispatch.rb +60 -0
- data/skills/dragonruby/examples/scenes/class_based.rb +150 -0
- data/skills/dragonruby/examples/scenes/pause_overlay.rb +100 -0
- data/skills/dragonruby/examples/scenes/safe_transitions.rb +68 -0
- data/skills/dragonruby/examples/scenes/scene_transitions.rb +98 -0
- data/skills/dragonruby/examples/scenes/send_dispatch.rb +88 -0
- data/skills/dragonruby/references/audio.md +396 -0
- data/skills/dragonruby/references/core.md +385 -0
- data/skills/dragonruby/references/distribution.md +434 -0
- data/skills/dragonruby/references/entities.md +516 -0
- data/skills/dragonruby/references/game-logic/persistence.md +386 -0
- data/skills/dragonruby/references/game-logic/state.md +389 -0
- data/skills/dragonruby/references/input.md +414 -0
- data/skills/dragonruby/references/rendering/animation.md +467 -0
- data/skills/dragonruby/references/rendering/primitives.md +403 -0
- data/skills/dragonruby/references/scenes.md +443 -0
- data/skills/draper-decorators/SKILL.md +344 -0
- data/skills/draper-decorators/examples/application_decorator.rb +61 -0
- data/skills/draper-decorators/examples/decorator_spec.rb +253 -0
- data/skills/draper-decorators/examples/model_decorator.rb +152 -0
- data/skills/draper-decorators/references/anti-patterns.md +640 -0
- data/skills/draper-decorators/references/patterns.md +507 -0
- data/skills/draper-decorators/references/testing.md +559 -0
- data/skills/gh-issue.md +182 -0
- data/skills/mcp-server/SKILL.md +177 -0
- data/skills/mcp-server/examples/dynamic_tools.rb +36 -0
- data/skills/mcp-server/examples/file_manager_tool.rb +85 -0
- data/skills/mcp-server/examples/http_client.rb +48 -0
- data/skills/mcp-server/examples/http_server.rb +97 -0
- data/skills/mcp-server/examples/rails_integration.rb +88 -0
- data/skills/mcp-server/examples/stdio_server.rb +108 -0
- data/skills/mcp-server/examples/streaming_client.rb +95 -0
- data/skills/mcp-server/references/gotchas.md +183 -0
- data/skills/mcp-server/references/prompts.md +98 -0
- data/skills/mcp-server/references/resources.md +53 -0
- data/skills/mcp-server/references/server.md +140 -0
- data/skills/mcp-server/references/tools.md +146 -0
- data/skills/mcp-server/references/transport.md +104 -0
- data/skills/ratatui-ruby/SKILL.md +315 -0
- data/skills/ratatui-ruby/references/core-concepts.md +340 -0
- data/skills/ratatui-ruby/references/events.md +387 -0
- data/skills/ratatui-ruby/references/frameworks.md +522 -0
- data/skills/ratatui-ruby/references/layout.md +423 -0
- data/skills/ratatui-ruby/references/styling.md +268 -0
- data/skills/ratatui-ruby/references/testing.md +433 -0
- data/skills/ratatui-ruby/references/widgets.md +532 -0
- data/skills/rspec/SKILL.md +340 -0
- data/skills/rspec/examples/core/basic_structure.rb +69 -0
- data/skills/rspec/examples/core/configuration.rb +126 -0
- data/skills/rspec/examples/core/hooks.rb +126 -0
- data/skills/rspec/examples/core/memoized_helpers.rb +139 -0
- data/skills/rspec/examples/core/metadata_filtering.rb +144 -0
- data/skills/rspec/examples/core/shared_examples.rb +145 -0
- data/skills/rspec/examples/factory_bot/associations.rb +314 -0
- data/skills/rspec/examples/factory_bot/build_strategies.rb +272 -0
- data/skills/rspec/examples/factory_bot/callbacks.rb +320 -0
- data/skills/rspec/examples/factory_bot/custom_construction.rb +328 -0
- data/skills/rspec/examples/factory_bot/factory_definition.rb +191 -0
- data/skills/rspec/examples/factory_bot/inheritance.rb +314 -0
- data/skills/rspec/examples/factory_bot/traits.rb +293 -0
- data/skills/rspec/examples/factory_bot/transients.rb +229 -0
- data/skills/rspec/examples/matchers/change.rb +115 -0
- data/skills/rspec/examples/matchers/collections.rb +154 -0
- data/skills/rspec/examples/matchers/comparisons.rb +79 -0
- data/skills/rspec/examples/matchers/composing.rb +155 -0
- data/skills/rspec/examples/matchers/custom_matchers.rb +197 -0
- data/skills/rspec/examples/matchers/equality.rb +58 -0
- data/skills/rspec/examples/matchers/errors.rb +136 -0
- data/skills/rspec/examples/matchers/output.rb +103 -0
- data/skills/rspec/examples/matchers/predicates.rb +87 -0
- data/skills/rspec/examples/matchers/truthiness.rb +101 -0
- data/skills/rspec/examples/matchers/types.rb +82 -0
- data/skills/rspec/examples/matchers/yield.rb +147 -0
- data/skills/rspec/examples/mocks/any_instance.rb +172 -0
- data/skills/rspec/examples/mocks/argument_matchers.rb +206 -0
- data/skills/rspec/examples/mocks/constants.rb +177 -0
- data/skills/rspec/examples/mocks/doubles.rb +139 -0
- data/skills/rspec/examples/mocks/expectations.rb +137 -0
- data/skills/rspec/examples/mocks/message_chains.rb +173 -0
- data/skills/rspec/examples/mocks/ordering.rb +144 -0
- data/skills/rspec/examples/mocks/receive_counts.rb +181 -0
- data/skills/rspec/examples/mocks/responses.rb +223 -0
- data/skills/rspec/examples/mocks/spies.rb +149 -0
- data/skills/rspec/examples/mocks/stubbing.rb +133 -0
- data/skills/rspec/examples/rails/channels.rb +250 -0
- data/skills/rspec/examples/rails/controller_specs.rb +302 -0
- data/skills/rspec/examples/rails/helper_specs.rb +245 -0
- data/skills/rspec/examples/rails/job_specs.rb +256 -0
- data/skills/rspec/examples/rails/mailer_specs.rb +228 -0
- data/skills/rspec/examples/rails/matchers.rb +374 -0
- data/skills/rspec/examples/rails/model_specs.rb +193 -0
- data/skills/rspec/examples/rails/request_specs.rb +275 -0
- data/skills/rspec/examples/rails/routing_specs.rb +276 -0
- data/skills/rspec/examples/rails/system_specs.rb +294 -0
- data/skills/rspec/examples/rails/transactions.rb +254 -0
- data/skills/rspec/examples/rails/view_specs.rb +252 -0
- data/skills/rspec/references/core.md +816 -0
- data/skills/rspec/references/factory_bot.md +641 -0
- data/skills/rspec/references/matchers.md +516 -0
- data/skills/rspec/references/mocks.md +381 -0
- data/skills/rspec/references/rails.md +528 -0
- data/templates/soul.md +40 -0
- data/workflows/commit.md +45 -0
- data/workflows/create_handoff.md +98 -0
- data/workflows/create_note.md +82 -0
- data/workflows/create_plan.md +457 -0
- data/workflows/decompose_ticket.md +109 -0
- data/workflows/feature.md +91 -0
- data/workflows/implement_plan.md +87 -0
- data/workflows/iterate_plan.md +247 -0
- data/workflows/research_codebase.md +210 -0
- data/workflows/resume_handoff.md +217 -0
- data/workflows/review_pr.md +320 -0
- data/workflows/thoughts_init.md +71 -0
- data/workflows/validate_plan.md +166 -0
- metadata +290 -3
- data/app/controllers/api/sessions_controller.rb +0 -25
- data/lib/events/subscribers/action_cable_bridge.rb +0 -59
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
# ActiveRecord Migration Examples: Reversible Patterns
|
|
2
|
+
# Techniques for writing migrations that can be rolled back safely
|
|
3
|
+
|
|
4
|
+
# =============================================================================
|
|
5
|
+
# AUTO-REVERSIBLE OPERATIONS
|
|
6
|
+
# =============================================================================
|
|
7
|
+
|
|
8
|
+
# These work with `change` method - Rails handles reversal automatically
|
|
9
|
+
|
|
10
|
+
class AutoReversibleOperations < ActiveRecord::Migration[7.2]
|
|
11
|
+
def change
|
|
12
|
+
# Table operations
|
|
13
|
+
create_table :products do |t|
|
|
14
|
+
t.string :name
|
|
15
|
+
t.timestamps
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
create_join_table :products, :categories
|
|
19
|
+
|
|
20
|
+
# Column operations
|
|
21
|
+
add_column :users, :phone, :string
|
|
22
|
+
add_timestamps :legacy_table
|
|
23
|
+
|
|
24
|
+
# Reference operations
|
|
25
|
+
add_reference :orders, :user, foreign_key: true
|
|
26
|
+
|
|
27
|
+
# Index operations
|
|
28
|
+
add_index :users, :email, unique: true
|
|
29
|
+
|
|
30
|
+
# Constraint operations
|
|
31
|
+
add_foreign_key :orders, :users
|
|
32
|
+
add_check_constraint :products, "price > 0", name: "price_positive"
|
|
33
|
+
|
|
34
|
+
# Rename operations
|
|
35
|
+
rename_table :old_name, :new_name
|
|
36
|
+
rename_column :users, :name, :full_name
|
|
37
|
+
rename_index :users, :old_index, :new_index
|
|
38
|
+
|
|
39
|
+
# Extension operations (PostgreSQL)
|
|
40
|
+
enable_extension "pgcrypto"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# =============================================================================
|
|
45
|
+
# REVERSIBLE WITH from/to
|
|
46
|
+
# =============================================================================
|
|
47
|
+
|
|
48
|
+
# These operations need explicit from/to values
|
|
49
|
+
|
|
50
|
+
class ReversibleWithFromTo < ActiveRecord::Migration[7.2]
|
|
51
|
+
def change
|
|
52
|
+
# Default changes
|
|
53
|
+
change_column_default :users, :status, from: nil, to: "active"
|
|
54
|
+
change_column_default :products, :quantity, from: 0, to: 1
|
|
55
|
+
|
|
56
|
+
# Comment changes
|
|
57
|
+
change_column_comment :users, :email, from: nil, to: "Primary contact email"
|
|
58
|
+
change_table_comment :users, from: nil, to: "Application users"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# =============================================================================
|
|
63
|
+
# REVERSIBLE WITH COLUMN TYPE
|
|
64
|
+
# =============================================================================
|
|
65
|
+
|
|
66
|
+
# Remove operations need type info for recreation on rollback
|
|
67
|
+
|
|
68
|
+
class ReversibleRemoveOperations < ActiveRecord::Migration[7.2]
|
|
69
|
+
def change
|
|
70
|
+
# Single column - must include type
|
|
71
|
+
remove_column :users, :legacy_token, :string
|
|
72
|
+
|
|
73
|
+
# Multiple columns - must include type option
|
|
74
|
+
remove_columns :users, :temp1, :temp2, type: :string
|
|
75
|
+
|
|
76
|
+
# With all options for exact recreation
|
|
77
|
+
remove_column :products, :discount, :decimal,
|
|
78
|
+
precision: 5, scale: 2, default: 0.0
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# =============================================================================
|
|
83
|
+
# REVERSIBLE DROP TABLE
|
|
84
|
+
# =============================================================================
|
|
85
|
+
|
|
86
|
+
# drop_table must include full schema for reversibility
|
|
87
|
+
|
|
88
|
+
class ReversibleDropTable < ActiveRecord::Migration[7.2]
|
|
89
|
+
def change
|
|
90
|
+
drop_table :legacy_reports do |t|
|
|
91
|
+
t.string :name, null: false
|
|
92
|
+
t.text :content
|
|
93
|
+
t.references :user, foreign_key: true
|
|
94
|
+
t.timestamps
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# With table options
|
|
98
|
+
drop_table :archived_data, id: :uuid do |t|
|
|
99
|
+
t.jsonb :data
|
|
100
|
+
t.timestamps
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# =============================================================================
|
|
106
|
+
# REVERSIBLE REMOVE INDEX
|
|
107
|
+
# =============================================================================
|
|
108
|
+
|
|
109
|
+
class ReversibleRemoveIndex < ActiveRecord::Migration[7.2]
|
|
110
|
+
def change
|
|
111
|
+
# By column (reversible)
|
|
112
|
+
remove_index :users, :email
|
|
113
|
+
|
|
114
|
+
# Composite index
|
|
115
|
+
remove_index :orders, [:user_id, :status]
|
|
116
|
+
|
|
117
|
+
# With all options for exact recreation
|
|
118
|
+
remove_index :users, :username, unique: true, name: "idx_users_username"
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# =============================================================================
|
|
123
|
+
# REVERSIBLE REMOVE FOREIGN KEY
|
|
124
|
+
# =============================================================================
|
|
125
|
+
|
|
126
|
+
class ReversibleRemoveForeignKey < ActiveRecord::Migration[7.2]
|
|
127
|
+
def change
|
|
128
|
+
# By table (reversible)
|
|
129
|
+
remove_foreign_key :orders, :users
|
|
130
|
+
|
|
131
|
+
# By column - must include to_table
|
|
132
|
+
remove_foreign_key :orders, column: :buyer_id, to_table: :users
|
|
133
|
+
|
|
134
|
+
# With options
|
|
135
|
+
remove_foreign_key :order_items, :products,
|
|
136
|
+
on_delete: :cascade, on_update: :cascade
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# =============================================================================
|
|
141
|
+
# USING reversible BLOCK
|
|
142
|
+
# =============================================================================
|
|
143
|
+
|
|
144
|
+
# For custom SQL or complex operations
|
|
145
|
+
|
|
146
|
+
class UsingReversibleBlock < ActiveRecord::Migration[7.2]
|
|
147
|
+
def change
|
|
148
|
+
create_table :products do |t|
|
|
149
|
+
t.string :name
|
|
150
|
+
t.decimal :price
|
|
151
|
+
t.timestamps
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Custom constraint with reversible
|
|
155
|
+
reversible do |dir|
|
|
156
|
+
dir.up do
|
|
157
|
+
execute <<~SQL
|
|
158
|
+
ALTER TABLE products
|
|
159
|
+
ADD CONSTRAINT price_range
|
|
160
|
+
CHECK (price BETWEEN 0 AND 1000000)
|
|
161
|
+
SQL
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
dir.down do
|
|
165
|
+
execute <<~SQL
|
|
166
|
+
ALTER TABLE products
|
|
167
|
+
DROP CONSTRAINT price_range
|
|
168
|
+
SQL
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Multiple reversible blocks
|
|
175
|
+
class ComplexReversibleMigration < ActiveRecord::Migration[7.2]
|
|
176
|
+
def change
|
|
177
|
+
add_column :orders, :status, :string
|
|
178
|
+
|
|
179
|
+
reversible do |dir|
|
|
180
|
+
dir.up do
|
|
181
|
+
execute "UPDATE orders SET status = 'pending' WHERE status IS NULL"
|
|
182
|
+
end
|
|
183
|
+
# No down - data would be lost anyway
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
change_column_null :orders, :status, false
|
|
187
|
+
|
|
188
|
+
reversible do |dir|
|
|
189
|
+
dir.up do
|
|
190
|
+
execute "CREATE INDEX CONCURRENTLY idx_orders_status ON orders(status)"
|
|
191
|
+
end
|
|
192
|
+
dir.down do
|
|
193
|
+
execute "DROP INDEX CONCURRENTLY idx_orders_status"
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# =============================================================================
|
|
200
|
+
# EXPLICIT up/down METHODS
|
|
201
|
+
# =============================================================================
|
|
202
|
+
|
|
203
|
+
# Use when operation is truly irreversible or complex
|
|
204
|
+
|
|
205
|
+
class ExplicitUpDown < ActiveRecord::Migration[7.2]
|
|
206
|
+
def up
|
|
207
|
+
# Change column type (irreversible without explicit down)
|
|
208
|
+
change_column :products, :price, :decimal, precision: 12, scale: 2
|
|
209
|
+
|
|
210
|
+
# Data transformation
|
|
211
|
+
execute <<~SQL
|
|
212
|
+
UPDATE products
|
|
213
|
+
SET price = price * 100
|
|
214
|
+
WHERE price_type = 'dollars'
|
|
215
|
+
SQL
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def down
|
|
219
|
+
execute <<~SQL
|
|
220
|
+
UPDATE products
|
|
221
|
+
SET price = price / 100
|
|
222
|
+
WHERE price_type = 'dollars'
|
|
223
|
+
SQL
|
|
224
|
+
|
|
225
|
+
change_column :products, :price, :decimal, precision: 10, scale: 2
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# =============================================================================
|
|
230
|
+
# IRREVERSIBLE MIGRATIONS
|
|
231
|
+
# =============================================================================
|
|
232
|
+
|
|
233
|
+
# Explicitly mark migrations that cannot be reversed
|
|
234
|
+
|
|
235
|
+
class IrreversibleMigration < ActiveRecord::Migration[7.2]
|
|
236
|
+
def up
|
|
237
|
+
# Data destruction - cannot be reversed
|
|
238
|
+
execute "DELETE FROM audit_logs WHERE created_at < '2020-01-01'"
|
|
239
|
+
|
|
240
|
+
# Remove column without saving type info
|
|
241
|
+
remove_column :users, :legacy_data
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def down
|
|
245
|
+
raise ActiveRecord::IrreversibleMigration,
|
|
246
|
+
"Cannot restore deleted audit logs or legacy_data column contents"
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Partial irreversibility
|
|
251
|
+
class PartiallyReversible < ActiveRecord::Migration[7.2]
|
|
252
|
+
def up
|
|
253
|
+
add_column :users, :full_name, :string
|
|
254
|
+
|
|
255
|
+
# Combine first_name + last_name into full_name
|
|
256
|
+
execute <<~SQL
|
|
257
|
+
UPDATE users SET full_name = first_name || ' ' || last_name
|
|
258
|
+
SQL
|
|
259
|
+
|
|
260
|
+
remove_column :users, :first_name
|
|
261
|
+
remove_column :users, :last_name
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def down
|
|
265
|
+
add_column :users, :first_name, :string
|
|
266
|
+
add_column :users, :last_name, :string
|
|
267
|
+
|
|
268
|
+
# Best effort - split on space (may not match original)
|
|
269
|
+
execute <<~SQL
|
|
270
|
+
UPDATE users SET
|
|
271
|
+
first_name = split_part(full_name, ' ', 1),
|
|
272
|
+
last_name = substring(full_name from position(' ' in full_name) + 1)
|
|
273
|
+
SQL
|
|
274
|
+
|
|
275
|
+
remove_column :users, :full_name
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# =============================================================================
|
|
280
|
+
# CONDITIONAL REVERSIBILITY
|
|
281
|
+
# =============================================================================
|
|
282
|
+
|
|
283
|
+
# Different behavior in up vs down
|
|
284
|
+
|
|
285
|
+
class ConditionalMigration < ActiveRecord::Migration[7.2]
|
|
286
|
+
def change
|
|
287
|
+
# These are always reversible
|
|
288
|
+
add_column :users, :verified_at, :datetime
|
|
289
|
+
add_column :users, :verified_by_id, :bigint
|
|
290
|
+
|
|
291
|
+
# Data population only on up
|
|
292
|
+
reversible do |dir|
|
|
293
|
+
dir.up do
|
|
294
|
+
# Set verified_at for users who completed verification
|
|
295
|
+
execute <<~SQL
|
|
296
|
+
UPDATE users
|
|
297
|
+
SET verified_at = completed_at
|
|
298
|
+
WHERE verification_status = 'completed'
|
|
299
|
+
SQL
|
|
300
|
+
end
|
|
301
|
+
# No down - we'd lose when they were actually verified
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
add_index :users, :verified_at
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# =============================================================================
|
|
309
|
+
# TESTING REVERSIBILITY
|
|
310
|
+
# =============================================================================
|
|
311
|
+
|
|
312
|
+
# Always test migrations in both directions
|
|
313
|
+
|
|
314
|
+
# From command line:
|
|
315
|
+
# rails db:migrate VERSION=20240101000000
|
|
316
|
+
# rails db:rollback STEP=1
|
|
317
|
+
# rails db:migrate
|
|
318
|
+
|
|
319
|
+
# Or use the migrate alias (recommended):
|
|
320
|
+
# rails db:migrate db:rollback && rails db:migrate
|
|
321
|
+
|
|
322
|
+
# In RSpec (if you have migration tests):
|
|
323
|
+
# describe Migration do
|
|
324
|
+
# it "migrates up and down" do
|
|
325
|
+
# migrate_up
|
|
326
|
+
# expect(User.column_names).to include("full_name")
|
|
327
|
+
#
|
|
328
|
+
# migrate_down
|
|
329
|
+
# expect(User.column_names).not_to include("full_name")
|
|
330
|
+
# end
|
|
331
|
+
# end
|
|
332
|
+
|
|
333
|
+
# =============================================================================
|
|
334
|
+
# PRACTICAL PATTERNS
|
|
335
|
+
# =============================================================================
|
|
336
|
+
|
|
337
|
+
# Reversible enum creation (PostgreSQL)
|
|
338
|
+
class CreateStatusEnum < ActiveRecord::Migration[7.2]
|
|
339
|
+
def change
|
|
340
|
+
reversible do |dir|
|
|
341
|
+
dir.up do
|
|
342
|
+
execute <<~SQL
|
|
343
|
+
CREATE TYPE order_status AS ENUM ('pending', 'processing', 'shipped', 'delivered')
|
|
344
|
+
SQL
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
dir.down do
|
|
348
|
+
execute "DROP TYPE order_status"
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
add_column :orders, :status, :order_status, default: "pending"
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
# Reversible trigger creation
|
|
357
|
+
class CreateAuditTrigger < ActiveRecord::Migration[7.2]
|
|
358
|
+
def change
|
|
359
|
+
reversible do |dir|
|
|
360
|
+
dir.up do
|
|
361
|
+
execute <<~SQL
|
|
362
|
+
CREATE OR REPLACE FUNCTION audit_changes() RETURNS TRIGGER AS $$
|
|
363
|
+
BEGIN
|
|
364
|
+
INSERT INTO audit_logs (table_name, record_id, action, created_at)
|
|
365
|
+
VALUES (TG_TABLE_NAME, NEW.id, TG_OP, NOW());
|
|
366
|
+
RETURN NEW;
|
|
367
|
+
END;
|
|
368
|
+
$$ LANGUAGE plpgsql;
|
|
369
|
+
|
|
370
|
+
CREATE TRIGGER users_audit
|
|
371
|
+
AFTER INSERT OR UPDATE ON users
|
|
372
|
+
FOR EACH ROW EXECUTE FUNCTION audit_changes();
|
|
373
|
+
SQL
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
dir.down do
|
|
377
|
+
execute <<~SQL
|
|
378
|
+
DROP TRIGGER IF EXISTS users_audit ON users;
|
|
379
|
+
DROP FUNCTION IF EXISTS audit_changes();
|
|
380
|
+
SQL
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
# Reversible view creation
|
|
387
|
+
class CreateActiveUsersView < ActiveRecord::Migration[7.2]
|
|
388
|
+
def change
|
|
389
|
+
reversible do |dir|
|
|
390
|
+
dir.up do
|
|
391
|
+
execute <<~SQL
|
|
392
|
+
CREATE VIEW active_users AS
|
|
393
|
+
SELECT * FROM users
|
|
394
|
+
WHERE active = true AND deleted_at IS NULL
|
|
395
|
+
SQL
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
dir.down do
|
|
399
|
+
execute "DROP VIEW active_users"
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
end
|