Practical 4
0. Finish Practical Sheet 3. Run the tests (rake test
) to make
sure everything works fine.
Today you will create some new tables for various types of
objects, and connect them to other tables in the database.
1. More precisely, we need two additional tables in the data base, one for shopping carts, and one for items in shopping carts, so-called line items.
The
purpose of a shopping cart object is to contain the products a buyer chooses for purchase. This concept is best modelled as a
relationship
between products and carts. Such a relationship sits in its own table,
which here will be called line_items
.
The cart object itself needs no components, and its scaffolding can be generated with a simple
rails generate scaffold cart
command. Then run
rake db:migrate
to actually make the table in the database.
2. Sessions. How does this work, a cart per user, with no user actually
logged in? Rails maintains state between requests through the use of
cookies, and one thing contained in the cookie is called the
session. This is a ruby
hash session
that allows the application to
recognise a returning user, and can furthermore be used to store
session related information, such as this user’s shopping cart.
3. Concerns. A concern is a piece of code that
can be shared between different controllers. It sits in
a file in the dedicated folder app/controllers/concerns
.
Here it will be convenient to declare a method set_cart()
(which either finds this user’s cart, or creates a new one)
as a concern. This is done by putting the following code
into a file app/controllers/concerns/current_cart.rb
.
This code contains a couple of new ruby
features, like module
,
which we might ignore for the moment.
Marking the current_cart
method as private
restricts access to it. The
rescue
clause is executed only in case
session[:cart_id]
is currently undefined
(which will cause an
ActiveRecord::RecordNotFound
error to be raised).
3a. It might be a good idea to commit this incremental development
step to the git
repository. What commands would you have to type at
the command line to achieve this?
4. Relationships.
The purpose of a line_item
is to connect a product to a cart.
In the language of relational databases,
the line_item
table represents a many-to-many relationship between products and carts:
a cart can contain many products, a product can be contained in many carts.
In this particular form, the many-to-many relationship is the result of 1. a
one-to-many relationship between line_items
and products
: each line item refers to one product
and a product can be referred to by many line items;
and 2. a
one-to-many relationship between line_items
and carts
: …
The scaffolding command
rails generate scaffold line_item product:references cart:references
will create the necessary files to set this up, including a migration. Run
rake db:migrate
to create the line_times
table in the database.
Defining the type of both the product
and the cart
component of a
line_item
as references
has various effects. In the database,
it will make columns product_id
and cart_id
of type integer
.
In the LineItem
class (which file?) it will add the declarations
with the following effect. In addition to the attributes .product_id
and .cart_id
(which are supported by database columns), any line item
now has two additional attributes .product
and .cart
which can be
used to access the cart of a line item li
simply as li.cart
(rather than Cart.find(li.cart_id)
).
5. Relationships work in two ways, backwards and forwards. It makes as
much sense to talk about all the line items in a cart, as it does
for the cart of a line item (but note the use of plural vs. singular:
we are talking about a one-to-many relationship here). This opposite
point of view is registered with the Cart
model (which file?) by
adding the following line to its definition:
Now, we can access the line items of a cart cart
as
cart.line_items
, and determine their number
as cart.line_items.count
if so desired.
(The dependent: :destroy
part will have the effect that when
a cart is deleted from the database, all line items associated with
the cart will also be deleted. Makes sense, doesn’t it.)
6. Add a similar line (without dependent: :destroy
) to
the Product
model (which file?). This will allow us to access all
the line items that reference a product product
as
product.line_items
.
7.
Here, rather than silently deleting all line items referring to a product when
that product is deleted, we want to ensure that a product is not
removed from the database, as long as there are carts containing it to
be processed, that is, as long as it is still referenced by a line
item. Add the following line to the top of the Product
class
definition:
and the following text to its end:
What does this code accomplish? We can add a test to products_controller_test.rb
that ensures that a product in a cart cannot be deleted:
Also change the line
product: one
to
in the line_items
fixture file (where?), so that
product two
is contained in both line items
when testing. Do a
rake test
to see (or not) the effect.
7a. If all works fine, commit the changes to your local git
repository, and push them to the github
cloud.
git add .
git commit -m "added cart and line item models."
git push
8. Buttons. Buttons (in Rails) by default send a HTTP POST
request
(as opposed to the HTTP GET
request that is sent by a link).
The create
action in the line item controller
(which file?) expects a HTTP POST
request, doesn’t it?
That’s why a button in the user interface is a perfect match for
the wish to create a line item (i.e., a buyer’s desire to
purchase a product).
The purchase decision in the form of a clickable button
is implemented as
in the right place (inside the <div>
with class="price"
) on the catalog page (store/index.html.erb
).
9. The button looks better with some additional styling:
to be inserted just after the CSS rule for .price
near the end of the stylesheet store.scss
.
10.
Next, we need to inform the line items controller (which file?)
how to actually create a new line item, taking into account the product
whose button has been pressed, and the cart to put it into.
For the cart, we have earlier prepared the CurrentCart
concern.
In order to use it now, add the following two lines
to the top of the definition of the line items controller.
This includes the CurrentCart
module and arranges that
the set_cart()
method is invoked before every create()
action.
The effect of this will be that a suitable cart is assigned to the instance
variable @cart
.
11. Then, to actually build the line item, replace the first line of
the create()
action by:
and in the format.html
part, replace redirect_to @line_item
by
What effect will that have?
12. Now, since the controller method has been modified, we
need to update the corresponding functional test.
Run the tests now and see one failing.
In that failing test ("should create line item"
, which file?),
replace the post
line by
and the assert_redirected_to
line by
Then run the tests again. And test the Add to Cart
button
in your application …
13.
The effect of clicking the button could be a bit more informative.
Firstly, the cart page could list the items currently in the cart.
For this, replace the contents of carts/show.html.erb
with
And secondly, the notice that a line item was successfully created
could look less out of place. Add the following to app/assets/stylesheets/application.scss
, before the .content { }
bracket:
And then: go shopping in your own online store!
13a. If all works fine, commit the changes to your local git
repository, and push them to the github
cloud.
Surely, there are many questions left open. Formulate one
as a comment below, as evidence that you attended this practical.