-
-
Notifications
You must be signed in to change notification settings - Fork 729
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
3437 supplier name on invoice #3444
Changes from all commits
7c5b430
d97fa60
3aea16e
4a3e5f1
2d0df7f
5ee3dbf
612ea4c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
class InvoiceRenderer | ||
def render_to_string(order) | ||
renderer.render_to_string(args(order)) | ||
end | ||
|
||
def args(order) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this method is not used outside the class, right? in that case, it's better if we move it to private to keep the public API to the minimum. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The OrdersController calls this:
I tried to move the
I think There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know there is a way to move render outside the controller although I never used it but I don't think we need it here. I was just talking about the |
||
{ | ||
pdf: "invoice-#{order.number}.pdf", | ||
template: invoice_template, | ||
formats: [:html], | ||
encoding: "UTF-8", | ||
locals: { :@order => order } | ||
} | ||
end | ||
|
||
private | ||
|
||
def renderer | ||
ApplicationController.new | ||
end | ||
|
||
def invoice_template | ||
if Spree::Config.invoice_style2? | ||
"spree/admin/orders/invoice2" | ||
else | ||
"spree/admin/orders/invoice" | ||
end | ||
end | ||
end | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nice abstraction 👏 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,9 @@ | |
%tr | ||
%td | ||
= render 'spree/shared/line_item_name', line_item: item | ||
%br | ||
%small | ||
%em= raw(item.variant.product.supplier.name) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. any chance we could pass something like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is actually within a loop. The only variable for the template is @order.line_items...each do |item|
#...
end We could put the content of the loop in a separate template, but I don't see the benefit here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see... what triggered my brain's attention was the violation of the Law of Demeter that The only solution that comes to my mind is to encapsulate it in EDIT https://www.youtube.com/watch?v=l9JXH7JPjR4is a fun explanation of the Law of Demeter 😂 (starts at 2:47) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see, a very important point. I always hesitate to modify Spree code though. I'm also wondering if it's the responsibility of a line item to know the supplier name. Two ideas:
If LineItem was our class I would definitely go for the first option, but since it's Spree, I'm not sure. What do you think? |
||
%td{:align => "right"} | ||
= item.quantity | ||
%td{:align => "right"} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,16 @@ | |
|
||
expect(Delayed::Job.last.payload_object.method_name).to eq :start_pdf_job_without_delay | ||
end | ||
|
||
it "creates a PDF invoice" do | ||
order = create(:completed_order_with_fees) | ||
order.bill_address = order.ship_address | ||
order.save! | ||
|
||
service.start_pdf_job_without_delay([order.id]) | ||
|
||
expect(service.invoice_created?(service.id)).to be_truthy | ||
end | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't it make more sense to have such integration-like spec in the controller and stick to unit-level in this spec file? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm testing only the interface of this service here. What makes it integration-like? Do you mean the creation of an order in the database? How would you unit-test the logic of this method? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO creating a job an depending on Delayed Job is what makes this an integration test, other than the order but you know... we're too coupled to AR. To make it unit I would use a test double rather than an actual CombinePDF object and assert that we call Then, I'd move to the controller test the responsibility to check that a background job is enqueued using Let me know if you want to pair on it or if I should provide more details. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point @sauloperez. This test isn't about Delayed Job at all. Your comment made me realise that I can invoke the method without delayed job to test it's actual execution. Have a look at my new commit. That's much clearer. Also big thanks for the Sandy Metz talk about unit testing. Very simple and clear. My conclusion is though that I don't want to introduce doubles in this spec for several reasons:
What do you think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is definitely better now without the explicit call to DelayedJob although due to its design we're still coupled to it (That's why I'm not a big fan of this handle_asynchronously magic it implements).
That talk is old and RSpec's
What you actually mock is the objects the class under test depends on. You never mock the subject of the test as this wouldn't reflect what happens in production. And that is what makes it unit. You put the class in an isolated test harness (like in a chemistry lab) to ensure the unit does what it says.
Of course not, that would be integration. If you need to do so is because the class under test is too coupled to the filesystem (generally speaking here). I always see consider a test as a symptom of the class' design. If it's hard to put it in a test harness is because the design I implemented needs to improve. If it is so while testing in isolation chances are it'll be much harder when I need to refactor or reuse in the codebase. In these cases, what is a real game changer is to inject the dependencies in the constructor. It gives the possibility to replace them and makes coupling stand out.
Sure, not a big deal but I see these 3 seconds in every spec. And this really adds up. Great discussion. I really enjoy talking about these ideas but let's move on. I think this is better now but I'll raise the topic again in the future, be sure of that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I totally agree! We should avoid that in future code.
Thank you! I didn't know that. I definitely want to use that more. (Just not here. ;-) ) The outcome feels good now. 👍 |
||
end | ||
|
||
describe "#invoice_created?" do | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
require 'spec_helper' | ||
|
||
describe InvoiceRenderer do | ||
let(:service) { described_class.new } | ||
|
||
it "creates a PDF invoice with two different templates" do | ||
order = create(:completed_order_with_fees) | ||
order.bill_address = order.ship_address | ||
order.save! | ||
|
||
result = service.render_to_string(order) | ||
expect(result).to match /^%PDF/ | ||
|
||
allow(Spree::Config).to receive(:invoice_style2?).and_return true | ||
|
||
alternative = service.render_to_string(order) | ||
expect(alternative).to match /^%PDF/ | ||
expect(alternative).to_not eq result | ||
end | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO this deserves to be split in two tests, one for each There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Haha, I had a comment there explaining why and then removed it. This is the second time that I removed a comment and then you ask me about it. I should never remove one of my comments again. Here are some reasons:
What do you think? Is it worth it splitting the spec into three and pay with 6 seconds more run time?
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That is what mocking is for. If this is a unit test, it shouldn't depend on anything else. Checking the various related unit units work together is the responsibility of an integration test. I strongly recommend you https://youtu.be/URSWYvyc42M. I'll never explain it as well as Sandy Metz. |
||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class would clearly benefit from moving
order
to an ivar populated in the constructor IMOThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see it as clearly. I see great benefit in declaring all input variables for each method. It makes refactoring easier. It also allow to call the same renderer with different orders. The BulkInvoiceService should probably do that.