Skip to content

Instantly share code, notes, and snippets.

@kellysutton
Last active June 12, 2017 00:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kellysutton/4df83e15940a7f428c9d3bf0be3f7867 to your computer and use it in GitHub Desktop.
Save kellysutton/4df83e15940a7f428c9d3bf0be3f7867 to your computer and use it in GitHub Desktop.
Replace Side Effects with Return Values
class PayrollRunner
def run!
TaxEngineWrapper.new(tax_engine).apply(commands)
computed_taxes = tax_engine.calculate_taxes
payroll.assign_taxes(computed_taxes)
end
# This is now a pure function from the view of its callers.
# Sweet!
def commands
tax_engine_spy = TaxEngineSpy.new
# Commands are recorded against our tax_engine_spy,
# which pretends to be a perfectly normal `tax_engine`.
TaxEngineSetter::Factory.for(state).apply_state_info(
tax_engine_spy,
build_state_info()
)
[SetFederalInfoCommand.new(build_federal_info())] +
tax_engine_spy.generated_commands
end
end
class TaxEngineSpy
def initialize
@commands = []
end
# We did not end up using `method_missing` in on our code,
# but rather listening for each specific method.
# `method_missing` is difficult to test and didn’t provide
# the level of safety we were looking for.
def method_missing(name, *args)
@commands << Command.new(name, *args)
end
def generated_commands
@commands
end
end
class PayrollRunner
def run!
federal_info = build_federal_info()
state_info = build_state_info()
tax_engine.set_federal_info(federal_info)
tax_engine.set_state_info(state_info)
computed_taxes = tax_engine.calculate_taxes
payroll.assign_taxes(computed_taxes)
end
end
class PayrollRunner
def run!
federal_info = build_federal_info()
state_info = build_state_info()
commands = [
SetFederalInfoCommand.new(federal_info),
SetStateInfoCommand.new(state_info),
]
TaxEngineWrapper.new(tax_engine).apply(commands)
computed_taxes = tax_engine.calculate_taxes
payroll.assign_taxes(computed_taxes)
end
end
# This wrapper is designed to not change the API of something
# we don't control (the original tax engine), but allows
# us to apply our custom commands as normal method calls.
class TaxEngineWrapper
def initialize(tax_engine)
@tax_engine = tax_engine
end
def apply(commands)
commands.each do |command|
@tax_engine.public_send(
command.method_name, *command.arguments
)
end
end
end
class PayrollRunner
def run!
federal_info = build_federal_info()
state_info = build_state_info()
tax_engine.set_federal_info(federal_info)
# With 50 states plus Washington, D.C., there are a lot of
# classes on the other end of this factory.
#
# All of them expect to be interacting with the `tax_engine`
# in an imperative fashion.
TaxEngineSetter::Factory.for(state).apply_state_info(
tax_engine,
state_info
)
computed_taxes = tax_engine.calculate_taxes
payroll.assign_taxes(computed_taxes)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment