-
-
Notifications
You must be signed in to change notification settings - Fork 21
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
Marketplace: Differentiate between Checkout and Order #1057
Conversation
end | ||
router.resources :checkouts, only: [:show, :create] | ||
end | ||
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.
I moved these to furniture/marketplace/routes.rb
which I think will make it easier to find routes because you can use a fuzzy-finder and type marketplace routes
and get the routes; instead of having to remember that they live in furniture/marketplace.rb
def complete(stripe_session_id:) | ||
update!(status: :paid, stripe_session_id: stripe_session_id) | ||
cart.update!(status: :checked_out) | ||
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.
I figured shoveling this in here is probably better than leaving it in the controller; but maybe I'm wrong.
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.
I think it's okay to leave the complete
method here for now. It's just updating the cart record right? It's not doing anything in request/response flows -- which is what the checkouts_controller is for right?
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.
Yep, it's doing the data transformations; so the controller just happens to know that showing a checkout when stripe gives it an id it also completes the checkout.
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.
I like to leave conversations open, rather than "resolving" them. Resolving feels like it dismisses the conversation! You had good questions! Other people may have them too!
else | ||
Shopper.find_or_create_by(person: current_person) | ||
end | ||
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.
I pulled these into marketplace/controller.rb
since it was being used in many places.
app/furniture/marketplace/cart.rb
Outdated
@@ -12,6 +12,7 @@ class Cart < ApplicationRecord | |||
has_many :cart_products, dependent: :destroy, inverse_of: :cart | |||
has_many :products, through: :cart_products, inverse_of: :carts | |||
has_one :checkout, inverse_of: :cart | |||
has_one :order, inverse_of: :cart, class_name: "Order" |
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.
I don't think the class_name
is actually useful here 🤦
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.
what happens if you remove it? what breaks?
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.
I don't think anything will, I just added it sillilly.
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.
Remove it! 🪄 ✨ 🔥 it! 😄
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.
IT DEAD
expect(cart.reload).to be_checked_out | ||
expect(checkout.reload).to be_paid | ||
expect(response).to redirect_to(checkout.becomes(Marketplace::Order).location) |
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.
The changes in this PR seem like a large enough refactor that I assumed there'd be more unit tests added/modified to provide test coverage for the new Order model, corresponding Controllers, and orders/show, orders/_order. But maybe it's just the over tester in me.
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.
Can you clarify where you're seeing risk here? At this point, we don't have significant deal-flow; so flexibility in mutating the design seems more valuable than enshrining the design via tests.
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.
that's a good point. 👍
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.
I think there are definitely risks here, I just don't know which ones are worth writing tests for yet! If you have ideas about which particular cases you'd like tested (either cross-request or individual request) I would be happy to implement them!
policy_scope(marketplace.orders).find(params[:id]) | ||
end | ||
end | ||
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.
I am trying to visualize the way all these data models interact as the user goes thru the shopping flow is. Is it like this?
- A customer adds items to their cart == a
Cart
ofpre_checkout
status containscart_products
. - The customer goes to the checkout page and is filling out credit card info to complete their purchase == a
Cart
ofpre_checkout
status is going thru the stripe checkout flow thus creating aCheckout
record. - if the checkout flow via stripe is successful, the
Cart
changes tocheck_out
status, and anOrder
is created from thecart_products
.
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.
So, let's decompose the datastructure a bit here:
- at the database layer, a
Checkout
and anOrder
are equivalent; they both store their attributes in the same table. - At the application layer, a
Checkout
and anOrder
have different routes, controllers, policies, and views. - At present, whether something is represented as a
Checkout
or anOrder
depends on what url is being used to retrieve the data. If the request path includesmarketplace/*/checkouts/*
then the data stored in the database will be treated as aCheckout
. If it'smarketplace/*/orders
then the data stored in the database will be treated as anOrder
.
So, an Order
is not 'created' in the sense that it's not actually creating any records in the database table; rather we leverage "polymorphism" (which can be loosely defined as 'the ability for one data structure to be treated as another data structure') so that we have more Domain objects for different use cases; while retaining a more normalized database.
It's entirely possible that we do want an Order
to be a distinct database table from a Checkout
, or we may not want a Checkout
to not be a database-backed model at all!
So, to reframe:
Shopper
addsProduct
s to theirCart
, creating aCartProduct
record which keeps track of things like quantity. (note: I think aCart
is also a kind ofOrder
orCheckout
, which I think @anaulin may agree with)Shopper
creates aCheckout
, which directs them to Stripe to collect payment information.Shopper
completesCheckout
, which directs them back from Stripe to Convene, and updates theCheckout
object to indicate it had been succesfully checked-out.Shopper
is redirected to show theOrder
, which represents theCart
toShopper
s in a read-only manner.
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.
wow this is great! once Marketplace is finished/finalized/stable we could make a seperate .MD doc readme with the above info and maybe a diagram ❤️
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.
I think it could be dropped directling into app/furniture/marketplace/README.md
in it's own PR :D
end | ||
end | ||
end | ||
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.
why does Marketplace
get it's own routes.rb
file? Why not just put it in
https://github.com/zinc-collective/convene/blob/main/config/routes.rb
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.
If we put in in config/routes.rb
then every developer maintaining a piece of Furniture
would need to modify routes.rb
in order to adjust the routes their Furniture
users. Instead, we hook in to the Furniture
class in config/routes.rb
and tell it to iterate through every piece of furniture, and append the routes defined within the Furniture
itself.
This allows Furniture
pieces to "own" the code related to their furniture within the app/furniture/furniture_name
folder; which reduces the amount of surface area a Furniture maintainer needs to cover in order to make significant changes to their Furniture
's behavior.
TL/DR: Encapsulation! Every piece of Furniture owns it's entire implementation, from Routes to Models to Controllers to Policies to Views; within the app/furniture/furniture_name
directory.
Where `Checkout` is useful for handling a Checkout flow for the Payment Processor, it doesn't make a ton of sense from a Buyer perspective once the Cart has been Checked out. I've sprouted an `Order` model, with corresponding Controllers, and moved the `checkouts/show` and `checkouts/_checkout` views to `orders/show` and `orders/_order`.
d6b9eb0
to
6c4e3f1
Compare
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.
I don't see the need to have both a Checkout
and an Order
class. It seems a Checkout
is simply an Order
that is in the "initiated but not completed" state.
More broadly, personally I find that this style of code-reuse-via-inheritance makes codebases harder to understand and maintain. (And in general I am strongly pro composition-over-inheritance.)
All that said, I'm not going to stand in your way if you want to take things in this direction.
I've found in Rails codebases in particular, they become easier to maintain in a highly-rigorous domain-driven approach; which includes not having extraneous domain objects. Right now, I don't see My hope is that:
Does this sound like something you can be on board with? |
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.
My experience is that when folks insist on having a "highly-rigorous domain-driven approach" we end up with oddly convoluted structures, because of trying to shoe-horn the inevitable oddnesses of real-world needs and interactions into something that is "perfectly abstracted and modeled" instead of, say, using the occasional command pattern.
Not to talk about how highly debatable this approach can be: for example, in my opinion, "Checkout" makes no sense as a domain model for a marketplace, because it in fact represents a verb (the action of checking out) and not an noun, and thus has no business being a model.
All that said, I'm happy for you check in this or do whatever you feel is the best way to move this forward. I'm sure we'll shake out the kinks as we go along regardless.
I agree that we'll find the kinks and shake them out. I also agree that a Checkout is an absurd model; and I want it to be very tiny or not exist at all if we can get there. |
#831
Where
Checkout
is useful for handling a Checkout flow for the Payment Processor, it doesn't make a ton of sense from a Buyer perspective once the Cart has been Checked out.I've sprouted an
Order
model, with corresponding Controllers, and moved thecheckouts/show
andcheckouts/_checkout
views toorders/show
andorders/_order
.