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,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
|