Skip to content

Commit f32e49c

Browse files
Add Pipelines and Deals read models to CRM app
1 parent 53ab40d commit f32e49c

10 files changed

Lines changed: 364 additions & 1 deletion

File tree

apps/crm/.mutant.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,17 @@ matcher:
1111
subjects:
1212
- Companies*
1313
- Contacts*
14+
- Pipelines*
15+
- Deals*
1416
ignore:
1517
- Companies::Company
1618
- Companies::Configuration#call
1719
- Contacts::Contact
1820
- Contacts::Configuration#call
21+
- Pipelines::Pipeline
22+
- Pipelines::Stage
23+
- Pipelines::Configuration#call
24+
- Pipelines.stages_for
25+
- Deals::Deal
26+
- Deals::Configuration#call
27+
- Deals.for_pipeline
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
module Deals
2+
class Deal < ApplicationRecord
3+
self.table_name = "deals"
4+
end
5+
private_constant :Deal
6+
7+
def self.all
8+
Deal.order(id: :asc)
9+
end
10+
11+
def self.find_by_uid(uid)
12+
Deal.find_by!(uid: uid)
13+
end
14+
15+
def self.for_pipeline(pipeline_uid)
16+
Deal.where(pipeline_uid: pipeline_uid).order(id: :asc)
17+
end
18+
19+
class CreateDeal
20+
def call(event)
21+
Deal.create!(
22+
uid: event.data.fetch(:deal_id),
23+
pipeline_uid: event.data.fetch(:pipeline_id),
24+
name: event.data.fetch(:name)
25+
)
26+
end
27+
end
28+
29+
class SetValue
30+
def call(event)
31+
Deal.find_by!(uid: event.data.fetch(:deal_id)).update!(value: event.data.fetch(:value))
32+
end
33+
end
34+
35+
class SetExpectedCloseDate
36+
def call(event)
37+
Deal.find_by!(uid: event.data.fetch(:deal_id)).update!(expected_close_date: event.data.fetch(:expected_close_date))
38+
end
39+
end
40+
41+
class MoveToStage
42+
def call(event)
43+
Deal.find_by!(uid: event.data.fetch(:deal_id)).update!(stage: event.data.fetch(:stage))
44+
end
45+
end
46+
47+
class Configuration
48+
def call(event_store)
49+
event_store.subscribe(CreateDeal.new, to: [Crm::DealCreated])
50+
event_store.subscribe(SetValue.new, to: [Crm::DealValueSet])
51+
event_store.subscribe(SetExpectedCloseDate.new, to: [Crm::DealExpectedCloseDateSet])
52+
event_store.subscribe(MoveToStage.new, to: [Crm::DealMovedToStage])
53+
end
54+
end
55+
end
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
module Pipelines
2+
class Pipeline < ApplicationRecord
3+
self.table_name = "pipelines"
4+
end
5+
private_constant :Pipeline
6+
7+
class Stage < ApplicationRecord
8+
self.table_name = "pipeline_stages"
9+
end
10+
private_constant :Stage
11+
12+
def self.all
13+
Pipeline.order(id: :asc)
14+
end
15+
16+
def self.find_by_uid(uid)
17+
Pipeline.find_by!(uid: uid)
18+
end
19+
20+
def self.stages_for(pipeline_uid)
21+
Stage.where(pipeline_uid: pipeline_uid).order(id: :asc)
22+
end
23+
24+
class CreatePipeline
25+
def call(event)
26+
Pipeline.create!(
27+
uid: event.data.fetch(:pipeline_id),
28+
name: event.data.fetch(:name)
29+
)
30+
end
31+
end
32+
33+
class AddStage
34+
def call(event)
35+
Stage.create!(
36+
pipeline_uid: event.data.fetch(:pipeline_id),
37+
stage_name: event.data.fetch(:stage_name)
38+
)
39+
end
40+
end
41+
42+
class RemoveStage
43+
def call(event)
44+
Stage.find_by!(
45+
pipeline_uid: event.data.fetch(:pipeline_id),
46+
stage_name: event.data.fetch(:stage_name)
47+
).destroy!
48+
end
49+
end
50+
51+
class Configuration
52+
def call(event_store)
53+
event_store.subscribe(CreatePipeline.new, to: [Crm::PipelineCreated])
54+
event_store.subscribe(AddStage.new, to: [Crm::StageAddedToPipeline])
55+
event_store.subscribe(RemoveStage.new, to: [Crm::StageRemovedFromPipeline])
56+
end
57+
end
58+
end
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
class CreatePipelines < ActiveRecord::Migration[8.0]
2+
def change
3+
create_table :pipelines do |t|
4+
t.uuid :uid, null: false
5+
t.string :name, null: false
6+
7+
t.timestamps
8+
end
9+
add_index :pipelines, :uid, unique: true
10+
end
11+
end
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
class CreatePipelineStages < ActiveRecord::Migration[8.0]
2+
def change
3+
create_table :pipeline_stages do |t|
4+
t.uuid :pipeline_uid, null: false
5+
t.string :stage_name, null: false
6+
7+
t.timestamps
8+
end
9+
add_index :pipeline_stages, [:pipeline_uid, :stage_name], unique: true
10+
end
11+
end
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
class CreateDeals < ActiveRecord::Migration[8.0]
2+
def change
3+
create_table :deals do |t|
4+
t.uuid :uid, null: false
5+
t.uuid :pipeline_uid, null: false
6+
t.string :name, null: false
7+
t.integer :value
8+
t.string :expected_close_date
9+
t.string :stage
10+
11+
t.timestamps
12+
end
13+
add_index :deals, :uid, unique: true
14+
end
15+
end

apps/crm/db/schema.rb

Lines changed: 29 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/crm/lib/configuration.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ def call(event_store, command_bus)
66
enable_res_infra_event_linking(event_store)
77
enable_contacts_read_model(event_store)
88
enable_companies_read_model(event_store)
9+
enable_pipelines_read_model(event_store)
10+
enable_deals_read_model(event_store)
911

1012
Crm::Configuration.new.call(event_store, command_bus)
1113
end
@@ -20,6 +22,14 @@ def enable_contacts_read_model(event_store)
2022
Contacts::Configuration.new.call(event_store)
2123
end
2224

25+
def enable_pipelines_read_model(event_store)
26+
Pipelines::Configuration.new.call(event_store)
27+
end
28+
29+
def enable_deals_read_model(event_store)
30+
Deals::Configuration.new.call(event_store)
31+
end
32+
2333
def enable_res_infra_event_linking(event_store)
2434
[
2535
RailsEventStore::LinkByEventType.new,

apps/crm/test/deals/deals_test.rb

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
require "test_helper"
2+
3+
module Deals
4+
class DealsTest < InMemoryRESTestCase
5+
cover "Deals*"
6+
7+
def test_deal_created
8+
create_deal(deal_id, pipeline_id, "Big Deal")
9+
10+
assert_equal(1, Deals.all.count)
11+
deal = Deals.find_by_uid(deal_id)
12+
assert_equal("Big Deal", deal.name)
13+
assert_equal(pipeline_id, deal.pipeline_uid)
14+
end
15+
16+
def test_multiple_deals
17+
create_deal(deal_id, pipeline_id, "Big Deal")
18+
create_deal(other_deal_id, pipeline_id, "Small Deal")
19+
20+
assert_equal(2, Deals.all.count)
21+
assert_equal(["Big Deal", "Small Deal"], Deals.all.map(&:name))
22+
end
23+
24+
def test_for_pipeline
25+
other_pipeline_id = SecureRandom.uuid
26+
create_deal(deal_id, pipeline_id, "Big Deal")
27+
create_deal(other_deal_id, other_pipeline_id, "Small Deal")
28+
29+
assert_equal(["Big Deal"], Deals.for_pipeline(pipeline_id).map(&:name))
30+
assert_equal(["Small Deal"], Deals.for_pipeline(other_pipeline_id).map(&:name))
31+
end
32+
33+
def test_value_set
34+
create_deal(deal_id, pipeline_id, "Big Deal")
35+
create_deal(other_deal_id, pipeline_id, "Small Deal")
36+
set_value(deal_id, 10_000)
37+
38+
assert_equal(10_000, Deals.find_by_uid(deal_id).value)
39+
assert_nil(Deals.find_by_uid(other_deal_id).value)
40+
end
41+
42+
def test_expected_close_date_set
43+
create_deal(deal_id, pipeline_id, "Big Deal")
44+
create_deal(other_deal_id, pipeline_id, "Small Deal")
45+
set_expected_close_date(deal_id, "2026-03-01")
46+
47+
assert_equal("2026-03-01", Deals.find_by_uid(deal_id).expected_close_date)
48+
assert_nil(Deals.find_by_uid(other_deal_id).expected_close_date)
49+
end
50+
51+
def test_moved_to_stage
52+
create_deal(deal_id, pipeline_id, "Big Deal")
53+
create_deal(other_deal_id, pipeline_id, "Small Deal")
54+
move_to_stage(deal_id, "Negotiation")
55+
56+
assert_equal("Negotiation", Deals.find_by_uid(deal_id).stage)
57+
assert_nil(Deals.find_by_uid(other_deal_id).stage)
58+
end
59+
60+
private
61+
62+
def deal_id
63+
@deal_id ||= SecureRandom.uuid
64+
end
65+
66+
def other_deal_id
67+
@other_deal_id ||= SecureRandom.uuid
68+
end
69+
70+
def pipeline_id
71+
@pipeline_id ||= SecureRandom.uuid
72+
end
73+
74+
def create_deal(uid, pipeline_uid, name)
75+
event_store.publish(Crm::DealCreated.new(data: { deal_id: uid, pipeline_id: pipeline_uid, name: name }))
76+
end
77+
78+
def set_value(uid, value)
79+
event_store.publish(Crm::DealValueSet.new(data: { deal_id: uid, value: value }))
80+
end
81+
82+
def set_expected_close_date(uid, date)
83+
event_store.publish(Crm::DealExpectedCloseDateSet.new(data: { deal_id: uid, expected_close_date: date }))
84+
end
85+
86+
def move_to_stage(uid, stage)
87+
event_store.publish(Crm::DealMovedToStage.new(data: { deal_id: uid, stage: stage }))
88+
end
89+
end
90+
end

0 commit comments

Comments
 (0)