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.
- checksums.yaml +4 -4
- data/.reek.yml +27 -1
- data/CHANGELOG.md +4 -0
- data/README.md +219 -25
- 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 +76 -28
- data/app/jobs/agent_request_job.rb +24 -0
- data/app/jobs/analytical_brain_job.rb +33 -0
- data/app/jobs/count_event_tokens_job.rb +1 -1
- data/app/models/concerns/event/broadcasting.rb +20 -2
- data/app/models/event.rb +1 -1
- data/app/models/goal.rb +91 -0
- data/app/models/session.rb +347 -22
- data/config/application.rb +2 -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 -9
- 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 +4 -0
- 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/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 +8 -7
- 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 +3 -4
- data/lib/tools/mcp_tool.rb +114 -0
- data/lib/tools/read.rb +15 -16
- 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/tui/app.rb +332 -43
- data/lib/tui/message_store.rb +20 -0
- data/lib/tui/screens/chat.rb +207 -20
- 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 +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
|