Reputation: 378
I am torn between the best way to implement this service:
Currently I have this:
class ReissueInvoices
def initialize(registration, invoice_gateway, invoice_generator)
@registration = registration
@invoice_gateway = invoice_gateway
@invoice_generator = invoice_generator
end
def call
void_current_invoices
regenerate_invoices
create_third_party_invoices
end
private
attr_reader :registration, :invoice_gateway, :invoice_generator
def void_current_invoices
registration.invoices.each do |invoice|
unless invoice.paid?
invoice_gateway.void_invoice(invoice)
end
end
end
def regenerate_invoices
invoice_generator.call(registration)
end
def create_third_party_invoices
invoice_gateway.create_invoices(registration)
end
end
and I call this (normally from my controller) like this:
ReissueInvoices.new(@registration, InvoiceGateway.new, InvoiceGenerator.new).call
I obviously have an InvoiceGateway, InvoiceRegistration class and pass these in as a dependency to my ReissueInvoices class.
Is this the best way to do things? Is this correctly implementing dependency injection? or should I change my ReissueInvoices class to something like this removing the parameters from the initialize method and adding private methods to create and access the invoice_generator and invoice_gateway objects:
class ReissueInvoices
def initialize(registration)
@registration = registration
end
def call
void_current_invoices
regenerate_invoices
create_third_party_invoices
end
private
attr_reader :registration
def invoice_gateway
@invoice_gateway ||= InvoiceGateway.new
end
def invoice_generator
@invoice_generator ||= InvoiceGenerator.new
end
.....
and call it like so
ReissueInvoices.new(@registration).call
Lastly, what do you guys think of defining default parameters like this in the initializer:
def initialize(registration, invoice_gateway=InvoiceGateway.new, invoice_generator=InvoiceGenerator.new)
@registration = registration
@invoice_gateway = invoice_gateway
@invoice_generator = invoice_generator
end
Good or bad?
Thanks,
Matt
Upvotes: 0
Views: 71
Reputation: 31726
Why does your gateway not need something like credentials? I'm guessing it does, and the gateway's credentials are just hard-coded. Really, that should be initialized somewhere and then passed in (because your ReissueInvoices
class shouldn't have to deal with how to setup a gateway, presumably there is only one gateway and you should use that same object everywhere).
I wouldn't bother passing in the invoice generator unless it's expensive or you need control over what it returns for a test or there's multiple ways to generate the thing, and you can't reasonably know which is right from here. This is just because it's a thing that does some calculation and returns a value. Nice and simple, barring considerations like the ones I mentioned.
Also, I'd probably change it to be something like this:
module ReissueInvoices
def self.call(registration, invoice_gateway)
registration.invoices.each do |invoice|
invoice_gateway.void_invoice(invoice) unless invoice.paid?
end
InvoiceGenerator.call(registration)
invoice_gateway.create_invoices(registration)
end
end
The thought process went approximately like this:
#call
InvoiceGenerator
? It takes no arguments, so it shouldn't have state, so no need to instantiate. I assume it only has the one method, since the method is named #call
, so just make it a singleton method and if there's some value to instantiating, the singleton method can make this decision.ReissueInvoices#call
, what is left? It's just wiring to support the objectness of this action (#initialize
and accessors). Instead, make it a class method named call
. Then, these become local vars.Upvotes: 1