Skip to content

Commit 4138ea4

Browse files
authored
Setup Convenience Defaults (#5)
1 parent bb83753 commit 4138ea4

4 files changed

Lines changed: 136 additions & 24 deletions

File tree

lib/operational/form.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def self.method_added(method_name)
1818
end
1919
end
2020

21-
def self.build(model: nil, model_persisted: nil, state: {}, prepopulate_method: :prepopulate)
21+
def self.build(model: nil, model_persisted: nil, state: {}, build_method: :on_build)
2222
form = new
2323

2424
if model
@@ -30,7 +30,7 @@ def self.build(model: nil, model_persisted: nil, state: {}, prepopulate_method:
3030

3131
form.instance_variable_set(:@_operational_model_persisted, (model_persisted.nil? ? model&.persisted? || false : !!model_persisted))
3232
form.instance_variable_set(form.send(:_operational_state_variable), state.dup.freeze)
33-
form.send(prepopulate_method, state) if form.respond_to?(prepopulate_method)
33+
form.send(build_method, state) if form.respond_to?(build_method)
3434
form.changes_applied if form.respond_to?(:changes_applied)
3535
form
3636
end

lib/operational/operation/contract.rb

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
module Operational
22
class Operation
33
module Contract
4-
def self.Build(contract:, name: :contract, model_key: nil, model_persisted: nil, prepopulate_method: :prepopulate)
4+
def self.Build(contract:, name: :contract, model_key: :model, model_persisted: nil, build_method: :on_build)
55
lambda do |state|
6-
model = model_key.present? ? state[model_key] : nil
7-
raise InvalidContractModel if model_key.present? && model.nil?
6+
model = state.key?(model_key) ? state[model_key] : nil
7+
raise InvalidContractModel if state.key?(model_key) && model.nil?
88

99
state[name] = contract.build(
1010
model: model,
1111
model_persisted: model_persisted,
1212
state: state,
13-
prepopulate_method: prepopulate_method
13+
build_method: build_method
1414
)
1515
true
1616
end
@@ -29,10 +29,10 @@ def self.Validate(name: :contract, params_path: nil)
2929
end
3030
end
3131

32-
def self.Sync(name: :contract, model_key: nil, sync_method: :on_sync)
32+
def self.Sync(name: :contract, model_key: :model, sync_method: :on_sync)
3333
lambda do |state|
34-
model = model_key.present? ? state[model_key] : nil
35-
raise InvalidContractModel if model_key.present? && model.nil?
34+
model = state.key?(model_key) ? state[model_key] : nil
35+
raise InvalidContractModel if state.key?(model_key) && model.nil?
3636

3737
state[name].sync(model: model, state: state, sync_method: sync_method)
3838
end

spec/operational/form_spec.rb

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -120,32 +120,45 @@ def persisted? = true
120120
end
121121
end
122122

123-
describe "prepopulate" do
123+
describe "on_build" do
124124
let(:form_class) do
125125
Class.new(Operational::Form) do
126126
attribute :name, :string
127127

128-
def prepopulate(state)
128+
def on_build(state)
129129
self.name = "prepopulated"
130130
end
131131
end
132132
end
133133

134-
it "calls prepopulate if defined" do
134+
it "calls on_build if defined" do
135135
form = form_class.build
136136
expect(form.name).to eq "prepopulated"
137137
end
138138

139-
it "allows overriding the prepopulate method name" do
139+
it "passes state to on_build" do
140140
form_class = Class.new(Operational::Form) do
141141
attribute :name, :string
142142

143-
def custom_prepopulate(state)
143+
def on_build(state)
144+
self.name = state[:default_name]
145+
end
146+
end
147+
148+
form = form_class.build(state: { default_name: "from state" })
149+
expect(form.name).to eq "from state"
150+
end
151+
152+
it "allows overriding the build method name" do
153+
form_class = Class.new(Operational::Form) do
154+
attribute :name, :string
155+
156+
def custom_build(state)
144157
self.name = "custom"
145158
end
146159
end
147160

148-
form = form_class.build(prepopulate_method: :custom_prepopulate)
161+
form = form_class.build(build_method: :custom_build)
149162
expect(form.name).to eq "custom"
150163
end
151164
end
@@ -310,6 +323,19 @@ def custom_sync(state)
310323
end
311324
end
312325

326+
describe "#other_validators_have_passed?" do
327+
it "returns true when there are no errors" do
328+
form = form_class.build
329+
expect(form.other_validators_have_passed?).to eq true
330+
end
331+
332+
it "returns false when there are errors" do
333+
form = form_class.build
334+
form.validate(name: "")
335+
expect(form.other_validators_have_passed?).to eq false
336+
end
337+
end
338+
313339
describe "MethodCollision" do
314340
it "raises when a subclass defines #sync" do
315341
expect {

spec/operational/operation/contract_spec.rb

Lines changed: 95 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,20 +50,62 @@
5050
model_class.new(name: "Test", email: "test@test.com")
5151
end
5252

53-
it "allows sending a model" do
54-
expect(described_class::Build(contract: CreateForm, model_key: :model).call({ model: model })).to eq true
53+
it "defaults to state[:model] when present" do
54+
expect(described_class::Build(contract: CreateForm).call({ model: model })).to eq true
55+
end
56+
57+
it "pre-populates form attributes from the model" do
58+
state = { model: model }
59+
described_class::Build(contract: CreateForm).call(state)
60+
expect(state[:contract].name).to eq "Test"
61+
end
62+
63+
it "builds without a model when state[:model] is absent" do
64+
state = {}
65+
described_class::Build(contract: CreateForm).call(state)
66+
expect(state[:contract]).to be_a CreateForm
5567
end
5668

5769
it "allows specifying the model key" do
5870
expect(described_class::Build(contract: CreateForm, model_key: :different_model).call({ different_model: model })).to eq true
5971
end
6072

61-
it "ensures the model quacks like an Active Model" do
73+
it "raises when the model key is present in state but nil" do
74+
expect {
75+
described_class::Build(contract: CreateForm).call({ model: nil })
76+
}.to raise_error Operational::InvalidContractModel
77+
end
78+
79+
it "raises when the model does not quack like an Active Model" do
6280
expect {
63-
described_class::Build(contract: CreateForm, model_key: :invalid).call({ model: model })
81+
described_class::Build(contract: CreateForm).call({ model: "not a model" })
6482
}.to raise_error Operational::InvalidContractModel
6583
end
6684
end
85+
86+
describe "model_persisted" do
87+
let(:model) { model_class.new(name: "Test") }
88+
89+
it "overrides persisted? on the form" do
90+
state = { model: model }
91+
described_class::Build(contract: CreateForm, model_persisted: true).call(state)
92+
expect(state[:contract].persisted?).to eq true
93+
end
94+
end
95+
96+
describe "build_method" do
97+
it "calls a custom build_method during build" do
98+
form_class = Class.new(Operational::Form) do
99+
attribute :name, :string
100+
def custom_build(state)
101+
self.name = "from custom build"
102+
end
103+
end
104+
state = {}
105+
described_class::Build(contract: form_class, build_method: :custom_build).call(state)
106+
expect(state[:contract].name).to eq "from custom build"
107+
end
108+
end
67109
end
68110

69111
describe ".Validate" do
@@ -143,17 +185,61 @@
143185

144186
let(:state) { { model: model, params: { name: "update", email: "update@test.com" }} }
145187

146-
it "allows sending a model" do
147-
described_class::Build(contract: CreateForm, model_key: :model).call(state)
188+
it "defaults to state[:model] when present" do
189+
described_class::Build(contract: CreateForm).call(state)
148190

149-
expect(described_class::Sync(model_key: :model).call(state)).to eq true
191+
expect(described_class::Sync().call(state)).to eq true
150192
end
151193

152-
it "ensures the model quacks like an Active Model" do
194+
it "writes form attributes back to the model" do
195+
described_class::Build(contract: CreateForm).call(state)
196+
described_class::Validate().call(state)
197+
described_class::Sync().call(state)
198+
expect(model.name).to eq "update"
199+
end
200+
201+
it "syncs without a model when state[:model] is absent" do
202+
state = { params: { name: "update" } }
203+
described_class::Build(contract: CreateForm).call(state)
204+
205+
expect(described_class::Sync().call(state)).to eq true
206+
end
207+
208+
it "allows specifying the model key" do
209+
state = { different_model: model, params: { name: "update" } }
210+
described_class::Build(contract: CreateForm, model_key: :different_model).call(state)
211+
described_class::Validate().call(state)
212+
described_class::Sync(model_key: :different_model).call(state)
213+
expect(model.name).to eq "update"
214+
end
215+
216+
it "raises when the model key is present in state but nil" do
153217
expect {
154-
described_class::Sync(model_key: :invalid).call(state)
218+
described_class::Sync().call(state.merge(model: nil))
155219
}.to raise_error Operational::InvalidContractModel
156220
end
221+
222+
it "raises when the model does not quack like an Active Model" do
223+
expect {
224+
described_class::Sync().call(state.merge(model: "not a model"))
225+
}.to raise_error Operational::InvalidContractModel
226+
end
227+
228+
describe "sync_method" do
229+
it "calls a custom sync_method during sync" do
230+
form_class = Class.new(Operational::Form) do
231+
attribute :name, :string
232+
def custom_sync(state)
233+
state[:model].email = "synced@test.com"
234+
end
235+
end
236+
state = { model: model, params: { name: "update" } }
237+
described_class::Build(contract: form_class).call(state)
238+
described_class::Validate().call(state)
239+
described_class::Sync(sync_method: :custom_sync).call(state)
240+
expect(model.email).to eq "synced@test.com"
241+
end
242+
end
157243
end
158244
end
159245
end

0 commit comments

Comments
 (0)