anima-core 0.2.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.reek.yml +27 -1
- data/CHANGELOG.md +19 -0
- data/README.md +213 -43
- data/agents/codebase-analyzer.md +88 -0
- data/agents/codebase-pattern-finder.md +83 -0
- data/agents/documentation-researcher.md +59 -0
- data/agents/thoughts-analyzer.md +102 -0
- data/agents/web-search-researcher.md +71 -0
- data/anima-core.gemspec +3 -0
- data/app/channels/session_channel.rb +195 -45
- data/app/decorators/user_message_decorator.rb +16 -5
- data/app/jobs/agent_request_job.rb +55 -2
- data/app/jobs/analytical_brain_job.rb +33 -0
- data/app/jobs/count_event_tokens_job.rb +15 -4
- data/app/models/concerns/event/broadcasting.rb +81 -0
- data/app/models/event.rb +20 -1
- data/app/models/goal.rb +91 -0
- data/app/models/session.rb +366 -21
- data/config/application.rb +2 -0
- data/config/initializers/event_subscribers.rb +0 -1
- data/config/routes.rb +0 -6
- data/db/migrate/20260313010000_add_status_to_events.rb +8 -0
- data/db/migrate/20260313020000_add_processing_to_sessions.rb +7 -0
- data/db/migrate/20260314075248_add_subagent_support_to_sessions.rb +6 -0
- data/db/migrate/20260314112417_add_granted_tools_to_sessions.rb +5 -0
- data/db/migrate/20260314140000_add_name_to_sessions.rb +7 -0
- data/db/migrate/20260314150000_add_viewport_event_ids_to_sessions.rb +7 -0
- data/db/migrate/20260315100000_add_active_skills_to_sessions.rb +7 -0
- data/db/migrate/20260315140843_create_goals.rb +16 -0
- data/db/migrate/20260315144837_add_completed_at_to_goals.rb +5 -0
- data/db/migrate/20260315191105_add_active_workflow_to_sessions.rb +5 -0
- data/lib/agent_loop.rb +65 -6
- data/lib/agents/definition.rb +116 -0
- data/lib/agents/registry.rb +106 -0
- data/lib/analytical_brain/runner.rb +276 -0
- data/lib/analytical_brain/tools/activate_skill.rb +52 -0
- data/lib/analytical_brain/tools/deactivate_skill.rb +43 -0
- data/lib/analytical_brain/tools/deactivate_workflow.rb +34 -0
- data/lib/analytical_brain/tools/everything_is_ready.rb +28 -0
- data/lib/analytical_brain/tools/finish_goal.rb +62 -0
- data/lib/analytical_brain/tools/read_workflow.rb +58 -0
- data/lib/analytical_brain/tools/rename_session.rb +63 -0
- data/lib/analytical_brain/tools/set_goal.rb +60 -0
- data/lib/analytical_brain/tools/update_goal.rb +60 -0
- data/lib/analytical_brain.rb +23 -0
- data/lib/anima/cli/mcp/secrets.rb +76 -0
- data/lib/anima/cli/mcp.rb +197 -0
- data/lib/anima/cli.rb +5 -40
- data/lib/anima/installer.rb +168 -0
- data/lib/anima/settings.rb +226 -0
- data/lib/anima/version.rb +1 -1
- data/lib/anima.rb +9 -0
- data/lib/credential_store.rb +103 -0
- data/lib/environment_probe.rb +232 -0
- data/lib/events/subscribers/persister.rb +1 -0
- data/lib/events/user_message.rb +17 -0
- data/lib/llm/client.rb +29 -10
- data/lib/mcp/client_manager.rb +86 -0
- data/lib/mcp/config.rb +213 -0
- data/lib/mcp/health_check.rb +77 -0
- data/lib/mcp/secrets.rb +73 -0
- data/lib/mcp/stdio_transport.rb +206 -0
- data/lib/providers/anthropic.rb +11 -20
- data/lib/shell_session.rb +11 -10
- data/lib/skills/definition.rb +97 -0
- data/lib/skills/registry.rb +105 -0
- data/lib/tools/edit.rb +226 -0
- data/lib/tools/mcp_tool.rb +114 -0
- data/lib/tools/read.rb +151 -0
- data/lib/tools/registry.rb +14 -12
- data/lib/tools/request_feature.rb +121 -0
- data/lib/tools/return_result.rb +81 -0
- data/lib/tools/spawn_specialist.rb +109 -0
- data/lib/tools/spawn_subagent.rb +111 -0
- data/lib/tools/subagent_prompts.rb +12 -0
- data/lib/tools/web_get.rb +8 -9
- data/lib/tools/write.rb +86 -0
- data/lib/tui/app.rb +985 -26
- data/lib/tui/cable_client.rb +69 -31
- data/lib/tui/message_store.rb +103 -8
- data/lib/tui/screens/chat.rb +293 -45
- data/lib/workflows/definition.rb +97 -0
- data/lib/workflows/registry.rb +89 -0
- data/skills/activerecord/SKILL.md +255 -0
- data/skills/activerecord/examples/associations/association_extensions.rb +298 -0
- data/skills/activerecord/examples/associations/basic_associations.rb +118 -0
- data/skills/activerecord/examples/associations/counter_caches.rb +215 -0
- data/skills/activerecord/examples/associations/polymorphic_associations.rb +217 -0
- data/skills/activerecord/examples/associations/self_referential.rb +302 -0
- data/skills/activerecord/examples/associations/through_associations.rb +203 -0
- data/skills/activerecord/examples/basics/crud_operations.rb +209 -0
- data/skills/activerecord/examples/basics/dirty_tracking.rb +218 -0
- data/skills/activerecord/examples/basics/inheritance.rb +377 -0
- data/skills/activerecord/examples/basics/type_casting.rb +317 -0
- data/skills/activerecord/examples/callbacks/alternatives_to_callbacks.rb +447 -0
- data/skills/activerecord/examples/callbacks/conditional_callbacks.rb +353 -0
- data/skills/activerecord/examples/callbacks/lifecycle_callbacks.rb +280 -0
- data/skills/activerecord/examples/callbacks/transaction_callbacks.rb +340 -0
- data/skills/activerecord/examples/migrations/indexes_and_constraints.rb +337 -0
- data/skills/activerecord/examples/migrations/reversible_patterns.rb +403 -0
- data/skills/activerecord/examples/migrations/safe_patterns.rb +420 -0
- data/skills/activerecord/examples/migrations/schema_changes.rb +277 -0
- data/skills/activerecord/examples/querying/batch_processing.rb +226 -0
- data/skills/activerecord/examples/querying/eager_loading.rb +259 -0
- data/skills/activerecord/examples/querying/finder_methods.rb +170 -0
- data/skills/activerecord/examples/querying/optimization.rb +275 -0
- data/skills/activerecord/examples/querying/scopes.rb +260 -0
- data/skills/activerecord/examples/validations/built_in_validators.rb +277 -0
- data/skills/activerecord/examples/validations/conditional_validations.rb +288 -0
- data/skills/activerecord/examples/validations/custom_validators.rb +381 -0
- data/skills/activerecord/examples/validations/database_constraints.rb +432 -0
- data/skills/activerecord/examples/validations/validation_contexts.rb +367 -0
- data/skills/activerecord/references/associations.md +709 -0
- data/skills/activerecord/references/basics.md +622 -0
- data/skills/activerecord/references/callbacks.md +738 -0
- data/skills/activerecord/references/migrations.md +657 -0
- data/skills/activerecord/references/querying.md +655 -0
- data/skills/activerecord/references/validations.md +596 -0
- data/skills/dragonruby/SKILL.md +250 -0
- data/skills/dragonruby/examples/audio/audio_events.rb +55 -0
- data/skills/dragonruby/examples/audio/background_music.rb +29 -0
- data/skills/dragonruby/examples/audio/crossfade.rb +51 -0
- data/skills/dragonruby/examples/audio/music_controls.rb +51 -0
- data/skills/dragonruby/examples/audio/sound_effects.rb +30 -0
- data/skills/dragonruby/examples/core/coordinate_system.rb +27 -0
- data/skills/dragonruby/examples/core/hello_world.rb +24 -0
- data/skills/dragonruby/examples/core/labels.rb +22 -0
- data/skills/dragonruby/examples/core/sprites.rb +35 -0
- data/skills/dragonruby/examples/core/state_management.rb +29 -0
- data/skills/dragonruby/examples/distribution/background_pause.rb +42 -0
- data/skills/dragonruby/examples/distribution/build_workflow.sh +26 -0
- data/skills/dragonruby/examples/distribution/cvars_production.txt +16 -0
- data/skills/dragonruby/examples/distribution/game_metadata_hd.txt +23 -0
- data/skills/dragonruby/examples/distribution/game_metadata_minimal.txt +9 -0
- data/skills/dragonruby/examples/distribution/game_metadata_mobile.txt +31 -0
- data/skills/dragonruby/examples/distribution/platform_detection.rb +36 -0
- data/skills/dragonruby/examples/distribution/steam_metadata.txt +19 -0
- data/skills/dragonruby/examples/entities/collision_detection.rb +43 -0
- data/skills/dragonruby/examples/entities/entity_lifecycle.rb +68 -0
- data/skills/dragonruby/examples/entities/entity_storage.rb +38 -0
- data/skills/dragonruby/examples/entities/factory_methods.rb +45 -0
- data/skills/dragonruby/examples/entities/random_spawning.rb +50 -0
- data/skills/dragonruby/examples/game-logic/reset_patterns.rb +98 -0
- data/skills/dragonruby/examples/game-logic/save_load.rb +101 -0
- data/skills/dragonruby/examples/game-logic/scoring.rb +104 -0
- data/skills/dragonruby/examples/game-logic/state_transitions.rb +103 -0
- data/skills/dragonruby/examples/game-logic/timers.rb +87 -0
- data/skills/dragonruby/examples/input/action_triggers.rb +36 -0
- data/skills/dragonruby/examples/input/analog_movement.rb +28 -0
- data/skills/dragonruby/examples/input/controller_input.rb +28 -0
- data/skills/dragonruby/examples/input/directional_input.rb +24 -0
- data/skills/dragonruby/examples/input/keyboard_input.rb +28 -0
- data/skills/dragonruby/examples/input/mouse_click.rb +26 -0
- data/skills/dragonruby/examples/input/movement_with_bounds.rb +22 -0
- data/skills/dragonruby/examples/input/normalized_movement.rb +32 -0
- data/skills/dragonruby/examples/rendering/frame_animation.rb +32 -0
- data/skills/dragonruby/examples/rendering/labels.rb +32 -0
- data/skills/dragonruby/examples/rendering/layering.rb +51 -0
- data/skills/dragonruby/examples/rendering/solids.rb +61 -0
- data/skills/dragonruby/examples/rendering/sprites.rb +33 -0
- data/skills/dragonruby/examples/rendering/spritesheet_animation.rb +39 -0
- data/skills/dragonruby/examples/scenes/case_dispatch.rb +60 -0
- data/skills/dragonruby/examples/scenes/class_based.rb +150 -0
- data/skills/dragonruby/examples/scenes/pause_overlay.rb +100 -0
- data/skills/dragonruby/examples/scenes/safe_transitions.rb +68 -0
- data/skills/dragonruby/examples/scenes/scene_transitions.rb +98 -0
- data/skills/dragonruby/examples/scenes/send_dispatch.rb +88 -0
- data/skills/dragonruby/references/audio.md +396 -0
- data/skills/dragonruby/references/core.md +385 -0
- data/skills/dragonruby/references/distribution.md +434 -0
- data/skills/dragonruby/references/entities.md +516 -0
- data/skills/dragonruby/references/game-logic/persistence.md +386 -0
- data/skills/dragonruby/references/game-logic/state.md +389 -0
- data/skills/dragonruby/references/input.md +414 -0
- data/skills/dragonruby/references/rendering/animation.md +467 -0
- data/skills/dragonruby/references/rendering/primitives.md +403 -0
- data/skills/dragonruby/references/scenes.md +443 -0
- data/skills/draper-decorators/SKILL.md +344 -0
- data/skills/draper-decorators/examples/application_decorator.rb +61 -0
- data/skills/draper-decorators/examples/decorator_spec.rb +253 -0
- data/skills/draper-decorators/examples/model_decorator.rb +152 -0
- data/skills/draper-decorators/references/anti-patterns.md +640 -0
- data/skills/draper-decorators/references/patterns.md +507 -0
- data/skills/draper-decorators/references/testing.md +559 -0
- data/skills/gh-issue.md +182 -0
- data/skills/mcp-server/SKILL.md +177 -0
- data/skills/mcp-server/examples/dynamic_tools.rb +36 -0
- data/skills/mcp-server/examples/file_manager_tool.rb +85 -0
- data/skills/mcp-server/examples/http_client.rb +48 -0
- data/skills/mcp-server/examples/http_server.rb +97 -0
- data/skills/mcp-server/examples/rails_integration.rb +88 -0
- data/skills/mcp-server/examples/stdio_server.rb +108 -0
- data/skills/mcp-server/examples/streaming_client.rb +95 -0
- data/skills/mcp-server/references/gotchas.md +183 -0
- data/skills/mcp-server/references/prompts.md +98 -0
- data/skills/mcp-server/references/resources.md +53 -0
- data/skills/mcp-server/references/server.md +140 -0
- data/skills/mcp-server/references/tools.md +146 -0
- data/skills/mcp-server/references/transport.md +104 -0
- data/skills/ratatui-ruby/SKILL.md +315 -0
- data/skills/ratatui-ruby/references/core-concepts.md +340 -0
- data/skills/ratatui-ruby/references/events.md +387 -0
- data/skills/ratatui-ruby/references/frameworks.md +522 -0
- data/skills/ratatui-ruby/references/layout.md +423 -0
- data/skills/ratatui-ruby/references/styling.md +268 -0
- data/skills/ratatui-ruby/references/testing.md +433 -0
- data/skills/ratatui-ruby/references/widgets.md +532 -0
- data/skills/rspec/SKILL.md +340 -0
- data/skills/rspec/examples/core/basic_structure.rb +69 -0
- data/skills/rspec/examples/core/configuration.rb +126 -0
- data/skills/rspec/examples/core/hooks.rb +126 -0
- data/skills/rspec/examples/core/memoized_helpers.rb +139 -0
- data/skills/rspec/examples/core/metadata_filtering.rb +144 -0
- data/skills/rspec/examples/core/shared_examples.rb +145 -0
- data/skills/rspec/examples/factory_bot/associations.rb +314 -0
- data/skills/rspec/examples/factory_bot/build_strategies.rb +272 -0
- data/skills/rspec/examples/factory_bot/callbacks.rb +320 -0
- data/skills/rspec/examples/factory_bot/custom_construction.rb +328 -0
- data/skills/rspec/examples/factory_bot/factory_definition.rb +191 -0
- data/skills/rspec/examples/factory_bot/inheritance.rb +314 -0
- data/skills/rspec/examples/factory_bot/traits.rb +293 -0
- data/skills/rspec/examples/factory_bot/transients.rb +229 -0
- data/skills/rspec/examples/matchers/change.rb +115 -0
- data/skills/rspec/examples/matchers/collections.rb +154 -0
- data/skills/rspec/examples/matchers/comparisons.rb +79 -0
- data/skills/rspec/examples/matchers/composing.rb +155 -0
- data/skills/rspec/examples/matchers/custom_matchers.rb +197 -0
- data/skills/rspec/examples/matchers/equality.rb +58 -0
- data/skills/rspec/examples/matchers/errors.rb +136 -0
- data/skills/rspec/examples/matchers/output.rb +103 -0
- data/skills/rspec/examples/matchers/predicates.rb +87 -0
- data/skills/rspec/examples/matchers/truthiness.rb +101 -0
- data/skills/rspec/examples/matchers/types.rb +82 -0
- data/skills/rspec/examples/matchers/yield.rb +147 -0
- data/skills/rspec/examples/mocks/any_instance.rb +172 -0
- data/skills/rspec/examples/mocks/argument_matchers.rb +206 -0
- data/skills/rspec/examples/mocks/constants.rb +177 -0
- data/skills/rspec/examples/mocks/doubles.rb +139 -0
- data/skills/rspec/examples/mocks/expectations.rb +137 -0
- data/skills/rspec/examples/mocks/message_chains.rb +173 -0
- data/skills/rspec/examples/mocks/ordering.rb +144 -0
- data/skills/rspec/examples/mocks/receive_counts.rb +181 -0
- data/skills/rspec/examples/mocks/responses.rb +223 -0
- data/skills/rspec/examples/mocks/spies.rb +149 -0
- data/skills/rspec/examples/mocks/stubbing.rb +133 -0
- data/skills/rspec/examples/rails/channels.rb +250 -0
- data/skills/rspec/examples/rails/controller_specs.rb +302 -0
- data/skills/rspec/examples/rails/helper_specs.rb +245 -0
- data/skills/rspec/examples/rails/job_specs.rb +256 -0
- data/skills/rspec/examples/rails/mailer_specs.rb +228 -0
- data/skills/rspec/examples/rails/matchers.rb +374 -0
- data/skills/rspec/examples/rails/model_specs.rb +193 -0
- data/skills/rspec/examples/rails/request_specs.rb +275 -0
- data/skills/rspec/examples/rails/routing_specs.rb +276 -0
- data/skills/rspec/examples/rails/system_specs.rb +294 -0
- data/skills/rspec/examples/rails/transactions.rb +254 -0
- data/skills/rspec/examples/rails/view_specs.rb +252 -0
- data/skills/rspec/references/core.md +816 -0
- data/skills/rspec/references/factory_bot.md +641 -0
- data/skills/rspec/references/matchers.md +516 -0
- data/skills/rspec/references/mocks.md +381 -0
- data/skills/rspec/references/rails.md +528 -0
- data/templates/soul.md +40 -0
- data/workflows/commit.md +45 -0
- data/workflows/create_handoff.md +98 -0
- data/workflows/create_note.md +82 -0
- data/workflows/create_plan.md +457 -0
- data/workflows/decompose_ticket.md +109 -0
- data/workflows/feature.md +91 -0
- data/workflows/implement_plan.md +87 -0
- data/workflows/iterate_plan.md +247 -0
- data/workflows/research_codebase.md +210 -0
- data/workflows/resume_handoff.md +217 -0
- data/workflows/review_pr.md +320 -0
- data/workflows/thoughts_init.md +71 -0
- data/workflows/validate_plan.md +166 -0
- metadata +290 -3
- data/app/controllers/api/sessions_controller.rb +0 -25
- data/lib/events/subscribers/action_cable_bridge.rb +0 -59
|
@@ -0,0 +1,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` |
|