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,641 @@
1
+ # FactoryBot Reference
2
+
3
+ Comprehensive guide for test data preparation with FactoryBot in RSpec.
4
+
5
+ ## Build Strategies
6
+
7
+ ### Overview
8
+
9
+ | Strategy | Persisted | Database | Use Case |
10
+ |----------|-----------|----------|----------|
11
+ | `build` | No | No | Unit tests, method logic |
12
+ | `create` | Yes | Yes | Integration tests, associations |
13
+ | `build_stubbed` | Fake | No | Fastest, isolated unit tests |
14
+ | `attributes_for` | N/A | No | Controller params, form data |
15
+
16
+ ### build
17
+
18
+ Constructs instance without persisting:
19
+
20
+ ```ruby
21
+ user = build(:user)
22
+ user.new_record? # => true
23
+ ```
24
+
25
+ Use when:
26
+ - Testing instance methods that don't need persistence
27
+ - Testing object state and attribute logic
28
+ - Need in-memory object without database overhead
29
+
30
+ ### create
31
+
32
+ Constructs and persists to database:
33
+
34
+ ```ruby
35
+ user = create(:user)
36
+ user.new_record? # => false
37
+ user.id # => assigned by database
38
+ ```
39
+
40
+ Use when:
41
+ - Testing database queries, scopes, finders
42
+ - Testing associations that require foreign keys
43
+ - Integration tests requiring persisted records
44
+
45
+ ### build_stubbed
46
+
47
+ Returns fake persisted object (fastest):
48
+
49
+ ```ruby
50
+ user = build_stubbed(:user)
51
+ user.persisted? # => true (faked)
52
+ user.new_record? # => false (faked)
53
+ user.id # => sequential integer
54
+ user.save # => raises RuntimeError
55
+ ```
56
+
57
+ Characteristics:
58
+ - Sequential `id` assignment
59
+ - Sets `created_at` and `updated_at` to current time
60
+ - Stubs persistence methods to raise errors
61
+ - Cannot use `Marshal.dump`
62
+
63
+ Use when:
64
+ - Unit testing without database
65
+ - Mocking persisted records for speed
66
+ - Testing code that checks `persisted?`
67
+
68
+ ### attributes_for
69
+
70
+ Returns hash of attributes:
71
+
72
+ ```ruby
73
+ attrs = attributes_for(:user)
74
+ # => { name: "John", email: "john@example.com" }
75
+ ```
76
+
77
+ Use when:
78
+ - Controller specs with params
79
+ - Testing form submissions
80
+ - Need raw attribute data
81
+
82
+ ### List Methods
83
+
84
+ Build multiple records:
85
+
86
+ ```ruby
87
+ users = build_list(:user, 5)
88
+ users = create_list(:user, 5, role: :admin)
89
+ users = build_stubbed_list(:user, 5)
90
+ attrs = attributes_for_list(:user, 5)
91
+
92
+ # Pair methods (exactly 2)
93
+ users = build_pair(:user)
94
+ users = create_pair(:user)
95
+
96
+ # With block for index-based customization
97
+ users = create_list(:user, 5) do |user, i|
98
+ user.update!(position: i + 1)
99
+ end
100
+ ```
101
+
102
+ ## Factory Definition
103
+
104
+ ### Basic Factory
105
+
106
+ ```ruby
107
+ FactoryBot.define do
108
+ factory :user do
109
+ name { "John Doe" }
110
+ email { "john@example.com" }
111
+ admin { false }
112
+ end
113
+ end
114
+ ```
115
+
116
+ Key points:
117
+ - Always use block syntax `{ }` for lazy evaluation
118
+ - Factory name infers class (`:user` → `User`)
119
+ - Explicit class: `factory :admin_user, class: "User"`
120
+
121
+ ### Design Principle
122
+
123
+ Define one minimal factory per class with only required attributes:
124
+
125
+ ```ruby
126
+ # Good - minimal factory
127
+ factory :user do
128
+ email { generate(:email) } # Only required for validation
129
+ end
130
+
131
+ # Bad - too many defaults
132
+ factory :user do
133
+ email { generate(:email) }
134
+ name { "John" } # Has default in model
135
+ role { :member } # Has default in model
136
+ verified { false } # Has default in model
137
+ end
138
+ ```
139
+
140
+ ## Sequences
141
+
142
+ Generate unique values:
143
+
144
+ ### Global Sequences
145
+
146
+ ```ruby
147
+ FactoryBot.define do
148
+ sequence :email do |n|
149
+ "person#{n}@example.com"
150
+ end
151
+ end
152
+
153
+ generate(:email) # => "person1@example.com"
154
+ generate(:email) # => "person2@example.com"
155
+ ```
156
+
157
+ ### Factory-Scoped Sequences
158
+
159
+ ```ruby
160
+ factory :user do
161
+ sequence(:email) { |n| "user#{n}@example.com" }
162
+
163
+ # Ruby 2.7+ numbered parameters
164
+ sequence(:username) { "user#{_1}" }
165
+ end
166
+ ```
167
+
168
+ ### Without Block (Auto-increment)
169
+
170
+ ```ruby
171
+ factory :post do
172
+ sequence(:position) # 1, 2, 3...
173
+ end
174
+
175
+ factory :task do
176
+ sequence :priority, %i[low medium high urgent].cycle
177
+ end
178
+ ```
179
+
180
+ ### Uniqueness Caveat
181
+
182
+ Don't override with conflicting values:
183
+
184
+ ```ruby
185
+ factory :user do
186
+ sequence(:email) { |n| "person#{n}@example.com" }
187
+ end
188
+
189
+ # Danger zone
190
+ create(:user, email: "person1@example.com") # Uses person1
191
+ create(:user) # Also generates person1 → CONFLICT!
192
+ ```
193
+
194
+ ## Traits
195
+
196
+ Group attributes for composition:
197
+
198
+ ### Definition
199
+
200
+ ```ruby
201
+ factory :story do
202
+ title { "My Story" }
203
+
204
+ trait :published do
205
+ published { true }
206
+ published_at { Time.current }
207
+ end
208
+
209
+ trait :unpublished do
210
+ published { false }
211
+ end
212
+
213
+ trait :featured do
214
+ featured { true }
215
+ featured_at { Time.current }
216
+ end
217
+
218
+ # Compose traits in child factories
219
+ factory :featured_story, traits: [:published, :featured]
220
+ end
221
+ ```
222
+
223
+ ### Usage
224
+
225
+ ```ruby
226
+ create(:story, :published)
227
+ create(:story, :published, :featured)
228
+ create(:story, :published, title: "Custom Title")
229
+
230
+ create_list(:story, 3, :published, :featured)
231
+ ```
232
+
233
+ ### Trait Precedence
234
+
235
+ Last trait wins for same attribute:
236
+
237
+ ```ruby
238
+ factory :user do
239
+ trait :active do
240
+ status { "active" }
241
+ end
242
+
243
+ trait :pending do
244
+ status { "pending" }
245
+ end
246
+ end
247
+
248
+ create(:user, :active, :pending).status # => "pending"
249
+ ```
250
+
251
+ ### Enum Traits (Rails)
252
+
253
+ Automatically generated for ActiveRecord enums:
254
+
255
+ ```ruby
256
+ class Task < ApplicationRecord
257
+ enum status: { queued: 0, started: 1, finished: 2 }
258
+ end
259
+
260
+ # Auto-generated traits
261
+ build(:task, :queued)
262
+ build(:task, :started)
263
+ build(:task, :finished)
264
+
265
+ # Disable globally
266
+ FactoryBot.automatically_define_enum_traits = false
267
+ ```
268
+
269
+ ## Associations
270
+
271
+ ### Implicit Definition
272
+
273
+ ```ruby
274
+ factory :post do
275
+ author # Looks for :author factory
276
+ end
277
+ ```
278
+
279
+ ### Explicit Definition
280
+
281
+ ```ruby
282
+ factory :post do
283
+ association :author
284
+ association :author, factory: :user
285
+ association :author, factory: :user, strategy: :build
286
+ end
287
+ ```
288
+
289
+ ### With Traits
290
+
291
+ ```ruby
292
+ factory :post do
293
+ association :author, factory: [:user, :admin]
294
+ association :author, :admin, name: "Admin Author"
295
+ end
296
+ ```
297
+
298
+ ### Strategy Inheritance
299
+
300
+ Associations inherit parent's build strategy:
301
+
302
+ ```ruby
303
+ post = build(:post)
304
+ post.new_record? # => true
305
+ post.author.new_record? # => true (also built)
306
+
307
+ post = create(:post)
308
+ post.author.new_record? # => false (also created)
309
+ ```
310
+
311
+ Override with explicit strategy:
312
+
313
+ ```ruby
314
+ factory :post do
315
+ association :author, strategy: :build
316
+ end
317
+ ```
318
+
319
+ ### has_many Associations
320
+
321
+ ```ruby
322
+ factory :user do
323
+ factory :user_with_posts do
324
+ transient do
325
+ posts_count { 5 }
326
+ end
327
+
328
+ after(:create) do |user, evaluator|
329
+ create_list(:post, evaluator.posts_count, author: user)
330
+ user.reload
331
+ end
332
+ end
333
+ end
334
+
335
+ create(:user_with_posts, posts_count: 10)
336
+ ```
337
+
338
+ ### Inline has_many (FactoryBot 5+)
339
+
340
+ ```ruby
341
+ factory :user do
342
+ posts { [association(:post)] }
343
+
344
+ # Multiple with trait
345
+ posts { Array.new(3) { association(:post, :published) } }
346
+ end
347
+ ```
348
+
349
+ ### Polymorphic Associations
350
+
351
+ ```ruby
352
+ factory :comment do
353
+ for_photo # Default trait
354
+
355
+ trait :for_photo do
356
+ association :commentable, factory: :photo
357
+ end
358
+
359
+ trait :for_video do
360
+ association :commentable, factory: :video
361
+ end
362
+ end
363
+
364
+ create(:comment) # On photo
365
+ create(:comment, :for_video) # On video
366
+ ```
367
+
368
+ ### Interconnected Associations
369
+
370
+ Use `instance` to reference object being built:
371
+
372
+ ```ruby
373
+ factory :student do
374
+ school
375
+ profile { association :profile, student: instance, school: school }
376
+ end
377
+
378
+ # Both student and profile share same school
379
+ ```
380
+
381
+ ## Transient Attributes
382
+
383
+ Attributes available only within factory, not set on object:
384
+
385
+ ```ruby
386
+ factory :user do
387
+ transient do
388
+ upcased { false }
389
+ posts_count { 0 }
390
+ end
391
+
392
+ name { "John Doe" }
393
+
394
+ after(:create) do |user, evaluator|
395
+ user.name.upcase! if evaluator.upcased
396
+ create_list(:post, evaluator.posts_count, author: user)
397
+ end
398
+ end
399
+
400
+ create(:user, upcased: true, posts_count: 3)
401
+ ```
402
+
403
+ ## Dependent Attributes
404
+
405
+ Attributes referencing other attributes:
406
+
407
+ ```ruby
408
+ factory :user do
409
+ first_name { "Joe" }
410
+ last_name { "Blow" }
411
+ email { "#{first_name}.#{last_name}@example.com".downcase }
412
+ full_name { "#{first_name} #{last_name}" }
413
+ end
414
+
415
+ create(:user, last_name: "Doe").email # => "joe.doe@example.com"
416
+ ```
417
+
418
+ Overrides flow through dependent attributes.
419
+
420
+ ## Callbacks
421
+
422
+ ### Available Hooks
423
+
424
+ | Callback | Timing | Strategies |
425
+ |----------|--------|------------|
426
+ | `before(:build)` | Before construction | build, create |
427
+ | `after(:build)` | After construction | build, create |
428
+ | `before(:create)` | Before save | create |
429
+ | `after(:create)` | After save | create |
430
+ | `after(:stub)` | After stubbing | build_stubbed |
431
+
432
+ ### Usage
433
+
434
+ ```ruby
435
+ factory :user do
436
+ after(:build) do |user|
437
+ user.setup_defaults
438
+ end
439
+
440
+ after(:create) do |user, evaluator|
441
+ create(:profile, user: user) if evaluator.with_profile
442
+ end
443
+
444
+ transient do
445
+ with_profile { false }
446
+ end
447
+ end
448
+ ```
449
+
450
+ ### Callback Order
451
+
452
+ 1. Global callbacks (RSpec.configure)
453
+ 2. Inherited callbacks (parent factory)
454
+ 3. Factory callbacks
455
+ 4. Trait callbacks (in order applied)
456
+
457
+ ### Skipping Callbacks
458
+
459
+ ```ruby
460
+ factory :user do
461
+ to_create { |instance| instance.save(validate: false) }
462
+ end
463
+
464
+ # Skip create entirely
465
+ factory :user do
466
+ skip_create
467
+ end
468
+ ```
469
+
470
+ ## Inheritance
471
+
472
+ ### Nested Factories
473
+
474
+ ```ruby
475
+ factory :post do
476
+ title { "A Title" }
477
+
478
+ factory :published_post do
479
+ published { true }
480
+ published_at { Time.current }
481
+ end
482
+
483
+ factory :featured_post do
484
+ featured { true }
485
+ end
486
+ end
487
+
488
+ create(:published_post) # Has title and published attributes
489
+ ```
490
+
491
+ ### Explicit Parent
492
+
493
+ ```ruby
494
+ factory :admin, parent: :user do
495
+ admin { true }
496
+ end
497
+ ```
498
+
499
+ ## Custom Construction
500
+
501
+ ### initialize_with
502
+
503
+ For non-standard constructors:
504
+
505
+ ```ruby
506
+ factory :user do
507
+ name { "Jane Doe" }
508
+ initialize_with { new(name: name) }
509
+ end
510
+
511
+ # With all attributes
512
+ factory :user do
513
+ initialize_with { new(**attributes) }
514
+ end
515
+ ```
516
+
517
+ ### Custom Persistence
518
+
519
+ ```ruby
520
+ factory :api_resource do
521
+ to_create { |instance| instance.remote_save! }
522
+ end
523
+ ```
524
+
525
+ ## Context Isolation
526
+
527
+ ### Best Practices
528
+
529
+ 1. **Prefer build_stubbed** for unit tests:
530
+ ```ruby
531
+ let(:user) { build_stubbed(:user) } # Fast, isolated
532
+ ```
533
+
534
+ 2. **Use build when persistence not needed**:
535
+ ```ruby
536
+ let(:user) { build(:user) } # No database hit
537
+ ```
538
+
539
+ 3. **Reserve create for integration tests**:
540
+ ```ruby
541
+ let!(:user) { create(:user) } # When database required
542
+ ```
543
+
544
+ 4. **Rewind sequences in isolation**:
545
+ ```ruby
546
+ before { FactoryBot.rewind_sequences }
547
+ ```
548
+
549
+ ### Avoiding Shared State
550
+
551
+ ```ruby
552
+ # Bad - shared mutable state
553
+ let(:shared_user) { create(:user) }
554
+
555
+ # Good - fresh instance per example
556
+ let(:user) { build(:user) }
557
+ ```
558
+
559
+ ## Linting
560
+
561
+ Validate all factories:
562
+
563
+ ```ruby
564
+ # In Rake task (not before(:suite) - too slow)
565
+ namespace :factory_bot do
566
+ task lint: :environment do
567
+ abort unless Rails.env.test?
568
+
569
+ ActiveRecord::Base.connection.transaction do
570
+ FactoryBot.lint(traits: true, strategy: :build, verbose: true)
571
+ raise ActiveRecord::Rollback
572
+ end
573
+ end
574
+ end
575
+ ```
576
+
577
+ Options:
578
+ - `traits: true` - Lint all trait combinations
579
+ - `strategy: :build` - Use build instead of create
580
+ - `verbose: true` - Show factory names as they're linted
581
+
582
+ ## RSpec Integration
583
+
584
+ ### Setup
585
+
586
+ ```ruby
587
+ # spec/support/factory_bot.rb
588
+ RSpec.configure do |config|
589
+ config.include FactoryBot::Syntax::Methods
590
+ end
591
+ ```
592
+
593
+ Enables calling `create(:user)` instead of `FactoryBot.create(:user)`.
594
+
595
+ ### Spring/Zeus Compatibility
596
+
597
+ ```ruby
598
+ RSpec.configure do |config|
599
+ config.before(:suite) { FactoryBot.reload }
600
+ end
601
+ ```
602
+
603
+ ## Quick Reference
604
+
605
+ ### Common Patterns
606
+
607
+ ```ruby
608
+ # Simple creation
609
+ user = create(:user)
610
+ user = build(:user)
611
+ user = build_stubbed(:user)
612
+ attrs = attributes_for(:user)
613
+
614
+ # With traits
615
+ user = create(:user, :admin, :verified)
616
+
617
+ # With attributes
618
+ user = create(:user, name: "Custom Name")
619
+
620
+ # With associations
621
+ post = create(:post, author: user)
622
+
623
+ # Lists
624
+ users = create_list(:user, 5)
625
+ users = create_list(:user, 5, :admin)
626
+
627
+ # Transient attributes
628
+ user = create(:user, posts_count: 3)
629
+ ```
630
+
631
+ ### Strategy Selection Guide
632
+
633
+ | Scenario | Strategy |
634
+ |----------|----------|
635
+ | Instance method testing | `build` |
636
+ | Method that doesn't touch DB | `build` or `build_stubbed` |
637
+ | Testing scopes/queries | `create` |
638
+ | Testing associations | `create` |
639
+ | Controller params | `attributes_for` |
640
+ | Mocking persisted objects | `build_stubbed` |
641
+ | Performance-critical unit tests | `build_stubbed` |