Practical 5
1.
Finish Practical Sheet 4. Run the tests (rake test
) to make
sure everything works as it should.
2. Migrations. Add a few more books to your shopping cart, until it contains several copies of at least one book. At that point the shopping cart starts looking kind of stupid, listing the same product several times. Can those computers not keep track of the number of times a product is chosen? They can. For this, you just have to add a column to the line items table in the database.
The structure of the database can be changed by
migrations, and migrations can be generated by rails. To add a
quantity
column of type integer
to the line_items
table, type
the command:
rails generate migration add_quantity_to_line_items quantity:integer
then find the generated file in db/migrate
.
Enter the name of this file in the comment box below,
as evidence that you have attended this practical.
3. Add the argument , default: 1
to the
add_column
function call within the change()
method in this file (so that a new line item
starts off with quantity
set to 1, and existing line items get a value 1
for their new column quantity
). Then run the migration:
rake db:migrate
4. In order to use the new quantity
field, the
logic behind the pages needs to be changed. Logic resides in the controller.
Line items are controlled by which controller in which file?
Replace the line
@line_item = @cart.line_items.build(...)
in the create
method of this controller
by
5. While Rails does a lot of things automatically, it cannot know
how you intend to add a product to a cart. The method add_product
needs to be defined explicitly, in the cart model (which file?).
Based on the given product id, this method needs to find out
whether this product is already referred to by one of the cart’s line items
(this is the list line_items
which exists as a consequence of the
has_many :line_items
declaration) and in that case increment the quantity
, or create a new line item for the product and add it to the cart’s list of line items. In either
case, the line item needs to be returned to the caller:
6. Add <%= item.quantity %> ×
to the product
title in the page template that displays the shopping cart (which
file?). Now, add a few more products to your cart and watch the
quantities increase.
6a. 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 quantity column to line items table"
git push
7. Unfortunately, this change in the database structure does not automatically get rid of the older repeated entries. What’s needed is another migration to clean up the entire database! The command
rails generate migration combine_items_in_cart
creates the migration (where?), but doesn’t specify what to do.
8. In order to adjust all line items to the new setup, one
needs to arrange a loop over all carts (Cart.all.each do |cart|
... end
). Inside the loop, it has to be checked whether a
product appears in several line items, and if so, these instances need
to be replaced by a single line item, with the correct quantity.
Rename the method change
to up
in the migration file.
Then
insert the above loop into the up()
method, and insert the following code into the loop:
Then run the migration: rake db:migrate
, and reload your shopping cart.
Does it look any different?
9. Migrations are normally designed so that they can be reversed
if necessary. This can be specified as a single change()
method
which can be applied forwards and backwards (study the existing
migrations in the db/migrate
folder and try to imagine how each of
the operations in there can be undone). Or it is specified by a pair
of methods, one called up()
and one called down()
. Here we already have
an up()
method, it’s counterpart however is missing.
10. Design a down()
method that undoes the
latest change: it needs to replace each line item with a quantity of
more than 1 by a corresponding sequence of line items with quantity
- (Hint: use
1
,destroy
,each
,end
,item
,product_id
,times
, to fill the gaps.)
Insert the code into its place in the migration.
Then run rake db:rollback
and reload the cart. Does it look different?
The status of all the migrations can be listed with the command
rake db:migrate:status
Now run rake db:migrate
again to reactivate the quantity counting for shopping carts.
10a.
The change in output format makes a corresponding change in the tests
necessary; in the "should create line_item"
test, replace the
expected title of the book by
Then, if all works fine, commit the changes to your local git
repository, and push them to the github
cloud.
11. Error Handling. Under the hood, our application receives its commands in the form
of HTTP requests. These requests can be typed directly into the URL field of the browser, and might not have the expected format. Try to open the page
http://localhost:3000/carts/2019, that is a cart with 2019
as its id
:
the application raises a RecordNotFound
error
and exhibits its inner workings.
12. In order to handle this error, we can add a rescue_from
clause to the controller and specify what to do in a new method invalid_cart()
. Add the line
to the top of the carts controller (which file?), and the method
to the private
section of the controller. This method will now be executed
when the controller tries to recover from a RecordNotFound
error.
It will add a line to the application’s log file log/development.log
,
and it prepares a notice
which the user will see as part of
the next page that is shown. Try to open the page
http://localhost:3000/carts/2019 again, and watch the difference.
Also check the log file for that message.
13. Non-existing carts are not the biggest problem. Criminals could
try and get access to other peoples existing carts. In order to
prevent this, let’s remove cart_id
from the list of permitted parameters
in the line items controller: replace the one line in line_items_params
by
Match this change in the line items controller test by changing
a line in the "should update lile item"
test to
Now run the tests as
rake test:controllers
and find a line about Unpermitted parameters
in the log/test.log
file.
Note that this did not make the test fail.
Remove the reference to cart_id
in the "should update line item"
test
to fix this.
13a. If all works fine, commit the changes to your local git
repository, and push them to the github
cloud.
14. Deleting Objects. There needs to be a way to empty the cart. One possible solution is to simply delete the entire cart from the database. Add this button
to the cart display page. (Which file? Where in the file?) DON’T PUSH THE BUTTON yet.
15. Clicking this button will eventually invoke the
destroy
action of the cart controller. (Which file? Where in the file?) Modify this method as follows:
Replace the line @cart.destroy
by
And replace the
format.html
block with
Now try and empty your cart, and then go back and shop for more.
16. The corresponding test needs to be updated (why?). Add the lines
to the top of the assert_difference
block of the "should
destroy cart"
test. Also replace carts_path
by store_path
in the
assert_redirected_to
command. Then run the tests and make sure
it all works well.
17. Clean up. Remove the message that is automatically generated whenever a new line item is added to the cart. (Where?)
18. Replace the line items list in a cart page by a table as follows:
19. This new table depends on two methods, both called
total_price
, one for a line item and one for a cart. Add a method
total_price
to the LineItem
model that computes the total price as
product of the product.price
and the quantity
. Also add the
following method to the Cart
model to compute the total price as the
sum of the prices of the line items.
20. Finally, we add a bit of style to the carts.scss
stylesheet:
Which further improvements should be made? (Use the comment box below to make some suggestions.)
20a. If all works fine, commit the changes to your local git
repository, and push them to the github
cloud.