anima-core 0.3.0 → 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 (269) hide show
  1. checksums.yaml +4 -4
  2. data/.reek.yml +27 -1
  3. data/CHANGELOG.md +4 -0
  4. data/README.md +219 -25
  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 +76 -28
  12. data/app/jobs/agent_request_job.rb +24 -0
  13. data/app/jobs/analytical_brain_job.rb +33 -0
  14. data/app/jobs/count_event_tokens_job.rb +1 -1
  15. data/app/models/concerns/event/broadcasting.rb +20 -2
  16. data/app/models/event.rb +1 -1
  17. data/app/models/goal.rb +91 -0
  18. data/app/models/session.rb +347 -22
  19. data/config/application.rb +2 -0
  20. data/db/migrate/20260314075248_add_subagent_support_to_sessions.rb +6 -0
  21. data/db/migrate/20260314112417_add_granted_tools_to_sessions.rb +5 -0
  22. data/db/migrate/20260314140000_add_name_to_sessions.rb +7 -0
  23. data/db/migrate/20260314150000_add_viewport_event_ids_to_sessions.rb +7 -0
  24. data/db/migrate/20260315100000_add_active_skills_to_sessions.rb +7 -0
  25. data/db/migrate/20260315140843_create_goals.rb +16 -0
  26. data/db/migrate/20260315144837_add_completed_at_to_goals.rb +5 -0
  27. data/db/migrate/20260315191105_add_active_workflow_to_sessions.rb +5 -0
  28. data/lib/agent_loop.rb +65 -9
  29. data/lib/agents/definition.rb +116 -0
  30. data/lib/agents/registry.rb +106 -0
  31. data/lib/analytical_brain/runner.rb +276 -0
  32. data/lib/analytical_brain/tools/activate_skill.rb +52 -0
  33. data/lib/analytical_brain/tools/deactivate_skill.rb +43 -0
  34. data/lib/analytical_brain/tools/deactivate_workflow.rb +34 -0
  35. data/lib/analytical_brain/tools/everything_is_ready.rb +28 -0
  36. data/lib/analytical_brain/tools/finish_goal.rb +62 -0
  37. data/lib/analytical_brain/tools/read_workflow.rb +58 -0
  38. data/lib/analytical_brain/tools/rename_session.rb +63 -0
  39. data/lib/analytical_brain/tools/set_goal.rb +60 -0
  40. data/lib/analytical_brain/tools/update_goal.rb +60 -0
  41. data/lib/analytical_brain.rb +23 -0
  42. data/lib/anima/cli/mcp/secrets.rb +76 -0
  43. data/lib/anima/cli/mcp.rb +197 -0
  44. data/lib/anima/cli.rb +4 -0
  45. data/lib/anima/installer.rb +168 -0
  46. data/lib/anima/settings.rb +226 -0
  47. data/lib/anima/version.rb +1 -1
  48. data/lib/anima.rb +9 -0
  49. data/lib/credential_store.rb +103 -0
  50. data/lib/environment_probe.rb +232 -0
  51. data/lib/llm/client.rb +29 -10
  52. data/lib/mcp/client_manager.rb +86 -0
  53. data/lib/mcp/config.rb +213 -0
  54. data/lib/mcp/health_check.rb +77 -0
  55. data/lib/mcp/secrets.rb +73 -0
  56. data/lib/mcp/stdio_transport.rb +206 -0
  57. data/lib/providers/anthropic.rb +8 -7
  58. data/lib/shell_session.rb +11 -10
  59. data/lib/skills/definition.rb +97 -0
  60. data/lib/skills/registry.rb +105 -0
  61. data/lib/tools/edit.rb +3 -4
  62. data/lib/tools/mcp_tool.rb +114 -0
  63. data/lib/tools/read.rb +15 -16
  64. data/lib/tools/registry.rb +14 -12
  65. data/lib/tools/request_feature.rb +121 -0
  66. data/lib/tools/return_result.rb +81 -0
  67. data/lib/tools/spawn_specialist.rb +109 -0
  68. data/lib/tools/spawn_subagent.rb +111 -0
  69. data/lib/tools/subagent_prompts.rb +12 -0
  70. data/lib/tools/web_get.rb +8 -9
  71. data/lib/tui/app.rb +332 -43
  72. data/lib/tui/message_store.rb +20 -0
  73. data/lib/tui/screens/chat.rb +207 -20
  74. data/lib/workflows/definition.rb +97 -0
  75. data/lib/workflows/registry.rb +89 -0
  76. data/skills/activerecord/SKILL.md +255 -0
  77. data/skills/activerecord/examples/associations/association_extensions.rb +298 -0
  78. data/skills/activerecord/examples/associations/basic_associations.rb +118 -0
  79. data/skills/activerecord/examples/associations/counter_caches.rb +215 -0
  80. data/skills/activerecord/examples/associations/polymorphic_associations.rb +217 -0
  81. data/skills/activerecord/examples/associations/self_referential.rb +302 -0
  82. data/skills/activerecord/examples/associations/through_associations.rb +203 -0
  83. data/skills/activerecord/examples/basics/crud_operations.rb +209 -0
  84. data/skills/activerecord/examples/basics/dirty_tracking.rb +218 -0
  85. data/skills/activerecord/examples/basics/inheritance.rb +377 -0
  86. data/skills/activerecord/examples/basics/type_casting.rb +317 -0
  87. data/skills/activerecord/examples/callbacks/alternatives_to_callbacks.rb +447 -0
  88. data/skills/activerecord/examples/callbacks/conditional_callbacks.rb +353 -0
  89. data/skills/activerecord/examples/callbacks/lifecycle_callbacks.rb +280 -0
  90. data/skills/activerecord/examples/callbacks/transaction_callbacks.rb +340 -0
  91. data/skills/activerecord/examples/migrations/indexes_and_constraints.rb +337 -0
  92. data/skills/activerecord/examples/migrations/reversible_patterns.rb +403 -0
  93. data/skills/activerecord/examples/migrations/safe_patterns.rb +420 -0
  94. data/skills/activerecord/examples/migrations/schema_changes.rb +277 -0
  95. data/skills/activerecord/examples/querying/batch_processing.rb +226 -0
  96. data/skills/activerecord/examples/querying/eager_loading.rb +259 -0
  97. data/skills/activerecord/examples/querying/finder_methods.rb +170 -0
  98. data/skills/activerecord/examples/querying/optimization.rb +275 -0
  99. data/skills/activerecord/examples/querying/scopes.rb +260 -0
  100. data/skills/activerecord/examples/validations/built_in_validators.rb +277 -0
  101. data/skills/activerecord/examples/validations/conditional_validations.rb +288 -0
  102. data/skills/activerecord/examples/validations/custom_validators.rb +381 -0
  103. data/skills/activerecord/examples/validations/database_constraints.rb +432 -0
  104. data/skills/activerecord/examples/validations/validation_contexts.rb +367 -0
  105. data/skills/activerecord/references/associations.md +709 -0
  106. data/skills/activerecord/references/basics.md +622 -0
  107. data/skills/activerecord/references/callbacks.md +738 -0
  108. data/skills/activerecord/references/migrations.md +657 -0
  109. data/skills/activerecord/references/querying.md +655 -0
  110. data/skills/activerecord/references/validations.md +596 -0
  111. data/skills/dragonruby/SKILL.md +250 -0
  112. data/skills/dragonruby/examples/audio/audio_events.rb +55 -0
  113. data/skills/dragonruby/examples/audio/background_music.rb +29 -0
  114. data/skills/dragonruby/examples/audio/crossfade.rb +51 -0
  115. data/skills/dragonruby/examples/audio/music_controls.rb +51 -0
  116. data/skills/dragonruby/examples/audio/sound_effects.rb +30 -0
  117. data/skills/dragonruby/examples/core/coordinate_system.rb +27 -0
  118. data/skills/dragonruby/examples/core/hello_world.rb +24 -0
  119. data/skills/dragonruby/examples/core/labels.rb +22 -0
  120. data/skills/dragonruby/examples/core/sprites.rb +35 -0
  121. data/skills/dragonruby/examples/core/state_management.rb +29 -0
  122. data/skills/dragonruby/examples/distribution/background_pause.rb +42 -0
  123. data/skills/dragonruby/examples/distribution/build_workflow.sh +26 -0
  124. data/skills/dragonruby/examples/distribution/cvars_production.txt +16 -0
  125. data/skills/dragonruby/examples/distribution/game_metadata_hd.txt +23 -0
  126. data/skills/dragonruby/examples/distribution/game_metadata_minimal.txt +9 -0
  127. data/skills/dragonruby/examples/distribution/game_metadata_mobile.txt +31 -0
  128. data/skills/dragonruby/examples/distribution/platform_detection.rb +36 -0
  129. data/skills/dragonruby/examples/distribution/steam_metadata.txt +19 -0
  130. data/skills/dragonruby/examples/entities/collision_detection.rb +43 -0
  131. data/skills/dragonruby/examples/entities/entity_lifecycle.rb +68 -0
  132. data/skills/dragonruby/examples/entities/entity_storage.rb +38 -0
  133. data/skills/dragonruby/examples/entities/factory_methods.rb +45 -0
  134. data/skills/dragonruby/examples/entities/random_spawning.rb +50 -0
  135. data/skills/dragonruby/examples/game-logic/reset_patterns.rb +98 -0
  136. data/skills/dragonruby/examples/game-logic/save_load.rb +101 -0
  137. data/skills/dragonruby/examples/game-logic/scoring.rb +104 -0
  138. data/skills/dragonruby/examples/game-logic/state_transitions.rb +103 -0
  139. data/skills/dragonruby/examples/game-logic/timers.rb +87 -0
  140. data/skills/dragonruby/examples/input/action_triggers.rb +36 -0
  141. data/skills/dragonruby/examples/input/analog_movement.rb +28 -0
  142. data/skills/dragonruby/examples/input/controller_input.rb +28 -0
  143. data/skills/dragonruby/examples/input/directional_input.rb +24 -0
  144. data/skills/dragonruby/examples/input/keyboard_input.rb +28 -0
  145. data/skills/dragonruby/examples/input/mouse_click.rb +26 -0
  146. data/skills/dragonruby/examples/input/movement_with_bounds.rb +22 -0
  147. data/skills/dragonruby/examples/input/normalized_movement.rb +32 -0
  148. data/skills/dragonruby/examples/rendering/frame_animation.rb +32 -0
  149. data/skills/dragonruby/examples/rendering/labels.rb +32 -0
  150. data/skills/dragonruby/examples/rendering/layering.rb +51 -0
  151. data/skills/dragonruby/examples/rendering/solids.rb +61 -0
  152. data/skills/dragonruby/examples/rendering/sprites.rb +33 -0
  153. data/skills/dragonruby/examples/rendering/spritesheet_animation.rb +39 -0
  154. data/skills/dragonruby/examples/scenes/case_dispatch.rb +60 -0
  155. data/skills/dragonruby/examples/scenes/class_based.rb +150 -0
  156. data/skills/dragonruby/examples/scenes/pause_overlay.rb +100 -0
  157. data/skills/dragonruby/examples/scenes/safe_transitions.rb +68 -0
  158. data/skills/dragonruby/examples/scenes/scene_transitions.rb +98 -0
  159. data/skills/dragonruby/examples/scenes/send_dispatch.rb +88 -0
  160. data/skills/dragonruby/references/audio.md +396 -0
  161. data/skills/dragonruby/references/core.md +385 -0
  162. data/skills/dragonruby/references/distribution.md +434 -0
  163. data/skills/dragonruby/references/entities.md +516 -0
  164. data/skills/dragonruby/references/game-logic/persistence.md +386 -0
  165. data/skills/dragonruby/references/game-logic/state.md +389 -0
  166. data/skills/dragonruby/references/input.md +414 -0
  167. data/skills/dragonruby/references/rendering/animation.md +467 -0
  168. data/skills/dragonruby/references/rendering/primitives.md +403 -0
  169. data/skills/dragonruby/references/scenes.md +443 -0
  170. data/skills/draper-decorators/SKILL.md +344 -0
  171. data/skills/draper-decorators/examples/application_decorator.rb +61 -0
  172. data/skills/draper-decorators/examples/decorator_spec.rb +253 -0
  173. data/skills/draper-decorators/examples/model_decorator.rb +152 -0
  174. data/skills/draper-decorators/references/anti-patterns.md +640 -0
  175. data/skills/draper-decorators/references/patterns.md +507 -0
  176. data/skills/draper-decorators/references/testing.md +559 -0
  177. data/skills/gh-issue.md +182 -0
  178. data/skills/mcp-server/SKILL.md +177 -0
  179. data/skills/mcp-server/examples/dynamic_tools.rb +36 -0
  180. data/skills/mcp-server/examples/file_manager_tool.rb +85 -0
  181. data/skills/mcp-server/examples/http_client.rb +48 -0
  182. data/skills/mcp-server/examples/http_server.rb +97 -0
  183. data/skills/mcp-server/examples/rails_integration.rb +88 -0
  184. data/skills/mcp-server/examples/stdio_server.rb +108 -0
  185. data/skills/mcp-server/examples/streaming_client.rb +95 -0
  186. data/skills/mcp-server/references/gotchas.md +183 -0
  187. data/skills/mcp-server/references/prompts.md +98 -0
  188. data/skills/mcp-server/references/resources.md +53 -0
  189. data/skills/mcp-server/references/server.md +140 -0
  190. data/skills/mcp-server/references/tools.md +146 -0
  191. data/skills/mcp-server/references/transport.md +104 -0
  192. data/skills/ratatui-ruby/SKILL.md +315 -0
  193. data/skills/ratatui-ruby/references/core-concepts.md +340 -0
  194. data/skills/ratatui-ruby/references/events.md +387 -0
  195. data/skills/ratatui-ruby/references/frameworks.md +522 -0
  196. data/skills/ratatui-ruby/references/layout.md +423 -0
  197. data/skills/ratatui-ruby/references/styling.md +268 -0
  198. data/skills/ratatui-ruby/references/testing.md +433 -0
  199. data/skills/ratatui-ruby/references/widgets.md +532 -0
  200. data/skills/rspec/SKILL.md +340 -0
  201. data/skills/rspec/examples/core/basic_structure.rb +69 -0
  202. data/skills/rspec/examples/core/configuration.rb +126 -0
  203. data/skills/rspec/examples/core/hooks.rb +126 -0
  204. data/skills/rspec/examples/core/memoized_helpers.rb +139 -0
  205. data/skills/rspec/examples/core/metadata_filtering.rb +144 -0
  206. data/skills/rspec/examples/core/shared_examples.rb +145 -0
  207. data/skills/rspec/examples/factory_bot/associations.rb +314 -0
  208. data/skills/rspec/examples/factory_bot/build_strategies.rb +272 -0
  209. data/skills/rspec/examples/factory_bot/callbacks.rb +320 -0
  210. data/skills/rspec/examples/factory_bot/custom_construction.rb +328 -0
  211. data/skills/rspec/examples/factory_bot/factory_definition.rb +191 -0
  212. data/skills/rspec/examples/factory_bot/inheritance.rb +314 -0
  213. data/skills/rspec/examples/factory_bot/traits.rb +293 -0
  214. data/skills/rspec/examples/factory_bot/transients.rb +229 -0
  215. data/skills/rspec/examples/matchers/change.rb +115 -0
  216. data/skills/rspec/examples/matchers/collections.rb +154 -0
  217. data/skills/rspec/examples/matchers/comparisons.rb +79 -0
  218. data/skills/rspec/examples/matchers/composing.rb +155 -0
  219. data/skills/rspec/examples/matchers/custom_matchers.rb +197 -0
  220. data/skills/rspec/examples/matchers/equality.rb +58 -0
  221. data/skills/rspec/examples/matchers/errors.rb +136 -0
  222. data/skills/rspec/examples/matchers/output.rb +103 -0
  223. data/skills/rspec/examples/matchers/predicates.rb +87 -0
  224. data/skills/rspec/examples/matchers/truthiness.rb +101 -0
  225. data/skills/rspec/examples/matchers/types.rb +82 -0
  226. data/skills/rspec/examples/matchers/yield.rb +147 -0
  227. data/skills/rspec/examples/mocks/any_instance.rb +172 -0
  228. data/skills/rspec/examples/mocks/argument_matchers.rb +206 -0
  229. data/skills/rspec/examples/mocks/constants.rb +177 -0
  230. data/skills/rspec/examples/mocks/doubles.rb +139 -0
  231. data/skills/rspec/examples/mocks/expectations.rb +137 -0
  232. data/skills/rspec/examples/mocks/message_chains.rb +173 -0
  233. data/skills/rspec/examples/mocks/ordering.rb +144 -0
  234. data/skills/rspec/examples/mocks/receive_counts.rb +181 -0
  235. data/skills/rspec/examples/mocks/responses.rb +223 -0
  236. data/skills/rspec/examples/mocks/spies.rb +149 -0
  237. data/skills/rspec/examples/mocks/stubbing.rb +133 -0
  238. data/skills/rspec/examples/rails/channels.rb +250 -0
  239. data/skills/rspec/examples/rails/controller_specs.rb +302 -0
  240. data/skills/rspec/examples/rails/helper_specs.rb +245 -0
  241. data/skills/rspec/examples/rails/job_specs.rb +256 -0
  242. data/skills/rspec/examples/rails/mailer_specs.rb +228 -0
  243. data/skills/rspec/examples/rails/matchers.rb +374 -0
  244. data/skills/rspec/examples/rails/model_specs.rb +193 -0
  245. data/skills/rspec/examples/rails/request_specs.rb +275 -0
  246. data/skills/rspec/examples/rails/routing_specs.rb +276 -0
  247. data/skills/rspec/examples/rails/system_specs.rb +294 -0
  248. data/skills/rspec/examples/rails/transactions.rb +254 -0
  249. data/skills/rspec/examples/rails/view_specs.rb +252 -0
  250. data/skills/rspec/references/core.md +816 -0
  251. data/skills/rspec/references/factory_bot.md +641 -0
  252. data/skills/rspec/references/matchers.md +516 -0
  253. data/skills/rspec/references/mocks.md +381 -0
  254. data/skills/rspec/references/rails.md +528 -0
  255. data/templates/soul.md +40 -0
  256. data/workflows/commit.md +45 -0
  257. data/workflows/create_handoff.md +98 -0
  258. data/workflows/create_note.md +82 -0
  259. data/workflows/create_plan.md +457 -0
  260. data/workflows/decompose_ticket.md +109 -0
  261. data/workflows/feature.md +91 -0
  262. data/workflows/implement_plan.md +87 -0
  263. data/workflows/iterate_plan.md +247 -0
  264. data/workflows/research_codebase.md +210 -0
  265. data/workflows/resume_handoff.md +217 -0
  266. data/workflows/review_pr.md +320 -0
  267. data/workflows/thoughts_init.md +71 -0
  268. data/workflows/validate_plan.md +166 -0
  269. metadata +284 -1
@@ -0,0 +1,314 @@
1
+ # FactoryBot: Associations Examples
2
+ # Source: factory_bot gem spec/acceptance/associations_spec.rb
3
+
4
+ # Associations create related records automatically.
5
+ # Strategy propagates: build creates built associations,
6
+ # create creates persisted associations.
7
+
8
+ # Implicit belongs_to
9
+ FactoryBot.define do
10
+ factory :user do
11
+ name { "John" }
12
+ end
13
+
14
+ factory :post do
15
+ title { "My Post" }
16
+ user # Implicit - looks for :user factory
17
+ end
18
+ end
19
+
20
+ RSpec.describe "Implicit associations" do
21
+ describe "belongs_to" do
22
+ let(:post) { build(:post) }
23
+
24
+ it "creates associated record" do
25
+ expect(post.user).to be_present
26
+ expect(post.user.name).to eq("John")
27
+ end
28
+ end
29
+ end
30
+
31
+ # Explicit association syntax
32
+ FactoryBot.define do
33
+ factory :comment do
34
+ body { "Comment text" }
35
+ association :author, factory: :user
36
+ end
37
+
38
+ factory :review do
39
+ content { "Review content" }
40
+ association :reviewer, factory: :user, name: "Reviewer"
41
+ end
42
+ end
43
+
44
+ RSpec.describe "Explicit associations" do
45
+ describe "with factory option" do
46
+ let(:comment) { build(:comment) }
47
+
48
+ it "uses specified factory" do
49
+ expect(comment.author).to be_a(User)
50
+ end
51
+ end
52
+
53
+ describe "with attribute overrides" do
54
+ let(:review) { build(:review) }
55
+
56
+ it "applies overrides to association" do
57
+ expect(review.reviewer.name).to eq("Reviewer")
58
+ end
59
+ end
60
+ end
61
+
62
+ # Association with traits
63
+ FactoryBot.define do
64
+ factory :admin_post, class: "Post" do
65
+ title { "Admin Post" }
66
+ association :author, factory: [:user, :admin]
67
+ end
68
+
69
+ factory :featured_article, class: "Article" do
70
+ title { "Featured" }
71
+ association :author, :verified, factory: :user
72
+ end
73
+ end
74
+
75
+ RSpec.describe "Association with traits" do
76
+ describe "array syntax for factory with traits" do
77
+ let(:post) { build(:admin_post) }
78
+
79
+ it "applies traits to associated record" do
80
+ expect(post.author).to be_admin
81
+ end
82
+ end
83
+
84
+ describe "inline trait syntax" do
85
+ let(:article) { build(:featured_article) }
86
+
87
+ it "applies trait to association" do
88
+ expect(article.author).to be_verified
89
+ end
90
+ end
91
+ end
92
+
93
+ # Strategy inheritance
94
+ RSpec.describe "Strategy inheritance" do
95
+ FactoryBot.define do
96
+ factory :post do
97
+ title { "Post" }
98
+ user
99
+ end
100
+ end
101
+
102
+ describe "build inherits to associations" do
103
+ let(:post) { build(:post) }
104
+
105
+ it "builds association (not persisted)" do
106
+ expect(post).to be_new_record
107
+ expect(post.user).to be_new_record
108
+ end
109
+ end
110
+
111
+ describe "create inherits to associations" do
112
+ let(:post) { create(:post) }
113
+
114
+ it "creates association (persisted)" do
115
+ expect(post).to be_persisted
116
+ expect(post.user).to be_persisted
117
+ end
118
+ end
119
+
120
+ describe "build_stubbed inherits to associations" do
121
+ let(:post) { build_stubbed(:post) }
122
+
123
+ it "stubs association" do
124
+ expect(post).to be_persisted
125
+ expect(post.user).to be_persisted
126
+ end
127
+ end
128
+ end
129
+
130
+ # Override strategy for association
131
+ FactoryBot.define do
132
+ factory :post do
133
+ title { "Post" }
134
+ association :user, strategy: :create # Always create user
135
+ end
136
+ end
137
+
138
+ RSpec.describe "Strategy override" do
139
+ describe "explicit strategy on association" do
140
+ let(:post) { build(:post) }
141
+
142
+ it "uses specified strategy regardless of parent" do
143
+ expect(post).to be_new_record
144
+ expect(post.user).to be_persisted # Created due to strategy: :create
145
+ end
146
+ end
147
+ end
148
+
149
+ # has_many associations
150
+ FactoryBot.define do
151
+ factory :blog do
152
+ name { "My Blog" }
153
+ posts { [association(:post)] }
154
+ end
155
+
156
+ factory :author do
157
+ name { "Author" }
158
+ articles { Array.new(3) { association(:article) } }
159
+ end
160
+ end
161
+
162
+ RSpec.describe "has_many associations" do
163
+ describe "inline collection" do
164
+ let(:blog) { build(:blog) }
165
+
166
+ it "creates collection with one item" do
167
+ expect(blog.posts.length).to eq(1)
168
+ end
169
+ end
170
+
171
+ describe "multiple items" do
172
+ let(:author) { build(:author) }
173
+
174
+ it "creates specified number of items" do
175
+ expect(author.articles.length).to eq(3)
176
+ end
177
+ end
178
+ end
179
+
180
+ # has_many with transient attributes
181
+ FactoryBot.define do
182
+ factory :user do
183
+ name { "User" }
184
+
185
+ transient do
186
+ posts_count { 0 }
187
+ end
188
+
189
+ after(:create) do |user, evaluator|
190
+ create_list(:post, evaluator.posts_count, author: user)
191
+ user.reload
192
+ end
193
+ end
194
+ end
195
+
196
+ RSpec.describe "has_many with transient" do
197
+ describe "configurable collection size" do
198
+ let(:user) { create(:user, posts_count: 5) }
199
+
200
+ it "creates specified number of associations" do
201
+ expect(user.posts.count).to eq(5)
202
+ end
203
+ end
204
+ end
205
+
206
+ # Polymorphic associations
207
+ FactoryBot.define do
208
+ factory :comment do
209
+ body { "Comment" }
210
+
211
+ for_post # Default trait
212
+
213
+ trait :for_post do
214
+ association :commentable, factory: :post
215
+ end
216
+
217
+ trait :for_article do
218
+ association :commentable, factory: :article
219
+ end
220
+
221
+ trait :for_video do
222
+ association :commentable, factory: :video
223
+ end
224
+ end
225
+ end
226
+
227
+ RSpec.describe "Polymorphic associations" do
228
+ describe "default polymorphic type" do
229
+ let(:comment) { create(:comment) }
230
+
231
+ it "uses default trait" do
232
+ expect(comment.commentable).to be_a(Post)
233
+ end
234
+ end
235
+
236
+ describe "alternate polymorphic types" do
237
+ let(:article_comment) { create(:comment, :for_article) }
238
+ let(:video_comment) { create(:comment, :for_video) }
239
+
240
+ it "uses trait-specified type" do
241
+ expect(article_comment.commentable).to be_a(Article)
242
+ expect(video_comment.commentable).to be_a(Video)
243
+ end
244
+ end
245
+ end
246
+
247
+ # Interconnected associations
248
+ FactoryBot.define do
249
+ factory :account do
250
+ name { "Account" }
251
+ supplier { association(:supplier, account: instance) }
252
+ end
253
+
254
+ factory :supplier do
255
+ name { "Supplier" }
256
+ account { association(:account, supplier: instance) }
257
+ end
258
+ end
259
+
260
+ RSpec.describe "Interconnected associations" do
261
+ describe "bidirectional references" do
262
+ let(:account) { build(:account) }
263
+
264
+ it "creates circular reference" do
265
+ expect(account.supplier.account).to eq(account)
266
+ end
267
+ end
268
+ end
269
+
270
+ # Complex interconnected models
271
+ FactoryBot.define do
272
+ factory :student do
273
+ name { "Student" }
274
+ school
275
+ profile { association(:profile, student: instance, school:) }
276
+ end
277
+
278
+ factory :profile do
279
+ bio { "Bio" }
280
+ school
281
+ student { association(:student, profile: instance, school:) }
282
+ end
283
+ end
284
+
285
+ RSpec.describe "Complex interconnected models" do
286
+ describe "shared associations" do
287
+ let(:student) { build(:student) }
288
+
289
+ it "shares the same school across related records" do
290
+ expect(student.profile.school).to eq(student.school)
291
+ end
292
+ end
293
+ end
294
+
295
+ # Overriding associations at call time
296
+ RSpec.describe "Overriding associations" do
297
+ describe "passing existing record" do
298
+ let(:author) { create(:user, name: "Specific Author") }
299
+ let(:post) { create(:post, user: author) }
300
+
301
+ it "uses provided association" do
302
+ expect(post.user).to eq(author)
303
+ expect(post.user.name).to eq("Specific Author")
304
+ end
305
+ end
306
+
307
+ describe "nil association" do
308
+ let(:post) { build(:post, user: nil) }
309
+
310
+ it "allows nil" do
311
+ expect(post.user).to be_nil
312
+ end
313
+ end
314
+ end
@@ -0,0 +1,272 @@
1
+ # FactoryBot: Build Strategies Examples
2
+ # Source: factory_bot gem spec/acceptance/
3
+
4
+ # Build strategies control how objects are constructed:
5
+ # - build: in-memory, not persisted
6
+ # - create: persisted to database
7
+ # - build_stubbed: fake persisted (fastest)
8
+ # - attributes_for: returns hash only
9
+
10
+ # build - constructs without persisting
11
+ RSpec.describe "build strategy" do
12
+ describe "basic usage" do
13
+ let(:user) { build(:user) }
14
+
15
+ it "creates a new record" do
16
+ expect(user).to be_new_record
17
+ end
18
+
19
+ it "does not persist to database" do
20
+ expect(user).not_to be_persisted
21
+ end
22
+
23
+ it "can validate" do
24
+ expect(user).to be_valid
25
+ end
26
+ end
27
+
28
+ describe "with attributes" do
29
+ let(:user) { build(:user, name: "Custom Name") }
30
+
31
+ it "applies attribute overrides" do
32
+ expect(user.name).to eq("Custom Name")
33
+ end
34
+ end
35
+
36
+ describe "with traits" do
37
+ let(:user) { build(:user, :admin) }
38
+
39
+ it "applies trait attributes" do
40
+ expect(user.admin).to be true
41
+ end
42
+ end
43
+
44
+ describe "with block" do
45
+ it "yields the built instance" do
46
+ build(:user, name: "John") do |user|
47
+ expect(user.name).to eq("John")
48
+ expect(user).to be_new_record
49
+ end
50
+ end
51
+ end
52
+
53
+ describe "associations" do
54
+ let(:post) { build(:post) }
55
+
56
+ it "builds associations (not persisted)" do
57
+ expect(post.author).to be_new_record
58
+ end
59
+ end
60
+ end
61
+
62
+ # create - constructs and persists
63
+ RSpec.describe "create strategy" do
64
+ describe "basic usage" do
65
+ let(:user) { create(:user) }
66
+
67
+ it "is not a new record" do
68
+ expect(user).not_to be_new_record
69
+ end
70
+
71
+ it "is persisted" do
72
+ expect(user).to be_persisted
73
+ end
74
+
75
+ it "has a database ID" do
76
+ expect(user.id).to be_present
77
+ end
78
+ end
79
+
80
+ describe "associations" do
81
+ let(:post) { create(:post) }
82
+
83
+ it "creates associated records" do
84
+ expect(post.author).to be_persisted
85
+ end
86
+ end
87
+
88
+ describe "with block" do
89
+ it "yields the created instance" do
90
+ create(:user, name: "Jane") do |user|
91
+ expect(user.name).to eq("Jane")
92
+ expect(user).to be_persisted
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ # build_stubbed - fake persisted (fastest)
99
+ RSpec.describe "build_stubbed strategy" do
100
+ describe "basic usage" do
101
+ let(:user) { build_stubbed(:user) }
102
+
103
+ it "appears persisted" do
104
+ expect(user).to be_persisted
105
+ end
106
+
107
+ it "is not a new record" do
108
+ expect(user).not_to be_new_record
109
+ end
110
+
111
+ it "has a sequential ID" do
112
+ expect(user.id).to be > 0
113
+ end
114
+
115
+ it "has timestamps set" do
116
+ expect(user.created_at).to be_present
117
+ expect(user.updated_at).to be_present
118
+ end
119
+
120
+ it "is not marked as changed" do
121
+ expect(user).not_to be_changed
122
+ end
123
+ end
124
+
125
+ describe "persistence methods raise" do
126
+ let(:user) { build_stubbed(:user) }
127
+
128
+ it "raises on save" do
129
+ expect { user.save }.to raise_error(RuntimeError)
130
+ end
131
+
132
+ it "raises on destroy" do
133
+ expect { user.destroy }.to raise_error(RuntimeError)
134
+ end
135
+ end
136
+
137
+ describe "associations" do
138
+ let(:post) { build_stubbed(:post) }
139
+
140
+ it "stubs associations too" do
141
+ expect(post.author).to be_persisted
142
+ expect(post.author).not_to be_new_record
143
+ end
144
+ end
145
+ end
146
+
147
+ # attributes_for - returns hash only
148
+ RSpec.describe "attributes_for strategy" do
149
+ describe "basic usage" do
150
+ subject(:attrs) { attributes_for(:user) }
151
+
152
+ it "returns a hash" do
153
+ expect(attrs).to be_a(Hash)
154
+ end
155
+
156
+ it "includes factory attributes" do
157
+ expect(attrs).to have_key(:name)
158
+ expect(attrs).to have_key(:email)
159
+ end
160
+
161
+ it "excludes associations" do
162
+ expect(attrs).not_to have_key(:posts)
163
+ expect(attrs).not_to have_key(:company_id)
164
+ end
165
+ end
166
+
167
+ describe "with overrides" do
168
+ subject(:attrs) { attributes_for(:user, name: "Custom") }
169
+
170
+ it "applies overrides" do
171
+ expect(attrs[:name]).to eq("Custom")
172
+ end
173
+ end
174
+
175
+ describe "with traits" do
176
+ subject(:attrs) { attributes_for(:user, :admin) }
177
+
178
+ it "applies trait attributes" do
179
+ expect(attrs[:admin]).to be true
180
+ end
181
+ end
182
+
183
+ describe "excludes transient attributes" do
184
+ # Given factory with transient { posts_count { 5 } }
185
+ subject(:attrs) { attributes_for(:user) }
186
+
187
+ it "does not include transient attributes" do
188
+ expect(attrs).not_to have_key(:posts_count)
189
+ end
190
+ end
191
+ end
192
+
193
+ # List methods
194
+ RSpec.describe "list methods" do
195
+ describe "build_list" do
196
+ let(:users) { build_list(:user, 5) }
197
+
198
+ it "builds multiple records" do
199
+ expect(users.length).to eq(5)
200
+ end
201
+
202
+ it "builds without persisting" do
203
+ users.each do |user|
204
+ expect(user).to be_new_record
205
+ end
206
+ end
207
+ end
208
+
209
+ describe "create_list" do
210
+ let(:users) { create_list(:user, 3) }
211
+
212
+ it "creates multiple records" do
213
+ expect(users.length).to eq(3)
214
+ end
215
+
216
+ it "persists all records" do
217
+ users.each do |user|
218
+ expect(user).to be_persisted
219
+ end
220
+ end
221
+ end
222
+
223
+ describe "build_stubbed_list" do
224
+ let(:users) { build_stubbed_list(:user, 3) }
225
+
226
+ it "stubs multiple records" do
227
+ users.each do |user|
228
+ expect(user).to be_persisted
229
+ expect(user.id).to be > 0
230
+ end
231
+ end
232
+ end
233
+
234
+ describe "attributes_for_list" do
235
+ let(:attrs_list) { attributes_for_list(:user, 3) }
236
+
237
+ it "returns array of hashes" do
238
+ expect(attrs_list.length).to eq(3)
239
+ attrs_list.each { |attrs| expect(attrs).to be_a(Hash) }
240
+ end
241
+ end
242
+
243
+ describe "pair methods" do
244
+ let(:users) { create_pair(:user) }
245
+
246
+ it "creates exactly 2 records" do
247
+ expect(users.length).to eq(2)
248
+ end
249
+ end
250
+
251
+ describe "list with traits and overrides" do
252
+ let(:admins) { create_list(:user, 3, :admin, name: "Admin") }
253
+
254
+ it "applies traits to all" do
255
+ admins.each { |user| expect(user.admin).to be true }
256
+ end
257
+
258
+ it "applies overrides to all" do
259
+ admins.each { |user| expect(user.name).to eq("Admin") }
260
+ end
261
+ end
262
+
263
+ describe "list with block and index" do
264
+ it "yields each instance with index" do
265
+ users = build_list(:user, 3) do |user, index|
266
+ user.position = index + 1
267
+ end
268
+
269
+ expect(users.map(&:position)).to eq([1, 2, 3])
270
+ end
271
+ end
272
+ end