Task
The task for the second TCP (2019) was to create an online webshop for the F-guild. Below one can find a detailed description of the task.
Rails
Create a database table
store_productinweb/db/migrate. Each product should have the following fields:name,price,image_urlandin_stock. Remember to assign each field with an appropriate type, specify whether or not it should be mandatory and or if it should have a default value. The price should be saved in Swedish öre. Use the code below and add the missing code.# web/db/migrate/YYYYMMDDHHMMSS_create_store_products.rb class CreateStoreProducts < ActiveRecord::Migration[5.0] def change create_table :store_products do |t| # TODO. Add the four fields here. t.timestamps null: false end end end
Create the corresponding
modelfor Rails inweb/app/models. Think of the validations, e.g. that a price should not be able to be negative. Also, implement ato_smethod in themodel.Migrate the database using
rails db:migrateand create a few test products using the Rails console. This console in entered by typingrails c. Hint: By typingStoreProductin the Rails console you should be able too see all of its fields. This is a good way to test that everything works as expected.Add an admin route to
web/config/routes.rbby adding a few lines in the “User-related routes” section. The path to the products should be/produkter. You have successfully set up the route if you get anuninitialized constanterror when going totcp://localhost:3000/admin/produkter.Implement an admin
controller. Copy and paste the following script and fill in the missing code:# web/app/controllers/admin/store_products_controller.rb class Admin::StoreProductsController < Admin::BaseController load_permissions_and_authorize_resource def new # TODO end def index # TODO. This method should return a grid initialization. end def edit @store_product = StoreProduct.find(params[:id]) end def create # TODO. First, make a new StoreProduct object and then try to save it. # Use `redirect_to` with an appropriate path and `notice` saying something # relevant depending on if it was saved successfully or not. end def update # TODO. The StoreProduct that is being updated should be named @store_product. end def destroy @store_product = StoreProduct.find(params[:id]) if @store_product.destroy redirect_to admin_store_products_path, notice: alert_destroy(StoreProduct) else redirect_to edit_admin_store_product_path, notice: alert_danger('Kunde inte förinta produkt') end end # All methods below this will be private private def store_product_params # This method ensures that the hash we get from the view has # a `StoreProduct`, and that we allow the permitted fields to be updated. params.require(:store_product).permit(:name, :price, :image_url, :in_stock) end end
Hints: Type
rails routes | grep storeto see all paths that have the wordstorein it. Make use of thestore_product_paramsmethod when creating a new product.Create a few basic
viewsto list and create new products. Theseviewsshould be placed inweb/views/admin/store_products, namelyweb/views/admin/store_products/ _form.html.erb edit.html.erb index.html.erb new.html.erb
The code for
edit.html.erbcan be found here:<% # web/app/views/admin/store_products/edit.html.erb %> <div class="col-md-10 col-md-offset-1 col-sm-12 reg-page"> <div class="headline"> <h1><%= 'Redigera produkt' %></h1> </div> <%= render('form', store_product: @store_product) %> <hr> <%= link_to('Förinta', admin_store_product_path(@store_product), method: :delete, data: {confirm: 'Är du säker på att du vill förinta produkten?'}, class: 'btn danger pull-right') %> <%= link_to('Alla produkter', admin_store_products_path, class: 'btn secondary') %> </div>
When you have implemented the views, make sure that they work as expected before moving to the next task.
Create a
serializerfor the products. Copy and paste the following script and implement the missing code:# web/app/serializers/api/store_product_serializer.rb class Api::StoreProductSerializer < ActiveModel::Serializer class Api::StoreProductSerializer::Index < ActiveModel::Serializer # TODO. Include all fields end end
Create an
API controllerfor the store and implement theindexmethod below. Hint: By doing task 10 and commenting outload_permissions_and_authorize_resourceyou can test if yourAPI controllerandserializerworks as expected.# web/app/controllers/api/store_products_controller.rb class Api::StoreProductsController < Api::BaseController load_permissions_and_authorize_resource def index # TODO. This should return a JSON object containing all products. end end
Add the rights to fetch the products for all signed in users in
abilities. This file can be found inweb/app/models/ability.rb.Add an API route for the created
API controllerinroutes.rband test that it works. The path will become what you write afterresources, e.g.tcp://localhost:3000/api/songs. Hint: By removingload_permissions_and_authorize_resourcefrom theAPI controlleryou can fetch the data without being logged in, allowing you to simply test yourAPI controllerandserializer.
App
Replace line 9 in
app/www/index.htmlwith:<!-- app/www/index.html --> <meta http-equiv="Content-Security-Policy" content="default-src 'self' https://stage.fsektionen.se https://fsektionen.se wss://fsektionen.se wss://stage.fsektionen.se gap://ready 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; child-src 'self' https://www.youtube.com gap://ready; media-src *; img-src * 'self' https://stage.fsektionen.se https://fsektionen.se data:" />
Create a HTML, SCSS and JS file for the F-store. Don’t forget to load the JS file in
index.htmland the SCSS file inindex.scss. Note that the JS file cannot be loaded before a few basic JS files have been loaded. Copy and paste the following outline of the JS file to the newly created one:// app/www/js/store.js $$(document).on('', '', function () { // TODO let storeProductAPIEndpointURL = ''; // TODO $.getJSON(storeProductAPIEndpointURL) .done(function(resp) { initStore(resp); }) .fail(function(resp) { console.log(resp.statusText); }); function initStore(resp) { // TODO } });
This is the general structure of our JS files. We first fetch the data and then send it to a function called
initSomethingwhere we handle the rest. Note that the other files don’t need to contain any code at this point.Add routes to the store in the alternatives view. The
nameshould bestoreandurlshould be the relative path to the created HTML file. The routes are defined inindex.js.Implement the created HTML file similarly to the other pages. Remember to set the
date-nameto thenameyou defined in the routes in the previous task, i.e.store.Add navigation to the new page in the alternatives tab. This is done by adding a few lines of code to the
<div id="view-alternatives" class="view tab"></div>inindex.html. The<a>tag should referene to the path you defined in the routes in task 2.Catch the
page:initevent in the created JS file. Make sure that it works by loggingSpodermon iz kewl.Fetch data from the API endpoint called
store_productsand log it.Create a template in
index.htmland test that it works. The latter is done by calling the template in the JS file and storing the HTML code in the store container. Note that the template does not have to handle potential input data, it should only be able to be used correctly.Edit the template such that it generates a store. The page should make use of Framework7 Cards to display information about the products. On the cards there should be a button where one should be able to purchase the product. Each button should have a data attribute where the
idof the product is stored. In this case the attribute will be nameddata-id. Remember that the price of the products are in Swedish Öre. Hint: Scroll down on the Framework 7 Cards documentation to see some examples.Catch the
clickevent when a buy button has been clicked using the jQuery methodon(). This code should be placed inside theinitStorefunction. When the button has been clicked, it should make aPOSTrequest tohttps://stage.fsektionen.se/api/store_orders. This should be done by calling the following function with the productid, which one can get from the data attribute of the button, as the input argument:// app/www/js/store.js function buyProduct(id) { $.ajax({ url: '', // TODO type: '', // TODO dataType: 'json', data: {}, // TODO success: function(resp) { app.dialog.alert(resp.success, 'Varan är köpt'); }, error: function(resp) { app.dialog.alert(resp.responseJSON.error); } }); }
Make sure that the
POSTrequest is successful. The data of thePOSTrequest should contain anitemobject withidandquantityas data. Below you can find an example of the structure of the data."item": { "id": 1, "quantity": 1 }
Style the page so it looks fresh.