This removes an N+1 with taggings but doesn't solve the one with tags.
Using `includes(taggings: :base_tags)` based on
47da5036de/lib/acts_as_taggable_on/taggable.rb (L83-L84)
wasn't enough to solve it and I got to stop here. This is scope-creeping
too much.
We get from an initial INNER JOIN with variants and products to fetch
the variant overrides + N queries like:
```sql
SELECT "spree_variants".* FROM "spree_variants" WHERE
"spree_variants"."deleted_at" IS NULL AND "spree_variants"."id" = $1
LIMIT 1 [["id", 1545]]
SELECT "spree_products".* FROM "spree_products" WHERE
"spree_products"."id" = $1 LIMIT 1 [["id", 604]]
```
to the same initial INNER JOIN + just 2 queries like:
```sql
SELECT "spree_variants".* FROM "spree_variants" WHERE
"spree_variants"."deleted_at" IS NULL AND "spree_variants"."id" IN
(1551, 1554)
SELECT "spree_products".* FROM "spree_products" WHERE
"spree_products"."deleted_at" IS NULL AND "spree_products"."id" IN (606,
607)
```
In the line below we filter them out in Ruby so it's a waste of
resources. The fundamental difference is that `#includes` and
`#references` results in LEFT JOINs, whereas `#joins` results in INNER
JOIN, and because there's a default scope on `deleted_at IS NULL`, these
are not included in the result set.
This however, requires us to move away from the current algorithm but
unfortunately we can't refactor it completely yet.
Before:
```sql
SELECT *
FROM "variant_overrides"
LEFT OUTER
JOIN "spree_variants"
ON "spree_variants"."id" = "variant_overrides"."variant_id"
AND "spree_variants"."deleted_at" IS NULL
LEFT OUTER
JOIN "spree_products"
ON "spree_products"."id" = "spree_variants"."product_id"
AND "spree_products"."deleted_at" IS NULL
WHERE "variant_overrides"."permission_revoked_at" IS NULL
AND "variant_overrides"."hub_id" IN (
SELECT "enterprises"."id"
FROM "enterprises"
INNER
JOIN "enterprise_roles"
ON "enterprise_roles"."enterprise_id" = "enterprises"."id"
WHERE (enterprise_roles.user_id = ?)
AND (sells != 'none')
ORDER BY name)
```
After:
```sql
SELECT "variant_overrides".*
FROM "variant_overrides"
INNER
JOIN "spree_variants"
ON "spree_variants"."id" = "variant_overrides"."variant_id"
AND "spree_variants"."deleted_at" IS NULL
INNER
JOIN "spree_products"
ON "spree_products"."id" = "spree_variants"."product_id"
AND "spree_products"."deleted_at" IS NULL
WHERE "variant_overrides"."permission_revoked_at" IS NULL
AND "variant_overrides"."hub_id" IN (
SELECT "enterprises"."id"
FROM "enterprises"
INNER
JOIN "enterprise_roles"
ON "enterprise_roles"."enterprise_id" = "enterprises"."id"
WHERE (enterprise_roles.user_id = ?)
AND (sells != 'none')
ORDER BY name)
```
This is covered in the test suite by
spec/controllers/admin/variant_overrides_controller_spec.rb:72. It keeps
passing so we're good to go.
#update_column(s) skips callbacks (which is useful), but it doesn't change the updated_at field on the record by default (which we should be doing in these cases).
This change is made in Spree 2.2 here: b367c629ce
Fixes#6435 i.e. If the customer paid for their order by Stripe/Paypal then the Enterprise needs to know that the order was cancelled in order to arrange a refund. Refunds are not automatically processed when an order is cancelled.
This will send a very basic email to the shop, it only includes a link to view the cancelled order in the admin area initially.
I created a CustomerOrderCancellation object here because orders can be cancelled in two ways (1) by the customer, so an email should be sent to the shop. (2) by the shop, so an email doesn't need to be sent. However the code for cancelling order happens in Order#cancel via the state machine. Rather than passing some sort of parameter into #cancel to indicate whether it is a customer or shop cancelled order it might be clearer to have a CustomerOrderCancellation object, there could be other differences between customer or shop cancelled orders in future maybe.