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,191 @@
1
+ # FactoryBot: Factory Definition Examples
2
+ # Source: factory_bot gem spec/acceptance/
3
+
4
+ # Factory definitions go in spec/factories/ directory.
5
+ # Each factory maps to a model class.
6
+
7
+ # Basic factory definition
8
+ # spec/factories/users.rb
9
+ FactoryBot.define do
10
+ factory :user do
11
+ name { "John Doe" }
12
+ email { "john@example.com" }
13
+ admin { false }
14
+ end
15
+ end
16
+
17
+ # Explicit class mapping
18
+ FactoryBot.define do
19
+ factory :admin_user, class: "User" do
20
+ name { "Admin" }
21
+ admin { true }
22
+ end
23
+
24
+ factory :guest, class: User do
25
+ name { "Guest" }
26
+ end
27
+ end
28
+
29
+ # Always use block syntax for lazy evaluation
30
+ FactoryBot.define do
31
+ factory :article do
32
+ # Good - evaluated when factory is invoked
33
+ title { "My Article" }
34
+ published_at { Time.current }
35
+
36
+ # Bad - evaluated once at load time (avoid)
37
+ # created_date Date.today
38
+ end
39
+ end
40
+
41
+ # Minimal factory principle
42
+ # Define only required attributes, use traits for variations
43
+ FactoryBot.define do
44
+ # Good - minimal factory
45
+ factory :post do
46
+ title { "Post Title" }
47
+ # Only what's required for validation
48
+ end
49
+
50
+ # Bad - too many defaults
51
+ # factory :post do
52
+ # title { "Post Title" }
53
+ # body { "Some content" } # Has model default
54
+ # published { false } # Has model default
55
+ # views_count { 0 } # Has model default
56
+ # end
57
+ end
58
+
59
+ # Sequences
60
+ RSpec.describe "Sequences" do
61
+ # Global sequence
62
+ FactoryBot.define do
63
+ sequence :email do |n|
64
+ "person#{n}@example.com"
65
+ end
66
+
67
+ sequence :username do |n|
68
+ "user_#{n}"
69
+ end
70
+ end
71
+
72
+ describe "global sequences" do
73
+ it "generates unique values" do
74
+ first = generate(:email)
75
+ second = generate(:email)
76
+
77
+ expect(first).not_to eq(second)
78
+ expect(first).to match(/person\d+@example.com/)
79
+ end
80
+ end
81
+
82
+ # Factory-scoped sequence
83
+ FactoryBot.define do
84
+ factory :user do
85
+ sequence(:email) { |n| "user#{n}@example.com" }
86
+ end
87
+ end
88
+
89
+ # Sequence without block (auto-increment)
90
+ FactoryBot.define do
91
+ factory :task do
92
+ sequence(:position)
93
+ end
94
+ end
95
+
96
+ describe "auto-increment sequence" do
97
+ it "returns sequential integers" do
98
+ tasks = build_list(:task, 3)
99
+ expect(tasks.map(&:position)).to eq([1, 2, 3])
100
+ end
101
+ end
102
+
103
+ # Cycling sequence
104
+ FactoryBot.define do
105
+ factory :priority_task, class: "Task" do
106
+ sequence(:priority, %i[low medium high urgent].cycle)
107
+ end
108
+ end
109
+
110
+ # Sequence with custom starting value
111
+ FactoryBot.define do
112
+ sequence(:letter, "a") { |n| "letter_#{n}" }
113
+ end
114
+
115
+ describe "alphabetic sequence" do
116
+ it "increments alphabetically" do
117
+ expect(generate(:letter)).to eq("letter_a")
118
+ expect(generate(:letter)).to eq("letter_b")
119
+ end
120
+ end
121
+
122
+ # Sequence aliases
123
+ FactoryBot.define do
124
+ sequence(:counter, aliases: [:count, :total]) { |n| n * 10 }
125
+ end
126
+
127
+ describe "sequence aliases" do
128
+ it "shares counter across aliases" do
129
+ expect(generate(:counter)).to eq(10)
130
+ expect(generate(:count)).to eq(20)
131
+ expect(generate(:total)).to eq(30)
132
+ end
133
+ end
134
+
135
+ # generate_list for sequences
136
+ describe "generate_list" do
137
+ it "generates multiple values" do
138
+ emails = generate_list(:email, 3)
139
+ expect(emails.length).to eq(3)
140
+ expect(emails.uniq.length).to eq(3)
141
+ end
142
+ end
143
+ end
144
+
145
+ # Dependent attributes
146
+ RSpec.describe "Dependent attributes" do
147
+ FactoryBot.define do
148
+ factory :user do
149
+ first_name { "John" }
150
+ last_name { "Doe" }
151
+ email { "#{first_name}.#{last_name}@example.com".downcase }
152
+ full_name { "#{first_name} #{last_name}" }
153
+ end
154
+ end
155
+
156
+ describe "cascading dependencies" do
157
+ let(:user) { build(:user, first_name: "Jane") }
158
+
159
+ it "computes dependent attributes from overrides" do
160
+ expect(user.email).to eq("jane.doe@example.com")
161
+ expect(user.full_name).to eq("Jane Doe")
162
+ end
163
+ end
164
+
165
+ # Symbol#to_proc for dependent attributes
166
+ FactoryBot.define do
167
+ factory :account do
168
+ password { "secret123" }
169
+ password_confirmation(&:password)
170
+ end
171
+ end
172
+
173
+ describe "Symbol#to_proc syntax" do
174
+ let(:account) { build(:account) }
175
+
176
+ it "references another attribute" do
177
+ expect(account.password_confirmation).to eq(account.password)
178
+ end
179
+ end
180
+ end
181
+
182
+ # Rewind sequences
183
+ RSpec.describe "Rewinding sequences" do
184
+ before { FactoryBot.rewind_sequences }
185
+
186
+ it "resets sequence counters" do
187
+ # After rewind, sequences start fresh
188
+ user = build(:user)
189
+ expect(user.email).to match(/user1@/)
190
+ end
191
+ end
@@ -0,0 +1,314 @@
1
+ # FactoryBot: Inheritance Examples
2
+ # Source: factory_bot gem spec/acceptance/parent_spec.rb
3
+
4
+ # Child factories inherit attributes, traits, and callbacks
5
+ # from parent factories.
6
+
7
+ # Nested factories (implicit inheritance)
8
+ FactoryBot.define do
9
+ factory :user do
10
+ name { "John Doe" }
11
+ email { "#{name.parameterize}@example.com" }
12
+ role { "member" }
13
+
14
+ factory :admin do
15
+ role { "admin" }
16
+ admin_since { Time.current }
17
+ end
18
+
19
+ factory :moderator do
20
+ role { "moderator" }
21
+ end
22
+
23
+ factory :guest do
24
+ name { "Guest User" }
25
+ role { "guest" }
26
+ end
27
+ end
28
+ end
29
+
30
+ RSpec.describe "Nested factories" do
31
+ describe "inherit parent attributes" do
32
+ let(:admin) { build(:admin) }
33
+
34
+ it "has parent attributes" do
35
+ expect(admin.email).to include("@example.com")
36
+ end
37
+
38
+ it "overrides specified attributes" do
39
+ expect(admin.role).to eq("admin")
40
+ end
41
+
42
+ it "adds child-specific attributes" do
43
+ expect(admin.admin_since).to be_present
44
+ end
45
+ end
46
+
47
+ describe "dependent attribute inheritance" do
48
+ let(:guest) { build(:guest) }
49
+
50
+ it "cascades changes through dependent attributes" do
51
+ # name changed, so email changes too
52
+ expect(guest.email).to eq("guest-user@example.com")
53
+ end
54
+ end
55
+ end
56
+
57
+ # Explicit parent
58
+ FactoryBot.define do
59
+ factory :user do
60
+ name { "User" }
61
+
62
+ factory :active_user do
63
+ active { true }
64
+ end
65
+ end
66
+
67
+ factory :super_user, parent: :active_user do
68
+ superpower { true }
69
+ end
70
+ end
71
+
72
+ RSpec.describe "Explicit parent" do
73
+ describe "parent: option" do
74
+ let(:super_user) { build(:super_user) }
75
+
76
+ it "inherits from specified parent" do
77
+ expect(super_user.active).to be true
78
+ expect(super_user.superpower).to be true
79
+ end
80
+ end
81
+ end
82
+
83
+ # Parent overrides nesting
84
+ FactoryBot.define do
85
+ factory :base_user, class: "User" do
86
+ name { "Base" }
87
+
88
+ factory :child_user do
89
+ name { "Child" }
90
+
91
+ # Despite nesting, inherits from :special_user
92
+ factory :override_user, parent: :special_user do
93
+ name { "Override" }
94
+ end
95
+ end
96
+ end
97
+
98
+ factory :special_user, class: "User" do
99
+ name { "Special" }
100
+ special { true }
101
+ end
102
+ end
103
+
104
+ RSpec.describe "Parent overrides nesting" do
105
+ describe "explicit parent takes precedence" do
106
+ let(:override) { build(:override_user) }
107
+
108
+ it "inherits from parent: not nesting context" do
109
+ # Has special from :special_user parent
110
+ expect(override.special).to be true
111
+ expect(override.name).to eq("Override")
112
+ end
113
+ end
114
+ end
115
+
116
+ # Trait inheritance
117
+ FactoryBot.define do
118
+ factory :post do
119
+ title { "Post" }
120
+
121
+ trait :published do
122
+ published { true }
123
+ end
124
+
125
+ trait :featured do
126
+ featured { true }
127
+ end
128
+
129
+ factory :blog_post do
130
+ title { "Blog Post" }
131
+ # Traits from parent available
132
+
133
+ factory :featured_blog_post do
134
+ featured # Grandparent trait
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ RSpec.describe "Trait inheritance" do
141
+ describe "traits available to children" do
142
+ let(:published_blog) { build(:blog_post, :published) }
143
+
144
+ it "uses parent traits" do
145
+ expect(published_blog.published).to be true
146
+ end
147
+ end
148
+
149
+ describe "grandparent traits" do
150
+ let(:featured) { build(:featured_blog_post) }
151
+
152
+ it "inherits traits from grandparent" do
153
+ expect(featured.featured).to be true
154
+ end
155
+ end
156
+ end
157
+
158
+ # Callback inheritance
159
+ FactoryBot.define do
160
+ factory :record do
161
+ sequence(:log) { [] }
162
+
163
+ after(:create) { |r| r.log << "parent" }
164
+
165
+ factory :child_record do
166
+ after(:create) { |r| r.log << "child" }
167
+
168
+ factory :grandchild_record do
169
+ after(:create) { |r| r.log << "grandchild" }
170
+ end
171
+ end
172
+ end
173
+ end
174
+
175
+ RSpec.describe "Callback inheritance" do
176
+ describe "callbacks execute in order" do
177
+ let(:grandchild) { create(:grandchild_record) }
178
+
179
+ it "runs parent callbacks first" do
180
+ expect(grandchild.log).to eq(%w[parent child grandchild])
181
+ end
182
+ end
183
+ end
184
+
185
+ # Deep inheritance chain
186
+ FactoryBot.define do
187
+ factory :vehicle do
188
+ wheels { 4 }
189
+ engine { true }
190
+
191
+ factory :car do
192
+ doors { 4 }
193
+
194
+ factory :sedan do
195
+ body_type { "sedan" }
196
+ end
197
+
198
+ factory :suv do
199
+ body_type { "suv" }
200
+ wheels { 4 } # Same as parent, explicit for clarity
201
+ end
202
+ end
203
+
204
+ factory :motorcycle do
205
+ wheels { 2 }
206
+ doors { 0 }
207
+ end
208
+ end
209
+ end
210
+
211
+ RSpec.describe "Deep inheritance" do
212
+ describe "multi-level hierarchy" do
213
+ let(:sedan) { build(:sedan) }
214
+
215
+ it "inherits all ancestor attributes" do
216
+ expect(sedan.wheels).to eq(4) # from :vehicle
217
+ expect(sedan.engine).to be true # from :vehicle
218
+ expect(sedan.doors).to eq(4) # from :car
219
+ expect(sedan.body_type).to eq("sedan")
220
+ end
221
+ end
222
+ end
223
+
224
+ # Overriding inherited attributes
225
+ FactoryBot.define do
226
+ factory :employee do
227
+ name { "Employee" }
228
+ salary { 50_000 }
229
+ department { "General" }
230
+
231
+ factory :manager do
232
+ salary { 80_000 }
233
+ department { "Management" }
234
+
235
+ factory :executive do
236
+ salary { 150_000 }
237
+ department { "Executive" }
238
+ stock_options { true }
239
+ end
240
+ end
241
+ end
242
+ end
243
+
244
+ RSpec.describe "Overriding inherited attributes" do
245
+ describe "each level can override" do
246
+ let(:employee) { build(:employee) }
247
+ let(:manager) { build(:manager) }
248
+ let(:executive) { build(:executive) }
249
+
250
+ it "uses most specific value" do
251
+ expect(employee.salary).to eq(50_000)
252
+ expect(manager.salary).to eq(80_000)
253
+ expect(executive.salary).to eq(150_000)
254
+ end
255
+ end
256
+ end
257
+
258
+ # Inheriting associations
259
+ FactoryBot.define do
260
+ factory :post do
261
+ title { "Post" }
262
+ user
263
+
264
+ factory :published_post do
265
+ published { true }
266
+ # Inherits user association
267
+ end
268
+ end
269
+ end
270
+
271
+ RSpec.describe "Association inheritance" do
272
+ describe "child inherits associations" do
273
+ let(:published) { build(:published_post) }
274
+
275
+ it "has parent association" do
276
+ expect(published.user).to be_present
277
+ end
278
+ end
279
+ end
280
+
281
+ # initialize_with inheritance
282
+ FactoryBot.define do
283
+ factory :custom_object do
284
+ name { "Custom" }
285
+ initialize_with { new(name:) }
286
+
287
+ factory :child_custom do
288
+ description { "Child" }
289
+ # Inherits initialize_with
290
+ end
291
+
292
+ factory :override_custom do
293
+ initialize_with { new(name: "Overridden") }
294
+ end
295
+ end
296
+ end
297
+
298
+ RSpec.describe "initialize_with inheritance" do
299
+ describe "child inherits constructor" do
300
+ let(:child) { build(:child_custom) }
301
+
302
+ it "uses parent initialize_with" do
303
+ expect(child.name).to eq("Custom")
304
+ end
305
+ end
306
+
307
+ describe "child can override" do
308
+ let(:override) { build(:override_custom) }
309
+
310
+ it "uses own initialize_with" do
311
+ expect(override.name).to eq("Overridden")
312
+ end
313
+ end
314
+ end