Restful Shopping Cart Example?

Hi --

I'm still trying to make restful desing click in my head. Yesterday I
asked about <a href="http://www.ruby-forum.com/topic/101827">good
examples apps to learn from</a>, and there don't appear to be very many.
And most of the examples that do exist seem to be fairly strait forward
mappings to rest concepts... posts and comments and such.

So I'd like to see some more examples, and in particular I think it
would be fun to turn the "Depot" store app from AWDR into a more restful
example. For me it would be a nice example because fairly simple and
clean and it's not quite clear to me how a restful desing would be
added.

Two sets of questsion to get me started.

1. Does this seems like a useful thing to do? Would anyone learning
restful design out there like to see this, and for people who already
get it is the Depot app a good candidate?

I think it could be quite worthwhile.

2. And second does anyone have design tips that I should use to get
started? So far this is what I've done.

- used the restful_authentication plugin to generate an authentication
system.
- used scaffold_resource to generate resources to model the store:

  - products
  - carts
  - cart_items
  - orders
  - order_items

I'd recommend trying a slightly different approach. (I don't use
scaffolding at all, but that's not my focus here.) In my book, I
develop a store application, and one of the things I realized early on
was that I didn't want or need a shopping cart model. The shopping
cart was really a virtual thing; it was essentially a view of a
collection of orders.

I've thought about what I'd do if I were to rewrite that very
application to use the new REST support. I would still not have a
cart model. But I would have a cart controller, and I would do:

  map.resources :cart

(or some nested variant thereof). In my original application, I had
actions like customer#view_cart. That could be turned into cart#show.

It's a good example of a case where the whole controller/model stack
is probably too much. The idea of a shopping cart resource need not
be wired to a shopping cart model. You can still leverage the full
range of named routes and abstraction that the REST techniques give
you.

Anyway -- it's something to think about.

David

Hey,

comments below:

2. And second does anyone have design tips that I should use to get
started? So far this is what I've done.

- used the restful_authentication plugin to generate an
authentication
system.
- used scaffold_resource to generate resources to model the store:

  - products
  - carts
  - cart_items
  - orders
  - order_items

I'd recommend trying a slightly different approach. (I don't use
scaffolding at all, but that's not my focus here.) In my book, I
develop a store application, and one of the things I realized early on
was that I didn't want or need a shopping cart model. The shopping
cart was really a virtual thing; it was essentially a view of a
collection of orders.

I've thought about what I'd do if I were to rewrite that very
application to use the new REST support. I would still not have a
cart model. But I would have a cart controller, and I would do:

  map.resources :cart

This brings up an interesting point: when to use map.resource vs
map.resources

Unless you're planning some sort of interface to view *all* carts,
you probably want a singleton resource:

map.resource :cart

Then adding and removing cart items would be the responsibility of
the cart_items controller:

map.resource :cart do |cart|
   cart.resources :cart_items
end

And with that I wouldn't bother having a cart_items_controller#index,
I'd just use cart_controller#show to present the listing.

Just another thing to think about.

Trevor

Hi --

Hi --

> I don't think I'd have cart_items, though; I'd just have orders. The
> cart would be implemented as the aggregate of the orders. So an order
> would not have any need to know that it was in a cart, and the cart
> would only exist as a particular way of representing the orders.

I was wondering about this approach earlier.

There does seem to be lots of duplication between carts, cart_items and
orders, order_items, it would be nice to get rid of that. I've also
found and downloaded your r4rmusic example... nice and short... I like
that!

To make this work the one trick is that I'll need some way to mark one
order as "in cart". I guess I can do this by having a new value for the
status field, and also by storing that order id in the current session.

I think I can even present this order to the user as the shopping cart
by creating a new singleton root and controller named cart:

  map.resources :cart

Do you mean map.resource? (See Trevor's earlier comment on my earlier post.)

That singleton will actually be backed by the current "in cart" order.
Does that all seem to be right?

If an order has a status (pending, confirmed, paid, cancelled, maybe
others), then you could just have the cart be all of the pending
orders of a particular user, or however you wanted to define it. That
way, the order objects don't need to have any knowledge of a cart.
"Shopping cart" would just be a colorful word with which to explain to
the customer what they were looking at.

David

Hi --

>> map.resources :cart
>
> Do you mean map.resource? (See Trevor's earlier comment on my earlier
> post.)

Yes, that was a typo on my part.

I've not got a first try implementation up for people to look and and
more important improve. You can download it here, I'll put it in svn
later:

http://www.hogbaysoftware.com/files/restfulstore.zip

It uses an SQLite database. Once you migrate you should be able to go to
/store, add items to you cart, and checkout. To access the admin
sections the username is 'admin' and the password is 'password'. Please
look through the code and help me get this right so that it can be a
good example for others. I'm sure there are many ruby and railsisms that
I've missed, and maybe bigger design issues as well. Also I've added a
number of "HELP" comments throughout the code in places where even I
could tell i was probably going down the wrong path.

Here's a quick overview of the apps structure:

1. The model objects stored in the database are user, product, order,
order_item.

2. The apps routes are:

  map.resources :users, :sessions

  map.resource :store do |store|
    store.resources :products
    store.resource :cart, :member => { :checkout => :post, :submit =>
:post, :completed => :get, :error => :get } do |cart|
      cart.resources :cart_items
    end
    store.resources :orders do |order|
      order.resources :order_items
    end
  end

3. And here is a quick overview of the important paths and their intent:

/store - list of products along with contents of cart
/store/cart - singleton resources of the current (per session) pending
order
/store/cart/cart_items - supports create and destroy actions for adding
items to cart
/store/products - admin section for products
/store/orders - admin section for orders

I'm trying to use the basic design as discussed above. The users cart is
really just a special order who's status is pending. I'm using a
singlton cart controller to represent that pending order. I've also
given that cart controller cart_items which are targeted by actions that
add or remove items from the cart.

4. Last the view part of the app is still a bit hacky. Probably many of
the admin views can be deleted, and I'd also like to make manipulating
the cart a bit more ajaxy, but I was having trouble making that work. So
for now it's all quite basic.

Thanks for any comment and suggestions.

I haven't downloaded the app yet -- just responding to what's above --
but just for fun, here's my minimalist version. It may be too
minimalist... but it might be interesting to identify things you can
do with yours that you can't do with mine.

This is all untested... just hypothetical code and routes.

class Order
  belongs_to :user
  belongs_to :product
end

class User
  has_many :orders
end

class Product
  has_many :orders
  has_many :buyers, :through => :orders, :class_name => "User",
:source => "user"
end

map.resources :products

map.resources :users do |u|
  u.resources :orders
end

Show all available products:

  GET /products

Show a user all their orders ("cart"):

  GET /users/1/orders

Add a product to cart:

  POST /users/1/orders

Remove a product from cart:

  DELETE /users/1/orders/1

etc.

David

Hi --

David,

Thanks again.

Here are a few things that I think are different, though I might also
just not be understanding some parts.

1. In my model 'user' is really just for administrators. I don't want
visitors to the site to have to create an account before making an
order. For big sites, like amazon, I think that requirement can make
sense, but for a small site like I'm imagining I don't want the user to
have to decide on a user name and password in the middle of the order
process. That does mean that a user can't use the site to see all of
there orders, but instead I can add an interface where they enter their
email and are sent an email listing all of there orders.

2. You've removed the order_items model, which I think is important to
keep if we want visitors to be able to buy more then one product per
checkout. With the current design I'm not sure how your "cart" can hold
more then one item...

...Or maybe in your model a visitor can have multiple orders pending and
all pending orders are considered to be in the "cart". But the problem
with this approach is that I don't think you have a good place to store
checkout info such as name, address, email, etc. I think you would have
to duplicate that info across each order per checkout?

No, I'm assuming it would be stored in the user object. I haven't
drafted the database tables, but it would be in there (or a separate
contacts table, or whatever).

In my model there is a single pending Order per visitor which is the
"cart" and it holds multiple order items, each of which is associated
with a specific product and also has a quantity. In my mind the addition
of the order_items class makes the model work a bit better since it's
strait forward to order multiple copies of the same product and there is
a single place to store checkout info.

The Order model could have a quantity field :slight_smile: We're using 'order'
differently, though. I'm thinking of an order as per user/per
product, and the "cart" as a view of the user's pending orders.

3. In your routes I'm not sure where the admin interface for editing
products and viewing orders is. I guess the interface for editing and
buying products could both take place at GET /products, but in my model
I've instead created the singleton GET /store route/controller that list
the products along with BUY buttons and I have a separate GET
/store/products route that display the admin interface for editing the
existing product collection.

I think distinguishing between /store and /store/products, with one
being public and one being admin, is a bit obscure. Could you just
have /store/products routes (or even just /products?) and just control
access to the operations that change them?

David

We need someone to create a Ruby shopping cart for us or maybe slice our
psd file into Substruct. In a perfect world, creating one for us is the
key.

We also need this to be syndicated into Atom or RSS 2.0 or both such as
the G2 Image Gallery Application. We also are wanting to import an
existing WordPress MySQL Database into it. More like a Ruby
blog/shopping cart that is syndicated with Atom & RSS 2.0. We also need
permalinks or URL friendly concepts, however you want to say it now
days.

We also have an existing G2 MySql database that we want in Ruby too.

Can anyone help me? Thanks, sb :slight_smile:

~~@~~@~~

oren wrote:

The Depot application is the perfect example of a good sample REST
application in my opinion. I really wish that the Pragmatic guys would
release a AWDR v 3 with a fully RESTified Depot application. Just wanted
to put it out there that I agree that this would be a good "real" rest
application.

I put a little thought into how I would do it and I think the only two
database-backed resources would need to be user, order, and product. So
those three would have their controllers and then all other controllers
would be based on those resources and would use the session.

Now I am tempted to try to whip together an exact copy of the Depot
application using all the new Rails 2 principles...

(Looking through the thread, it appears David beat me to the user /
order / product resource idea but I will post this anyways)