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.
Files changed (280) hide show
  1. checksums.yaml +4 -4
  2. data/.reek.yml +27 -1
  3. data/CHANGELOG.md +19 -0
  4. data/README.md +213 -43
  5. data/agents/codebase-analyzer.md +88 -0
  6. data/agents/codebase-pattern-finder.md +83 -0
  7. data/agents/documentation-researcher.md +59 -0
  8. data/agents/thoughts-analyzer.md +102 -0
  9. data/agents/web-search-researcher.md +71 -0
  10. data/anima-core.gemspec +3 -0
  11. data/app/channels/session_channel.rb +195 -45
  12. data/app/decorators/user_message_decorator.rb +16 -5
  13. data/app/jobs/agent_request_job.rb +55 -2
  14. data/app/jobs/analytical_brain_job.rb +33 -0
  15. data/app/jobs/count_event_tokens_job.rb +15 -4
  16. data/app/models/concerns/event/broadcasting.rb +81 -0
  17. data/app/models/event.rb +20 -1
  18. data/app/models/goal.rb +91 -0
  19. data/app/models/session.rb +366 -21
  20. data/config/application.rb +2 -0
  21. data/config/initializers/event_subscribers.rb +0 -1
  22. data/config/routes.rb +0 -6
  23. data/db/migrate/20260313010000_add_status_to_events.rb +8 -0
  24. data/db/migrate/20260313020000_add_processing_to_sessions.rb +7 -0
  25. data/db/migrate/20260314075248_add_subagent_support_to_sessions.rb +6 -0
  26. data/db/migrate/20260314112417_add_granted_tools_to_sessions.rb +5 -0
  27. data/db/migrate/20260314140000_add_name_to_sessions.rb +7 -0
  28. data/db/migrate/20260314150000_add_viewport_event_ids_to_sessions.rb +7 -0
  29. data/db/migrate/20260315100000_add_active_skills_to_sessions.rb +7 -0
  30. data/db/migrate/20260315140843_create_goals.rb +16 -0
  31. data/db/migrate/20260315144837_add_completed_at_to_goals.rb +5 -0
  32. data/db/migrate/20260315191105_add_active_workflow_to_sessions.rb +5 -0
  33. data/lib/agent_loop.rb +65 -6
  34. data/lib/agents/definition.rb +116 -0
  35. data/lib/agents/registry.rb +106 -0
  36. data/lib/analytical_brain/runner.rb +276 -0
  37. data/lib/analytical_brain/tools/activate_skill.rb +52 -0
  38. data/lib/analytical_brain/tools/deactivate_skill.rb +43 -0
  39. data/lib/analytical_brain/tools/deactivate_workflow.rb +34 -0
  40. data/lib/analytical_brain/tools/everything_is_ready.rb +28 -0
  41. data/lib/analytical_brain/tools/finish_goal.rb +62 -0
  42. data/lib/analytical_brain/tools/read_workflow.rb +58 -0
  43. data/lib/analytical_brain/tools/rename_session.rb +63 -0
  44. data/lib/analytical_brain/tools/set_goal.rb +60 -0
  45. data/lib/analytical_brain/tools/update_goal.rb +60 -0
  46. data/lib/analytical_brain.rb +23 -0
  47. data/lib/anima/cli/mcp/secrets.rb +76 -0
  48. data/lib/anima/cli/mcp.rb +197 -0
  49. data/lib/anima/cli.rb +5 -40
  50. data/lib/anima/installer.rb +168 -0
  51. data/lib/anima/settings.rb +226 -0
  52. data/lib/anima/version.rb +1 -1
  53. data/lib/anima.rb +9 -0
  54. data/lib/credential_store.rb +103 -0
  55. data/lib/environment_probe.rb +232 -0
  56. data/lib/events/subscribers/persister.rb +1 -0
  57. data/lib/events/user_message.rb +17 -0
  58. data/lib/llm/client.rb +29 -10
  59. data/lib/mcp/client_manager.rb +86 -0
  60. data/lib/mcp/config.rb +213 -0
  61. data/lib/mcp/health_check.rb +77 -0
  62. data/lib/mcp/secrets.rb +73 -0
  63. data/lib/mcp/stdio_transport.rb +206 -0
  64. data/lib/providers/anthropic.rb +11 -20
  65. data/lib/shell_session.rb +11 -10
  66. data/lib/skills/definition.rb +97 -0
  67. data/lib/skills/registry.rb +105 -0
  68. data/lib/tools/edit.rb +226 -0
  69. data/lib/tools/mcp_tool.rb +114 -0
  70. data/lib/tools/read.rb +151 -0
  71. data/lib/tools/registry.rb +14 -12
  72. data/lib/tools/request_feature.rb +121 -0
  73. data/lib/tools/return_result.rb +81 -0
  74. data/lib/tools/spawn_specialist.rb +109 -0
  75. data/lib/tools/spawn_subagent.rb +111 -0
  76. data/lib/tools/subagent_prompts.rb +12 -0
  77. data/lib/tools/web_get.rb +8 -9
  78. data/lib/tools/write.rb +86 -0
  79. data/lib/tui/app.rb +985 -26
  80. data/lib/tui/cable_client.rb +69 -31
  81. data/lib/tui/message_store.rb +103 -8
  82. data/lib/tui/screens/chat.rb +293 -45
  83. data/lib/workflows/definition.rb +97 -0
  84. data/lib/workflows/registry.rb +89 -0
  85. data/skills/activerecord/SKILL.md +255 -0
  86. data/skills/activerecord/examples/associations/association_extensions.rb +298 -0
  87. data/skills/activerecord/examples/associations/basic_associations.rb +118 -0
  88. data/skills/activerecord/examples/associations/counter_caches.rb +215 -0
  89. data/skills/activerecord/examples/associations/polymorphic_associations.rb +217 -0
  90. data/skills/activerecord/examples/associations/self_referential.rb +302 -0
  91. data/skills/activerecord/examples/associations/through_associations.rb +203 -0
  92. data/skills/activerecord/examples/basics/crud_operations.rb +209 -0
  93. data/skills/activerecord/examples/basics/dirty_tracking.rb +218 -0
  94. data/skills/activerecord/examples/basics/inheritance.rb +377 -0
  95. data/skills/activerecord/examples/basics/type_casting.rb +317 -0
  96. data/skills/activerecord/examples/callbacks/alternatives_to_callbacks.rb +447 -0
  97. data/skills/activerecord/examples/callbacks/conditional_callbacks.rb +353 -0
  98. data/skills/activerecord/examples/callbacks/lifecycle_callbacks.rb +280 -0
  99. data/skills/activerecord/examples/callbacks/transaction_callbacks.rb +340 -0
  100. data/skills/activerecord/examples/migrations/indexes_and_constraints.rb +337 -0
  101. data/skills/activerecord/examples/migrations/reversible_patterns.rb +403 -0
  102. data/skills/activerecord/examples/migrations/safe_patterns.rb +420 -0
  103. data/skills/activerecord/examples/migrations/schema_changes.rb +277 -0
  104. data/skills/activerecord/examples/querying/batch_processing.rb +226 -0
  105. data/skills/activerecord/examples/querying/eager_loading.rb +259 -0
  106. data/skills/activerecord/examples/querying/finder_methods.rb +170 -0
  107. data/skills/activerecord/examples/querying/optimization.rb +275 -0
  108. data/skills/activerecord/examples/querying/scopes.rb +260 -0
  109. data/skills/activerecord/examples/validations/built_in_validators.rb +277 -0
  110. data/skills/activerecord/examples/validations/conditional_validations.rb +288 -0
  111. data/skills/activerecord/examples/validations/custom_validators.rb +381 -0
  112. data/skills/activerecord/examples/validations/database_constraints.rb +432 -0
  113. data/skills/activerecord/examples/validations/validation_contexts.rb +367 -0
  114. data/skills/activerecord/references/associations.md +709 -0
  115. data/skills/activerecord/references/basics.md +622 -0
  116. data/skills/activerecord/references/callbacks.md +738 -0
  117. data/skills/activerecord/references/migrations.md +657 -0
  118. data/skills/activerecord/references/querying.md +655 -0
  119. data/skills/activerecord/references/validations.md +596 -0
  120. data/skills/dragonruby/SKILL.md +250 -0
  121. data/skills/dragonruby/examples/audio/audio_events.rb +55 -0
  122. data/skills/dragonruby/examples/audio/background_music.rb +29 -0
  123. data/skills/dragonruby/examples/audio/crossfade.rb +51 -0
  124. data/skills/dragonruby/examples/audio/music_controls.rb +51 -0
  125. data/skills/dragonruby/examples/audio/sound_effects.rb +30 -0
  126. data/skills/dragonruby/examples/core/coordinate_system.rb +27 -0
  127. data/skills/dragonruby/examples/core/hello_world.rb +24 -0
  128. data/skills/dragonruby/examples/core/labels.rb +22 -0
  129. data/skills/dragonruby/examples/core/sprites.rb +35 -0
  130. data/skills/dragonruby/examples/core/state_management.rb +29 -0
  131. data/skills/dragonruby/examples/distribution/background_pause.rb +42 -0
  132. data/skills/dragonruby/examples/distribution/build_workflow.sh +26 -0
  133. data/skills/dragonruby/examples/distribution/cvars_production.txt +16 -0
  134. data/skills/dragonruby/examples/distribution/game_metadata_hd.txt +23 -0
  135. data/skills/dragonruby/examples/distribution/game_metadata_minimal.txt +9 -0
  136. data/skills/dragonruby/examples/distribution/game_metadata_mobile.txt +31 -0
  137. data/skills/dragonruby/examples/distribution/platform_detection.rb +36 -0
  138. data/skills/dragonruby/examples/distribution/steam_metadata.txt +19 -0
  139. data/skills/dragonruby/examples/entities/collision_detection.rb +43 -0
  140. data/skills/dragonruby/examples/entities/entity_lifecycle.rb +68 -0
  141. data/skills/dragonruby/examples/entities/entity_storage.rb +38 -0
  142. data/skills/dragonruby/examples/entities/factory_methods.rb +45 -0
  143. data/skills/dragonruby/examples/entities/random_spawning.rb +50 -0
  144. data/skills/dragonruby/examples/game-logic/reset_patterns.rb +98 -0
  145. data/skills/dragonruby/examples/game-logic/save_load.rb +101 -0
  146. data/skills/dragonruby/examples/game-logic/scoring.rb +104 -0
  147. data/skills/dragonruby/examples/game-logic/state_transitions.rb +103 -0
  148. data/skills/dragonruby/examples/game-logic/timers.rb +87 -0
  149. data/skills/dragonruby/examples/input/action_triggers.rb +36 -0
  150. data/skills/dragonruby/examples/input/analog_movement.rb +28 -0
  151. data/skills/dragonruby/examples/input/controller_input.rb +28 -0
  152. data/skills/dragonruby/examples/input/directional_input.rb +24 -0
  153. data/skills/dragonruby/examples/input/keyboard_input.rb +28 -0
  154. data/skills/dragonruby/examples/input/mouse_click.rb +26 -0
  155. data/skills/dragonruby/examples/input/movement_with_bounds.rb +22 -0
  156. data/skills/dragonruby/examples/input/normalized_movement.rb +32 -0
  157. data/skills/dragonruby/examples/rendering/frame_animation.rb +32 -0
  158. data/skills/dragonruby/examples/rendering/labels.rb +32 -0
  159. data/skills/dragonruby/examples/rendering/layering.rb +51 -0
  160. data/skills/dragonruby/examples/rendering/solids.rb +61 -0
  161. data/skills/dragonruby/examples/rendering/sprites.rb +33 -0
  162. data/skills/dragonruby/examples/rendering/spritesheet_animation.rb +39 -0
  163. data/skills/dragonruby/examples/scenes/case_dispatch.rb +60 -0
  164. data/skills/dragonruby/examples/scenes/class_based.rb +150 -0
  165. data/skills/dragonruby/examples/scenes/pause_overlay.rb +100 -0
  166. data/skills/dragonruby/examples/scenes/safe_transitions.rb +68 -0
  167. data/skills/dragonruby/examples/scenes/scene_transitions.rb +98 -0
  168. data/skills/dragonruby/examples/scenes/send_dispatch.rb +88 -0
  169. data/skills/dragonruby/references/audio.md +396 -0
  170. data/skills/dragonruby/references/core.md +385 -0
  171. data/skills/dragonruby/references/distribution.md +434 -0
  172. data/skills/dragonruby/references/entities.md +516 -0
  173. data/skills/dragonruby/references/game-logic/persistence.md +386 -0
  174. data/skills/dragonruby/references/game-logic/state.md +389 -0
  175. data/skills/dragonruby/references/input.md +414 -0
  176. data/skills/dragonruby/references/rendering/animation.md +467 -0
  177. data/skills/dragonruby/references/rendering/primitives.md +403 -0
  178. data/skills/dragonruby/references/scenes.md +443 -0
  179. data/skills/draper-decorators/SKILL.md +344 -0
  180. data/skills/draper-decorators/examples/application_decorator.rb +61 -0
  181. data/skills/draper-decorators/examples/decorator_spec.rb +253 -0
  182. data/skills/draper-decorators/examples/model_decorator.rb +152 -0
  183. data/skills/draper-decorators/references/anti-patterns.md +640 -0
  184. data/skills/draper-decorators/references/patterns.md +507 -0
  185. data/skills/draper-decorators/references/testing.md +559 -0
  186. data/skills/gh-issue.md +182 -0
  187. data/skills/mcp-server/SKILL.md +177 -0
  188. data/skills/mcp-server/examples/dynamic_tools.rb +36 -0
  189. data/skills/mcp-server/examples/file_manager_tool.rb +85 -0
  190. data/skills/mcp-server/examples/http_client.rb +48 -0
  191. data/skills/mcp-server/examples/http_server.rb +97 -0
  192. data/skills/mcp-server/examples/rails_integration.rb +88 -0
  193. data/skills/mcp-server/examples/stdio_server.rb +108 -0
  194. data/skills/mcp-server/examples/streaming_client.rb +95 -0
  195. data/skills/mcp-server/references/gotchas.md +183 -0
  196. data/skills/mcp-server/references/prompts.md +98 -0
  197. data/skills/mcp-server/references/resources.md +53 -0
  198. data/skills/mcp-server/references/server.md +140 -0
  199. data/skills/mcp-server/references/tools.md +146 -0
  200. data/skills/mcp-server/references/transport.md +104 -0
  201. data/skills/ratatui-ruby/SKILL.md +315 -0
  202. data/skills/ratatui-ruby/references/core-concepts.md +340 -0
  203. data/skills/ratatui-ruby/references/events.md +387 -0
  204. data/skills/ratatui-ruby/references/frameworks.md +522 -0
  205. data/skills/ratatui-ruby/references/layout.md +423 -0
  206. data/skills/ratatui-ruby/references/styling.md +268 -0
  207. data/skills/ratatui-ruby/references/testing.md +433 -0
  208. data/skills/ratatui-ruby/references/widgets.md +532 -0
  209. data/skills/rspec/SKILL.md +340 -0
  210. data/skills/rspec/examples/core/basic_structure.rb +69 -0
  211. data/skills/rspec/examples/core/configuration.rb +126 -0
  212. data/skills/rspec/examples/core/hooks.rb +126 -0
  213. data/skills/rspec/examples/core/memoized_helpers.rb +139 -0
  214. data/skills/rspec/examples/core/metadata_filtering.rb +144 -0
  215. data/skills/rspec/examples/core/shared_examples.rb +145 -0
  216. data/skills/rspec/examples/factory_bot/associations.rb +314 -0
  217. data/skills/rspec/examples/factory_bot/build_strategies.rb +272 -0
  218. data/skills/rspec/examples/factory_bot/callbacks.rb +320 -0
  219. data/skills/rspec/examples/factory_bot/custom_construction.rb +328 -0
  220. data/skills/rspec/examples/factory_bot/factory_definition.rb +191 -0
  221. data/skills/rspec/examples/factory_bot/inheritance.rb +314 -0
  222. data/skills/rspec/examples/factory_bot/traits.rb +293 -0
  223. data/skills/rspec/examples/factory_bot/transients.rb +229 -0
  224. data/skills/rspec/examples/matchers/change.rb +115 -0
  225. data/skills/rspec/examples/matchers/collections.rb +154 -0
  226. data/skills/rspec/examples/matchers/comparisons.rb +79 -0
  227. data/skills/rspec/examples/matchers/composing.rb +155 -0
  228. data/skills/rspec/examples/matchers/custom_matchers.rb +197 -0
  229. data/skills/rspec/examples/matchers/equality.rb +58 -0
  230. data/skills/rspec/examples/matchers/errors.rb +136 -0
  231. data/skills/rspec/examples/matchers/output.rb +103 -0
  232. data/skills/rspec/examples/matchers/predicates.rb +87 -0
  233. data/skills/rspec/examples/matchers/truthiness.rb +101 -0
  234. data/skills/rspec/examples/matchers/types.rb +82 -0
  235. data/skills/rspec/examples/matchers/yield.rb +147 -0
  236. data/skills/rspec/examples/mocks/any_instance.rb +172 -0
  237. data/skills/rspec/examples/mocks/argument_matchers.rb +206 -0
  238. data/skills/rspec/examples/mocks/constants.rb +177 -0
  239. data/skills/rspec/examples/mocks/doubles.rb +139 -0
  240. data/skills/rspec/examples/mocks/expectations.rb +137 -0
  241. data/skills/rspec/examples/mocks/message_chains.rb +173 -0
  242. data/skills/rspec/examples/mocks/ordering.rb +144 -0
  243. data/skills/rspec/examples/mocks/receive_counts.rb +181 -0
  244. data/skills/rspec/examples/mocks/responses.rb +223 -0
  245. data/skills/rspec/examples/mocks/spies.rb +149 -0
  246. data/skills/rspec/examples/mocks/stubbing.rb +133 -0
  247. data/skills/rspec/examples/rails/channels.rb +250 -0
  248. data/skills/rspec/examples/rails/controller_specs.rb +302 -0
  249. data/skills/rspec/examples/rails/helper_specs.rb +245 -0
  250. data/skills/rspec/examples/rails/job_specs.rb +256 -0
  251. data/skills/rspec/examples/rails/mailer_specs.rb +228 -0
  252. data/skills/rspec/examples/rails/matchers.rb +374 -0
  253. data/skills/rspec/examples/rails/model_specs.rb +193 -0
  254. data/skills/rspec/examples/rails/request_specs.rb +275 -0
  255. data/skills/rspec/examples/rails/routing_specs.rb +276 -0
  256. data/skills/rspec/examples/rails/system_specs.rb +294 -0
  257. data/skills/rspec/examples/rails/transactions.rb +254 -0
  258. data/skills/rspec/examples/rails/view_specs.rb +252 -0
  259. data/skills/rspec/references/core.md +816 -0
  260. data/skills/rspec/references/factory_bot.md +641 -0
  261. data/skills/rspec/references/matchers.md +516 -0
  262. data/skills/rspec/references/mocks.md +381 -0
  263. data/skills/rspec/references/rails.md +528 -0
  264. data/templates/soul.md +40 -0
  265. data/workflows/commit.md +45 -0
  266. data/workflows/create_handoff.md +98 -0
  267. data/workflows/create_note.md +82 -0
  268. data/workflows/create_plan.md +457 -0
  269. data/workflows/decompose_ticket.md +109 -0
  270. data/workflows/feature.md +91 -0
  271. data/workflows/implement_plan.md +87 -0
  272. data/workflows/iterate_plan.md +247 -0
  273. data/workflows/research_codebase.md +210 -0
  274. data/workflows/resume_handoff.md +217 -0
  275. data/workflows/review_pr.md +320 -0
  276. data/workflows/thoughts_init.md +71 -0
  277. data/workflows/validate_plan.md +166 -0
  278. metadata +290 -3
  279. data/app/controllers/api/sessions_controller.rb +0 -25
  280. data/lib/events/subscribers/action_cable_bridge.rb +0 -59
@@ -0,0 +1,622 @@
1
+ # ActiveRecord Basics Reference
2
+
3
+ Comprehensive reference for ActiveRecord conventions, CRUD operations, attribute handling, and inheritance patterns.
4
+
5
+ ## Naming Conventions
6
+
7
+ ActiveRecord uses "convention over configuration" - follow these patterns and Rails works automatically.
8
+
9
+ ### Models and Tables
10
+
11
+ | Ruby Class | Database Table | Notes |
12
+ |------------|----------------|-------|
13
+ | `User` | `users` | Automatic pluralization |
14
+ | `Person` | `people` | Irregular plurals handled |
15
+ | `BookClub` | `book_clubs` | CamelCase → snake_case |
16
+ | `Admin::User` | `admin_users` | Namespace becomes prefix |
17
+
18
+ Override when needed:
19
+
20
+ ```ruby
21
+ class Product < ApplicationRecord
22
+ self.table_name = "inventory_items"
23
+ end
24
+ ```
25
+
26
+ ### Primary Keys
27
+
28
+ Default: `id` column (bigint for PostgreSQL/MySQL, integer for SQLite).
29
+
30
+ ```ruby
31
+ class Product < ApplicationRecord
32
+ self.primary_key = "product_id" # Override default
33
+ end
34
+ ```
35
+
36
+ ### Foreign Keys
37
+
38
+ Pattern: `singularized_table_name_id`
39
+
40
+ | Association | Column Name | References |
41
+ |-------------|-------------|------------|
42
+ | `belongs_to :user` | `user_id` | `users.id` |
43
+ | `belongs_to :category` | `category_id` | `categories.id` |
44
+ | `belongs_to :admin_user` | `admin_user_id` | `admin_users.id` |
45
+
46
+ Override when needed:
47
+
48
+ ```ruby
49
+ class Order < ApplicationRecord
50
+ belongs_to :customer, class_name: "User", foreign_key: "purchaser_id"
51
+ end
52
+ ```
53
+
54
+ ### Magic Columns
55
+
56
+ These optional columns add automatic functionality:
57
+
58
+ | Column | Type | Behavior |
59
+ |--------|------|----------|
60
+ | `created_at` | datetime | Set once on record creation |
61
+ | `updated_at` | datetime | Updated on creation and every update |
62
+ | `lock_version` | integer | Enables optimistic locking |
63
+ | `type` | string | Single Table Inheritance discriminator |
64
+
65
+ ## CRUD Operations
66
+
67
+ ### Create
68
+
69
+ **`new` + `save`** - Two-step creation:
70
+
71
+ ```ruby
72
+ user = User.new
73
+ user.name = "Alice"
74
+ user.email = "alice@example.com"
75
+ user.save # Returns true/false
76
+ ```
77
+
78
+ **`create`** - One-step creation:
79
+
80
+ ```ruby
81
+ user = User.create(name: "Alice", email: "alice@example.com")
82
+ # Returns user object (check user.persisted? or user.errors)
83
+ ```
84
+
85
+ **Bang methods** - Raise on failure:
86
+
87
+ ```ruby
88
+ user = User.create!(name: "Alice") # Raises ActiveRecord::RecordInvalid
89
+ user.save! # Raises ActiveRecord::RecordInvalid
90
+ ```
91
+
92
+ **Bulk insert** (skips validations and callbacks):
93
+
94
+ ```ruby
95
+ User.insert_all([
96
+ { name: "Alice", email: "alice@example.com" },
97
+ { name: "Bob", email: "bob@example.com" }
98
+ ])
99
+ ```
100
+
101
+ ### Read
102
+
103
+ | Method | Returns | Not Found |
104
+ |--------|---------|-----------|
105
+ | `find(id)` | Record or raises | `RecordNotFound` (404) |
106
+ | `find_by(attr: val)` | Record or `nil` | `nil` |
107
+ | `where(conditions)` | `Relation` | Empty relation `[]` |
108
+
109
+ ```ruby
110
+ # find - use when record MUST exist (controllers)
111
+ user = User.find(params[:id])
112
+
113
+ # find_by - use when missing record is expected
114
+ user = User.find_by(email: params[:email])
115
+ return "Not found" if user.nil?
116
+
117
+ # where - chainable queries
118
+ User.where(active: true).order(:name).limit(10)
119
+ ```
120
+
121
+ ### Update
122
+
123
+ | Method | Validations | Callbacks | Updates `updated_at` |
124
+ |--------|------------|-----------|---------------------|
125
+ | `update` | Yes | Yes | Yes |
126
+ | `update!` | Yes (raises) | Yes | Yes |
127
+ | `update_attribute` | No | Yes | Yes |
128
+ | `update_column` | No | No | No |
129
+ | `update_columns` | No | No | No |
130
+
131
+ ```ruby
132
+ # Standard update (recommended)
133
+ user.update(name: "Bob")
134
+ user.update!(name: "Bob") # Raises on validation failure
135
+
136
+ # Skip validations (use sparingly)
137
+ user.update_attribute(:verified, true)
138
+
139
+ # Direct SQL (dangerous - skips everything)
140
+ user.update_column(:login_count, 5)
141
+ user.update_columns(login_count: 5, last_login: Time.current)
142
+ ```
143
+
144
+ ### Delete vs Destroy
145
+
146
+ | Method | Callbacks | Dependent Associations | Returns |
147
+ |--------|-----------|------------------------|---------|
148
+ | `destroy` | Yes | Handled | Frozen object |
149
+ | `delete` | No | Ignored | Frozen object |
150
+
151
+ ```ruby
152
+ # destroy - triggers callbacks and dependent associations
153
+ user.destroy
154
+ # Runs: before_destroy, destroy dependent children, after_destroy
155
+
156
+ # delete - direct SQL DELETE
157
+ user.delete
158
+ # Only runs: DELETE FROM users WHERE id = 123
159
+
160
+ # Bulk operations
161
+ User.where(status: :inactive).destroy_all # With callbacks
162
+ User.where(status: :inactive).delete_all # Without callbacks
163
+ ```
164
+
165
+ **Best Practice**: Use `destroy` unless you need raw performance for bulk operations.
166
+
167
+ ### Find or Create Patterns
168
+
169
+ ```ruby
170
+ # Find or create by specific attributes
171
+ user = User.find_or_create_by(email: "alice@example.com")
172
+
173
+ # With block for additional attributes
174
+ user = User.find_or_create_by(email: "alice@example.com") do |u|
175
+ u.name = "Alice"
176
+ u.role = :member
177
+ end
178
+
179
+ # find_or_initialize_by - doesn't save
180
+ user = User.find_or_initialize_by(email: "alice@example.com")
181
+ user.save if user.new_record?
182
+
183
+ # create_or_find_by - handles race conditions (unique constraint required)
184
+ user = User.create_or_find_by(email: "alice@example.com")
185
+ ```
186
+
187
+ ## Dirty Tracking
188
+
189
+ Track attribute changes before and after saves.
190
+
191
+ ### Before Save (Pending Changes)
192
+
193
+ ```ruby
194
+ user.name = "Bob"
195
+
196
+ user.changed? # => true
197
+ user.name_changed? # => true
198
+ user.name_was # => "Alice" (original value)
199
+ user.name_change # => ["Alice", "Bob"]
200
+ user.changes # => {"name" => ["Alice", "Bob"]}
201
+
202
+ # Check what will be saved
203
+ user.will_save_change_to_name? # => true
204
+ user.changes_to_save # => {"name" => ["Alice", "Bob"]}
205
+ user.name_in_database # => "Alice"
206
+ ```
207
+
208
+ ### After Save (Previous Changes)
209
+
210
+ ```ruby
211
+ user.save
212
+
213
+ user.saved_change_to_name? # => true
214
+ user.saved_change_to_name # => ["Alice", "Bob"]
215
+ user.name_before_last_save # => "Alice"
216
+ user.name_previously_was # => "Alice"
217
+ user.previous_changes # => {"name" => ["Alice", "Bob"]}
218
+ ```
219
+
220
+ ### Reverting Changes
221
+
222
+ ```ruby
223
+ user.name = "Bob"
224
+ user.restore_name! # Reverts to original
225
+ user.name # => "Alice"
226
+
227
+ user.restore_attributes # Reverts all changes
228
+ ```
229
+
230
+ ### Using in Callbacks
231
+
232
+ ```ruby
233
+ class User < ApplicationRecord
234
+ after_save :notify_if_email_changed
235
+
236
+ private
237
+
238
+ def notify_if_email_changed
239
+ if saved_change_to_email?
240
+ UserMailer.email_changed(self, email_before_last_save).deliver_later
241
+ end
242
+ end
243
+ end
244
+ ```
245
+
246
+ ### Partial Updates
247
+
248
+ By default, Rails only sends changed attributes in UPDATE queries:
249
+
250
+ ```ruby
251
+ # With partial_updates enabled (default)
252
+ user.name = "Bob"
253
+ user.save
254
+ # SQL: UPDATE users SET name = 'Bob', updated_at = '...' WHERE id = 1
255
+
256
+ # Disable partial updates (sends all attributes)
257
+ ActiveRecord::Base.partial_updates = false
258
+ ```
259
+
260
+ ## Type Casting and Serialization
261
+
262
+ ### Attribute API
263
+
264
+ Define or override attribute types:
265
+
266
+ ```ruby
267
+ class Product < ApplicationRecord
268
+ attribute :price, :decimal, precision: 8, scale: 2
269
+ attribute :metadata, :json
270
+ attribute :published_on, :date
271
+ attribute :active, :boolean, default: true
272
+ end
273
+ ```
274
+
275
+ Built-in types: `:boolean`, `:date`, `:datetime`, `:decimal`, `:float`, `:integer`, `:string`, `:text`, `:time`, `:json`
276
+
277
+ ### Custom Types
278
+
279
+ ```ruby
280
+ class MoneyType < ActiveRecord::Type::Value
281
+ def cast(value)
282
+ return nil if value.blank?
283
+ BigDecimal(value.to_s.gsub(/[^\d.]/, ''))
284
+ end
285
+
286
+ def serialize(value)
287
+ value&.to_s
288
+ end
289
+ end
290
+
291
+ ActiveRecord::Type.register(:money, MoneyType)
292
+
293
+ class Product < ApplicationRecord
294
+ attribute :price, :money
295
+ end
296
+ ```
297
+
298
+ ### Serialize (Legacy)
299
+
300
+ Store Ruby objects in text columns:
301
+
302
+ ```ruby
303
+ class User < ApplicationRecord
304
+ # Rails 7.2+ syntax (coder as keyword)
305
+ serialize :preferences, coder: JSON, type: Hash
306
+ serialize :tags, coder: YAML, type: Array
307
+ end
308
+ ```
309
+
310
+ ### Store Accessor
311
+
312
+ Key-value storage with accessors:
313
+
314
+ ```ruby
315
+ class User < ApplicationRecord
316
+ # For serialized text column
317
+ store :settings, accessors: [:color, :language], coder: JSON
318
+
319
+ # For native JSON column (no serialization overhead)
320
+ store_accessor :preferences, :theme, :notifications
321
+ end
322
+
323
+ user.color = "blue"
324
+ user.theme = "dark"
325
+ user.settings # => {"color" => "blue"}
326
+ ```
327
+
328
+ **Best Practice**: Use native JSON columns (`jsonb` in PostgreSQL) with `store_accessor` instead of `serialize` for better performance and queryability.
329
+
330
+ ### Accessing Raw Values
331
+
332
+ ```ruby
333
+ user.created_at # => Mon, 01 Jan 2024 00:00:00 UTC
334
+ user.created_at_before_type_cast # => "2024-01-01 00:00:00"
335
+ user.read_attribute_before_type_cast(:created_at)
336
+ ```
337
+
338
+ ## Single Table Inheritance (STI)
339
+
340
+ Store multiple model types in one table using a `type` column.
341
+
342
+ ### Setup
343
+
344
+ ```ruby
345
+ # Migration
346
+ class CreateVehicles < ActiveRecord::Migration[7.2]
347
+ def change
348
+ create_table :vehicles do |t|
349
+ t.string :type # Required for STI
350
+ t.string :make
351
+ t.string :model
352
+ t.integer :wheels
353
+ t.float :wing_span
354
+ t.timestamps
355
+ end
356
+ end
357
+ end
358
+
359
+ # Models
360
+ class Vehicle < ApplicationRecord; end
361
+ class Car < Vehicle; end
362
+ class Truck < Vehicle; end
363
+ class Airplane < Vehicle; end
364
+ ```
365
+
366
+ ### Usage
367
+
368
+ ```ruby
369
+ car = Car.create(make: "Toyota", model: "Camry", wheels: 4)
370
+ car.type # => "Car"
371
+
372
+ Car.all # SELECT * FROM vehicles WHERE type = 'Car'
373
+ Vehicle.all # SELECT * FROM vehicles (all types)
374
+ ```
375
+
376
+ ### When to Use STI
377
+
378
+ | Use STI When | Avoid STI When |
379
+ |--------------|----------------|
380
+ | Subclasses share 80%+ attributes | Subclasses have divergent attributes |
381
+ | 2-4 subclasses | Many subclasses (5+) |
382
+ | Shared behavior across types | Types need different validations |
383
+ | Need to query all types together | Types rarely queried together |
384
+
385
+ ### Common Pitfalls
386
+
387
+ **1. Sparse Tables**:
388
+ ```ruby
389
+ # BAD - most columns null for most types
390
+ # vehicles: id, type, wheels, wing_span, sail_size, cargo_capacity
391
+ ```
392
+
393
+ **2. Polymorphic + STI Conflict**:
394
+ ```ruby
395
+ # BAD - saves base class name, breaks subclass associations
396
+ class Comment < ApplicationRecord
397
+ belongs_to :commentable, polymorphic: true
398
+ end
399
+
400
+ car.comments # May not work as expected
401
+ ```
402
+
403
+ **3. Type Changes**:
404
+ ```ruby
405
+ # Changing type is complex and error-prone
406
+ user.update(type: "Admin") # Requires validations for both types to pass
407
+ ```
408
+
409
+ ### Alternatives to STI
410
+
411
+ **Delegated Types** (Rails 6.1+):
412
+
413
+ ```ruby
414
+ class Entry < ApplicationRecord
415
+ delegated_type :entryable, types: %w[Message Comment]
416
+ end
417
+
418
+ class Message < ApplicationRecord
419
+ has_one :entry, as: :entryable, touch: true
420
+ end
421
+
422
+ class Comment < ApplicationRecord
423
+ has_one :entry, as: :entryable, touch: true
424
+ end
425
+ ```
426
+
427
+ **Separate Tables with Shared Concerns**:
428
+
429
+ ```ruby
430
+ module Drivable
431
+ extend ActiveSupport::Concern
432
+
433
+ included do
434
+ validates :make, :model, presence: true
435
+ end
436
+
437
+ def description
438
+ "#{make} #{model}"
439
+ end
440
+ end
441
+
442
+ class Car < ApplicationRecord
443
+ include Drivable
444
+ end
445
+
446
+ class Motorcycle < ApplicationRecord
447
+ include Drivable
448
+ end
449
+ ```
450
+
451
+ ### STI Decision Tree
452
+
453
+ ```
454
+ Do models share 80%+ attributes?
455
+ ├── No → Use separate tables or delegated types
456
+ └── Yes
457
+ └── Are there 4 or fewer subclasses?
458
+ ├── No → Use separate tables or delegated types
459
+ └── Yes
460
+ └── Do they share most behavior?
461
+ ├── No → Use separate tables with concerns
462
+ └── Yes → STI is appropriate
463
+ ```
464
+
465
+ ## Anti-Patterns
466
+
467
+ ### CRUD Anti-Patterns
468
+
469
+ ```ruby
470
+ # BAD - using delete when you need callbacks
471
+ user.delete # Orphans dependent records
472
+
473
+ # GOOD
474
+ user.destroy
475
+
476
+ # BAD - using update_column to skip validations
477
+ user.update_column(:email, "invalid") # No validation!
478
+
479
+ # GOOD - if you must skip validations, be explicit
480
+ user.update_attribute(:email, "valid@example.com")
481
+
482
+ # BAD - find for optional records
483
+ user = User.find(params[:user_id]) # Raises if not found
484
+
485
+ # GOOD
486
+ user = User.find_by(id: params[:user_id])
487
+ return "User not found" unless user
488
+ ```
489
+
490
+ ### Dirty Tracking Anti-Patterns
491
+
492
+ ```ruby
493
+ # BAD - checking changes in performance-critical code
494
+ users.each do |user|
495
+ user.update(processed: true)
496
+ log_changes(user.previous_changes) # Overhead for each user
497
+ end
498
+
499
+ # GOOD - bulk update without instantiation
500
+ User.where(id: user_ids).update_all(processed: true)
501
+ ```
502
+
503
+ ### Type Casting Anti-Patterns
504
+
505
+ ```ruby
506
+ # BAD - serialize with JSON column (double serialization)
507
+ class User < ApplicationRecord
508
+ serialize :metadata, coder: JSON # column already jsonb
509
+ end
510
+
511
+ # GOOD - use store_accessor directly
512
+ class User < ApplicationRecord
513
+ store_accessor :metadata, :key1, :key2
514
+ end
515
+
516
+ # BAD - Rails 7.2+ positional coder
517
+ serialize :preferences, JSON # Deprecated
518
+
519
+ # GOOD
520
+ serialize :preferences, coder: JSON
521
+ ```
522
+
523
+ ### STI Anti-Patterns
524
+
525
+ ```ruby
526
+ # BAD - wide sparse table
527
+ class Vehicle < ApplicationRecord
528
+ # Has: type, wheels, wing_span, sail_size, cargo_capacity, pedals
529
+ end
530
+
531
+ # BAD - using type column for non-STI
532
+ class Product < ApplicationRecord
533
+ # type column stores "clothing", "electronics" but no subclasses
534
+ end
535
+
536
+ # GOOD - use explicit column for categorization
537
+ class Product < ApplicationRecord
538
+ enum :category, { clothing: 0, electronics: 1 }
539
+ end
540
+ ```
541
+
542
+ ## Model Lifecycle
543
+
544
+ ### Object States
545
+
546
+ ```ruby
547
+ user = User.new
548
+ user.new_record? # => true
549
+ user.persisted? # => false
550
+ user.destroyed? # => false
551
+ user.previously_new_record? # => false
552
+
553
+ user.save
554
+ user.new_record? # => false
555
+ user.persisted? # => true
556
+ user.previously_new_record? # => true
557
+
558
+ user.destroy
559
+ user.destroyed? # => true
560
+ user.persisted? # => false
561
+ user.frozen? # => true
562
+ ```
563
+
564
+ ### Reload
565
+
566
+ ```ruby
567
+ user = User.find(1)
568
+ user.name = "Changed"
569
+ user.reload # Refreshes from database, clears dirty tracking
570
+ user.name # => Original value from database
571
+ ```
572
+
573
+ ### Touch
574
+
575
+ ```ruby
576
+ user.touch # Updates updated_at
577
+ user.touch(:last_login) # Updates last_login AND updated_at
578
+ user.touch(time: 1.hour.ago)
579
+ ```
580
+
581
+ ## Performance Tips
582
+
583
+ ### Avoid N+1 with Associations
584
+
585
+ ```ruby
586
+ # BAD - N+1 queries
587
+ User.all.each { |u| puts u.posts.count }
588
+
589
+ # GOOD - eager loading
590
+ User.includes(:posts).each { |u| puts u.posts.size }
591
+ ```
592
+
593
+ ### Use pluck for Single Columns
594
+
595
+ ```ruby
596
+ # BAD - instantiates all models
597
+ User.all.map(&:email)
598
+
599
+ # GOOD - returns array of values
600
+ User.pluck(:email)
601
+ ```
602
+
603
+ ### Use select for Limited Attributes
604
+
605
+ ```ruby
606
+ # BAD - loads all columns
607
+ User.all.each { |u| puts u.name }
608
+
609
+ # GOOD - loads only needed columns
610
+ User.select(:id, :name).each { |u| puts u.name }
611
+ ```
612
+
613
+ ### Batch Processing
614
+
615
+ ```ruby
616
+ # BAD - loads all into memory
617
+ User.all.each { |u| process(u) }
618
+
619
+ # GOOD - processes in batches of 1000
620
+ User.find_each { |u| process(u) }
621
+ User.find_each(batch_size: 500) { |u| process(u) }
622
+ ```