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,420 @@
|
|
|
1
|
+
# ActiveRecord Migration Examples: Safe Production Patterns
|
|
2
|
+
# Patterns for zero-downtime deployments and large table migrations
|
|
3
|
+
|
|
4
|
+
# =============================================================================
|
|
5
|
+
# SAFE COLUMN REMOVAL (3-Release Process)
|
|
6
|
+
# =============================================================================
|
|
7
|
+
|
|
8
|
+
# RELEASE 1: Add to ignored_columns in model
|
|
9
|
+
# app/models/user.rb
|
|
10
|
+
class User < ApplicationRecord
|
|
11
|
+
# Step 1: Tell ActiveRecord to ignore this column
|
|
12
|
+
# This prevents errors when old code still references it
|
|
13
|
+
self.ignored_columns += ["legacy_field"]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# RELEASE 2: Drop the column
|
|
17
|
+
class RemoveLegacyField < ActiveRecord::Migration[7.2]
|
|
18
|
+
def change
|
|
19
|
+
# Safe to remove now - code doesn't reference it
|
|
20
|
+
remove_column :users, :legacy_field, :string
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# RELEASE 3: Remove ignored_columns line from model
|
|
25
|
+
|
|
26
|
+
# =============================================================================
|
|
27
|
+
# SAFE NOT NULL ADDITION (PostgreSQL)
|
|
28
|
+
# =============================================================================
|
|
29
|
+
|
|
30
|
+
# Adding NOT NULL to existing column can lock table while validating all rows
|
|
31
|
+
# Split into multiple steps for large tables
|
|
32
|
+
|
|
33
|
+
# Step 1: Add check constraint without validation
|
|
34
|
+
class AddNotNullConstraintStep1 < ActiveRecord::Migration[7.2]
|
|
35
|
+
def change
|
|
36
|
+
add_check_constraint :users, "email IS NOT NULL",
|
|
37
|
+
name: "users_email_not_null",
|
|
38
|
+
validate: false
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Step 2: Validate constraint (separate deployment)
|
|
43
|
+
class AddNotNullConstraintStep2 < ActiveRecord::Migration[7.2]
|
|
44
|
+
def change
|
|
45
|
+
validate_check_constraint :users, name: "users_email_not_null"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Step 3: Add actual NOT NULL and remove constraint
|
|
50
|
+
class AddNotNullConstraintStep3 < ActiveRecord::Migration[7.2]
|
|
51
|
+
def change
|
|
52
|
+
change_column_null :users, :email, false
|
|
53
|
+
remove_check_constraint :users, name: "users_email_not_null"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# =============================================================================
|
|
58
|
+
# SAFE COLUMN ADDITION WITH DEFAULT
|
|
59
|
+
# =============================================================================
|
|
60
|
+
|
|
61
|
+
# Modern Rails (5.2+) handles this efficiently, but for very large tables:
|
|
62
|
+
|
|
63
|
+
# Option 1: Add column, backfill, then set default
|
|
64
|
+
class AddStatusSafely < ActiveRecord::Migration[7.2]
|
|
65
|
+
disable_ddl_transaction!
|
|
66
|
+
|
|
67
|
+
def up
|
|
68
|
+
# Step 1: Add column without default
|
|
69
|
+
add_column :orders, :priority, :string
|
|
70
|
+
|
|
71
|
+
# Step 2: Backfill in batches
|
|
72
|
+
Order.unscoped.in_batches(of: 10_000) do |batch|
|
|
73
|
+
batch.update_all(priority: "normal")
|
|
74
|
+
sleep(0.1) # Throttle
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Step 3: Set default for new records
|
|
78
|
+
change_column_default :orders, :priority, "normal"
|
|
79
|
+
|
|
80
|
+
# Step 4: Add NOT NULL if needed
|
|
81
|
+
change_column_null :orders, :priority, false
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def down
|
|
85
|
+
remove_column :orders, :priority
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# =============================================================================
|
|
90
|
+
# SAFE COLUMN RENAME (Dual-Write Pattern)
|
|
91
|
+
# =============================================================================
|
|
92
|
+
|
|
93
|
+
# Renaming columns breaks running code that references old name
|
|
94
|
+
# Use alias_attribute + phased deployment
|
|
95
|
+
|
|
96
|
+
# RELEASE 1: Add new column, dual-write
|
|
97
|
+
class RenameNameToFullNameStep1 < ActiveRecord::Migration[7.2]
|
|
98
|
+
def change
|
|
99
|
+
add_column :users, :full_name, :string
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# app/models/user.rb (Release 1)
|
|
104
|
+
class User < ApplicationRecord
|
|
105
|
+
# Dual-write to both columns
|
|
106
|
+
before_save :sync_name_columns
|
|
107
|
+
|
|
108
|
+
# Read from new column, fall back to old
|
|
109
|
+
def full_name
|
|
110
|
+
super || name
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
private
|
|
114
|
+
|
|
115
|
+
def sync_name_columns
|
|
116
|
+
self.full_name = name if name_changed?
|
|
117
|
+
self.name = full_name if full_name_changed?
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# RELEASE 2: Backfill existing data
|
|
122
|
+
class RenameNameToFullNameStep2 < ActiveRecord::Migration[7.2]
|
|
123
|
+
disable_ddl_transaction!
|
|
124
|
+
|
|
125
|
+
def up
|
|
126
|
+
User.unscoped.where(full_name: nil).in_batches(of: 10_000) do |batch|
|
|
127
|
+
batch.update_all("full_name = name")
|
|
128
|
+
sleep(0.1)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# RELEASE 3: Switch to new column, stop dual-write
|
|
134
|
+
# Update all code to use full_name, remove sync callback
|
|
135
|
+
|
|
136
|
+
# RELEASE 4: Remove old column (use safe removal pattern)
|
|
137
|
+
class RenameNameToFullNameStep4 < ActiveRecord::Migration[7.2]
|
|
138
|
+
def change
|
|
139
|
+
remove_column :users, :name, :string
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# =============================================================================
|
|
144
|
+
# SAFE TYPE CHANGE
|
|
145
|
+
# =============================================================================
|
|
146
|
+
|
|
147
|
+
# Changing column type often rewrites entire table
|
|
148
|
+
# Use new column + migration instead
|
|
149
|
+
|
|
150
|
+
class ChangeIdToUuid < ActiveRecord::Migration[7.2]
|
|
151
|
+
disable_ddl_transaction!
|
|
152
|
+
|
|
153
|
+
def up
|
|
154
|
+
# Add new UUID column
|
|
155
|
+
add_column :products, :uuid, :uuid, default: "gen_random_uuid()"
|
|
156
|
+
|
|
157
|
+
# Backfill existing records
|
|
158
|
+
Product.unscoped.in_batches(of: 10_000) do |batch|
|
|
159
|
+
batch.update_all("uuid = gen_random_uuid()")
|
|
160
|
+
sleep(0.1)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Add unique index
|
|
164
|
+
add_index :products, :uuid, unique: true, algorithm: :concurrently
|
|
165
|
+
|
|
166
|
+
# Now update foreign keys and code to use uuid
|
|
167
|
+
# Then in later migration, remove old id column
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def down
|
|
171
|
+
remove_column :products, :uuid
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# =============================================================================
|
|
176
|
+
# SAFE INDEX CREATION
|
|
177
|
+
# =============================================================================
|
|
178
|
+
|
|
179
|
+
class AddIndexSafely < ActiveRecord::Migration[7.2]
|
|
180
|
+
disable_ddl_transaction! # Required for concurrent
|
|
181
|
+
|
|
182
|
+
def change
|
|
183
|
+
add_index :users, :email,
|
|
184
|
+
unique: true,
|
|
185
|
+
algorithm: :concurrently,
|
|
186
|
+
name: "index_users_on_email_unique"
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# With if_not_exists for idempotency
|
|
191
|
+
class AddIndexIdempotent < ActiveRecord::Migration[7.2]
|
|
192
|
+
disable_ddl_transaction!
|
|
193
|
+
|
|
194
|
+
def change
|
|
195
|
+
add_index :users, :email, algorithm: :concurrently, if_not_exists: true
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# =============================================================================
|
|
200
|
+
# SAFE FOREIGN KEY ADDITION
|
|
201
|
+
# =============================================================================
|
|
202
|
+
|
|
203
|
+
# Foreign keys validate all rows on creation, which can lock tables
|
|
204
|
+
|
|
205
|
+
# Step 1: Add without validation
|
|
206
|
+
class AddForeignKeySafely1 < ActiveRecord::Migration[7.2]
|
|
207
|
+
def change
|
|
208
|
+
add_foreign_key :orders, :users, validate: false
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Step 2: Validate in separate migration
|
|
213
|
+
class AddForeignKeySafely2 < ActiveRecord::Migration[7.2]
|
|
214
|
+
def change
|
|
215
|
+
validate_foreign_key :orders, :users
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# =============================================================================
|
|
220
|
+
# SAFE DATA BACKFILL
|
|
221
|
+
# =============================================================================
|
|
222
|
+
|
|
223
|
+
class BackfillUserStatus < ActiveRecord::Migration[7.2]
|
|
224
|
+
disable_ddl_transaction!
|
|
225
|
+
|
|
226
|
+
def up
|
|
227
|
+
# Process in batches to avoid memory issues and long locks
|
|
228
|
+
loop do
|
|
229
|
+
# Find batch of records to update
|
|
230
|
+
count = User.unscoped
|
|
231
|
+
.where(status: nil)
|
|
232
|
+
.limit(10_000)
|
|
233
|
+
.update_all(status: "active")
|
|
234
|
+
|
|
235
|
+
break if count.zero?
|
|
236
|
+
|
|
237
|
+
sleep(0.1) # Throttle to reduce database load
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# Using find_each for complex logic
|
|
243
|
+
class BackfillCalculatedField < ActiveRecord::Migration[7.2]
|
|
244
|
+
disable_ddl_transaction!
|
|
245
|
+
|
|
246
|
+
# Define local model to avoid depending on app code
|
|
247
|
+
class Order < ApplicationRecord
|
|
248
|
+
self.table_name = "orders"
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def up
|
|
252
|
+
Order.unscoped.where(total_cents: nil).find_each(batch_size: 1000) do |order|
|
|
253
|
+
total = order.subtotal_cents.to_i + order.tax_cents.to_i
|
|
254
|
+
order.update_columns(total_cents: total)
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# =============================================================================
|
|
260
|
+
# MIGRATION WITH LOCK TIMEOUT
|
|
261
|
+
# =============================================================================
|
|
262
|
+
|
|
263
|
+
class MigrationWithTimeout < ActiveRecord::Migration[7.2]
|
|
264
|
+
def change
|
|
265
|
+
# Set lock timeout to avoid blocking for too long
|
|
266
|
+
execute "SET lock_timeout = '5s'"
|
|
267
|
+
|
|
268
|
+
begin
|
|
269
|
+
add_column :users, :verified, :boolean, default: false
|
|
270
|
+
ensure
|
|
271
|
+
execute "SET lock_timeout = DEFAULT"
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# =============================================================================
|
|
277
|
+
# SAFE TABLE COPY PATTERN
|
|
278
|
+
# =============================================================================
|
|
279
|
+
|
|
280
|
+
# For massive schema changes, copy to new table
|
|
281
|
+
|
|
282
|
+
class RestructureProductsTable < ActiveRecord::Migration[7.2]
|
|
283
|
+
def up
|
|
284
|
+
# Create new table with desired schema
|
|
285
|
+
create_table :products_v2 do |t|
|
|
286
|
+
t.string :name, null: false
|
|
287
|
+
t.decimal :price_cents, precision: 12, scale: 0, null: false
|
|
288
|
+
# ... new schema
|
|
289
|
+
t.timestamps
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# Copy data (in production, do this in background job)
|
|
293
|
+
execute <<~SQL
|
|
294
|
+
INSERT INTO products_v2 (id, name, price_cents, created_at, updated_at)
|
|
295
|
+
SELECT id, name, (price * 100)::bigint, created_at, updated_at
|
|
296
|
+
FROM products
|
|
297
|
+
SQL
|
|
298
|
+
|
|
299
|
+
# Rename tables
|
|
300
|
+
rename_table :products, :products_legacy
|
|
301
|
+
rename_table :products_v2, :products
|
|
302
|
+
|
|
303
|
+
# Later: drop legacy table after verification
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def down
|
|
307
|
+
rename_table :products, :products_v2
|
|
308
|
+
rename_table :products_legacy, :products
|
|
309
|
+
drop_table :products_v2
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# =============================================================================
|
|
314
|
+
# ENUM CHANGES (PostgreSQL)
|
|
315
|
+
# =============================================================================
|
|
316
|
+
|
|
317
|
+
# Adding enum values requires disable_ddl_transaction
|
|
318
|
+
class AddEnumValue < ActiveRecord::Migration[7.2]
|
|
319
|
+
disable_ddl_transaction!
|
|
320
|
+
|
|
321
|
+
def up
|
|
322
|
+
execute "ALTER TYPE order_status ADD VALUE 'refunded'"
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def down
|
|
326
|
+
# Cannot remove enum values easily in PostgreSQL
|
|
327
|
+
# Would need to recreate enum type
|
|
328
|
+
raise ActiveRecord::IrreversibleMigration
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# =============================================================================
|
|
333
|
+
# IDEMPOTENT MIGRATIONS
|
|
334
|
+
# =============================================================================
|
|
335
|
+
|
|
336
|
+
# Make migrations safe to run multiple times (useful for retry scenarios)
|
|
337
|
+
|
|
338
|
+
class IdempotentMigration < ActiveRecord::Migration[7.2]
|
|
339
|
+
def change
|
|
340
|
+
# Check before creating
|
|
341
|
+
unless table_exists?(:audits)
|
|
342
|
+
create_table :audits do |t|
|
|
343
|
+
t.string :action
|
|
344
|
+
t.timestamps
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# Check before adding column
|
|
349
|
+
unless column_exists?(:users, :audit_count)
|
|
350
|
+
add_column :users, :audit_count, :integer, default: 0
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
# Check before adding index
|
|
354
|
+
unless index_exists?(:users, :audit_count)
|
|
355
|
+
add_index :users, :audit_count
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
# =============================================================================
|
|
361
|
+
# POST-DEPLOYMENT MIGRATIONS
|
|
362
|
+
# =============================================================================
|
|
363
|
+
|
|
364
|
+
# For non-critical operations that can run after deployment
|
|
365
|
+
# These don't block the deploy and can take longer
|
|
366
|
+
|
|
367
|
+
# Place in db/post_migrate/ if using GitLab-style setup
|
|
368
|
+
# Or run via separate rake task
|
|
369
|
+
|
|
370
|
+
class PostDeploymentCleanup < ActiveRecord::Migration[7.2]
|
|
371
|
+
disable_ddl_transaction!
|
|
372
|
+
|
|
373
|
+
def up
|
|
374
|
+
# Remove unused indexes (non-blocking)
|
|
375
|
+
remove_index :orders, :legacy_column, algorithm: :concurrently, if_exists: true
|
|
376
|
+
|
|
377
|
+
# Clean up orphaned data
|
|
378
|
+
execute "DELETE FROM order_items WHERE order_id NOT IN (SELECT id FROM orders)"
|
|
379
|
+
|
|
380
|
+
# Add new index (non-blocking)
|
|
381
|
+
add_index :orders, :new_column, algorithm: :concurrently, if_not_exists: true
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
# =============================================================================
|
|
386
|
+
# USING strong_migrations GEM
|
|
387
|
+
# =============================================================================
|
|
388
|
+
|
|
389
|
+
# With strong_migrations installed, dangerous operations are blocked
|
|
390
|
+
# Use safety_assured only after careful review
|
|
391
|
+
|
|
392
|
+
class MigrationWithStrongMigrations < ActiveRecord::Migration[7.2]
|
|
393
|
+
def change
|
|
394
|
+
# This would normally be blocked
|
|
395
|
+
safety_assured do
|
|
396
|
+
add_column :users, :settings, :jsonb, default: {}
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
# Better: Follow recommended pattern
|
|
402
|
+
class SafeAddColumnWithDefault < ActiveRecord::Migration[7.2]
|
|
403
|
+
disable_ddl_transaction!
|
|
404
|
+
|
|
405
|
+
def change
|
|
406
|
+
# Add column without default first
|
|
407
|
+
add_column :users, :settings, :jsonb
|
|
408
|
+
|
|
409
|
+
# Backfill (for existing records)
|
|
410
|
+
User.unscoped.in_batches do |batch|
|
|
411
|
+
batch.update_all(settings: {})
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
# Set default for new records
|
|
415
|
+
change_column_default :users, :settings, {}
|
|
416
|
+
|
|
417
|
+
# Add NOT NULL if needed
|
|
418
|
+
change_column_null :users, :settings, false
|
|
419
|
+
end
|
|
420
|
+
end
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# ActiveRecord Migration Examples: Schema Changes
|
|
2
|
+
# Run migrations: rails db:migrate
|
|
3
|
+
# Rollback: rails db:rollback
|
|
4
|
+
|
|
5
|
+
# =============================================================================
|
|
6
|
+
# CREATING TABLES
|
|
7
|
+
# =============================================================================
|
|
8
|
+
|
|
9
|
+
# Basic table creation
|
|
10
|
+
class CreateProducts < ActiveRecord::Migration[7.2]
|
|
11
|
+
def change
|
|
12
|
+
create_table :products do |t|
|
|
13
|
+
t.string :name, null: false
|
|
14
|
+
t.string :sku, null: false
|
|
15
|
+
t.text :description
|
|
16
|
+
t.decimal :price, precision: 10, scale: 2, null: false
|
|
17
|
+
t.integer :quantity, default: 0
|
|
18
|
+
t.boolean :active, default: true
|
|
19
|
+
t.timestamps
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
add_index :products, :sku, unique: true
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Table with foreign key reference
|
|
27
|
+
class CreateOrderItems < ActiveRecord::Migration[7.2]
|
|
28
|
+
def change
|
|
29
|
+
create_table :order_items do |t|
|
|
30
|
+
t.references :order, null: false, foreign_key: true
|
|
31
|
+
t.references :product, null: false, foreign_key: true
|
|
32
|
+
t.integer :quantity, null: false, default: 1
|
|
33
|
+
t.decimal :unit_price, precision: 10, scale: 2, null: false
|
|
34
|
+
t.timestamps
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Join table (no primary key)
|
|
40
|
+
class CreateProductsCategories < ActiveRecord::Migration[7.2]
|
|
41
|
+
def change
|
|
42
|
+
create_join_table :products, :categories do |t|
|
|
43
|
+
t.index [:product_id, :category_id], unique: true
|
|
44
|
+
t.index :category_id
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Table with UUID primary key
|
|
50
|
+
class CreateApiKeys < ActiveRecord::Migration[7.2]
|
|
51
|
+
def change
|
|
52
|
+
create_table :api_keys, id: :uuid do |t|
|
|
53
|
+
t.references :user, null: false, foreign_key: true
|
|
54
|
+
t.string :name, null: false
|
|
55
|
+
t.string :key_digest, null: false
|
|
56
|
+
t.datetime :expires_at
|
|
57
|
+
t.datetime :last_used_at
|
|
58
|
+
t.timestamps
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
add_index :api_keys, :key_digest, unique: true
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Table with composite primary key
|
|
66
|
+
class CreateTenantUsers < ActiveRecord::Migration[7.2]
|
|
67
|
+
def change
|
|
68
|
+
create_table :tenant_users, primary_key: [:tenant_id, :user_id] do |t|
|
|
69
|
+
t.bigint :tenant_id, null: false
|
|
70
|
+
t.bigint :user_id, null: false
|
|
71
|
+
t.string :role, null: false, default: "member"
|
|
72
|
+
t.timestamps
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Polymorphic association table
|
|
78
|
+
class CreateComments < ActiveRecord::Migration[7.2]
|
|
79
|
+
def change
|
|
80
|
+
create_table :comments do |t|
|
|
81
|
+
t.references :commentable, polymorphic: true, null: false
|
|
82
|
+
t.references :author, null: false, foreign_key: { to_table: :users }
|
|
83
|
+
t.text :body, null: false
|
|
84
|
+
t.timestamps
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
add_index :comments, [:commentable_type, :commentable_id]
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# =============================================================================
|
|
92
|
+
# ADDING COLUMNS
|
|
93
|
+
# =============================================================================
|
|
94
|
+
|
|
95
|
+
class AddFieldsToUsers < ActiveRecord::Migration[7.2]
|
|
96
|
+
def change
|
|
97
|
+
# Simple columns
|
|
98
|
+
add_column :users, :phone, :string
|
|
99
|
+
add_column :users, :verified_at, :datetime
|
|
100
|
+
|
|
101
|
+
# Column with default
|
|
102
|
+
add_column :users, :locale, :string, default: "en", null: false
|
|
103
|
+
|
|
104
|
+
# JSON/JSONB column (PostgreSQL)
|
|
105
|
+
add_column :users, :preferences, :jsonb, default: {}
|
|
106
|
+
add_column :users, :metadata, :jsonb, default: {}
|
|
107
|
+
|
|
108
|
+
# Add index on JSON field (PostgreSQL GIN index)
|
|
109
|
+
add_index :users, :preferences, using: :gin
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Adding reference column
|
|
114
|
+
class AddOrganizationToUsers < ActiveRecord::Migration[7.2]
|
|
115
|
+
def change
|
|
116
|
+
# Creates user_id column with index and foreign key
|
|
117
|
+
add_reference :users, :organization, foreign_key: true
|
|
118
|
+
|
|
119
|
+
# Without index
|
|
120
|
+
add_reference :users, :invited_by, foreign_key: { to_table: :users }, index: false
|
|
121
|
+
|
|
122
|
+
# Polymorphic reference (no foreign key possible)
|
|
123
|
+
add_reference :attachments, :attachable, polymorphic: true, index: true
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# =============================================================================
|
|
128
|
+
# MODIFYING COLUMNS
|
|
129
|
+
# =============================================================================
|
|
130
|
+
|
|
131
|
+
# Renaming columns (reversible)
|
|
132
|
+
class RenameUserFields < ActiveRecord::Migration[7.2]
|
|
133
|
+
def change
|
|
134
|
+
rename_column :users, :name, :full_name
|
|
135
|
+
rename_column :users, :type, :account_type # Avoid STI conflict
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Changing defaults (reversible with from/to)
|
|
140
|
+
class ChangeUserDefaults < ActiveRecord::Migration[7.2]
|
|
141
|
+
def change
|
|
142
|
+
change_column_default :users, :status, from: nil, to: "pending"
|
|
143
|
+
change_column_default :users, :role, from: "user", to: "member"
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Changing null constraint
|
|
148
|
+
class AddNotNullToEmail < ActiveRecord::Migration[7.2]
|
|
149
|
+
def change
|
|
150
|
+
# Backfill NULLs first with default value
|
|
151
|
+
change_column_null :users, :email, false, "unknown@example.com"
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Changing column type (IRREVERSIBLE - requires up/down)
|
|
156
|
+
class ChangeDescriptionToText < ActiveRecord::Migration[7.2]
|
|
157
|
+
def up
|
|
158
|
+
change_column :products, :description, :text
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def down
|
|
162
|
+
change_column :products, :description, :string
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Changing precision/scale
|
|
167
|
+
class IncreasePricePrecision < ActiveRecord::Migration[7.2]
|
|
168
|
+
def up
|
|
169
|
+
change_column :products, :price, :decimal, precision: 12, scale: 2
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def down
|
|
173
|
+
change_column :products, :price, :decimal, precision: 10, scale: 2
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# =============================================================================
|
|
178
|
+
# REMOVING COLUMNS
|
|
179
|
+
# =============================================================================
|
|
180
|
+
|
|
181
|
+
# Removing single column (must include type for reversibility)
|
|
182
|
+
class RemoveLegacyField < ActiveRecord::Migration[7.2]
|
|
183
|
+
def change
|
|
184
|
+
remove_column :users, :legacy_token, :string
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Removing multiple columns
|
|
189
|
+
class RemoveDeprecatedFields < ActiveRecord::Migration[7.2]
|
|
190
|
+
def change
|
|
191
|
+
remove_columns :users, :old_field1, :old_field2, type: :string
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Removing reference
|
|
196
|
+
class RemoveOrganizationFromUsers < ActiveRecord::Migration[7.2]
|
|
197
|
+
def change
|
|
198
|
+
remove_reference :users, :organization, foreign_key: true
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# =============================================================================
|
|
203
|
+
# RENAMING TABLES
|
|
204
|
+
# =============================================================================
|
|
205
|
+
|
|
206
|
+
class RenameUsersToAccounts < ActiveRecord::Migration[7.2]
|
|
207
|
+
def change
|
|
208
|
+
rename_table :users, :accounts
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# =============================================================================
|
|
213
|
+
# DROPPING TABLES
|
|
214
|
+
# =============================================================================
|
|
215
|
+
|
|
216
|
+
# Must include full schema for reversibility
|
|
217
|
+
class DropLegacyReports < ActiveRecord::Migration[7.2]
|
|
218
|
+
def change
|
|
219
|
+
drop_table :legacy_reports do |t|
|
|
220
|
+
t.string :name
|
|
221
|
+
t.text :data
|
|
222
|
+
t.references :user
|
|
223
|
+
t.timestamps
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Irreversible drop (when you don't need rollback)
|
|
229
|
+
class DropTemporaryTable < ActiveRecord::Migration[7.2]
|
|
230
|
+
def up
|
|
231
|
+
drop_table :temp_imports
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def down
|
|
235
|
+
raise ActiveRecord::IrreversibleMigration
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# =============================================================================
|
|
240
|
+
# PRACTICAL PATTERNS
|
|
241
|
+
# =============================================================================
|
|
242
|
+
|
|
243
|
+
# Adding column with index in one migration
|
|
244
|
+
class AddStatusToOrders < ActiveRecord::Migration[7.2]
|
|
245
|
+
def change
|
|
246
|
+
add_column :orders, :status, :string, default: "pending", null: false
|
|
247
|
+
add_index :orders, :status
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Adding timestamps to existing table
|
|
252
|
+
class AddTimestampsToProducts < ActiveRecord::Migration[7.2]
|
|
253
|
+
def change
|
|
254
|
+
# Adds created_at and updated_at with null: false, precision: 6
|
|
255
|
+
add_timestamps :products, default: -> { "CURRENT_TIMESTAMP" }
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# Converting column with data preservation
|
|
260
|
+
class ConvertPriceToInteger < ActiveRecord::Migration[7.2]
|
|
261
|
+
def up
|
|
262
|
+
# Add new column
|
|
263
|
+
add_column :products, :price_cents, :integer
|
|
264
|
+
|
|
265
|
+
# Migrate data (for small tables)
|
|
266
|
+
execute "UPDATE products SET price_cents = (price * 100)::integer"
|
|
267
|
+
|
|
268
|
+
# Remove old column
|
|
269
|
+
remove_column :products, :price
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def down
|
|
273
|
+
add_column :products, :price, :decimal, precision: 10, scale: 2
|
|
274
|
+
execute "UPDATE products SET price = price_cents / 100.0"
|
|
275
|
+
remove_column :products, :price_cents
|
|
276
|
+
end
|
|
277
|
+
end
|