[readolog_dropcap ]H[/readolog_dropcap]appy new Rails year, fellow riders!
I finished my very first „serious“ project – yay! It looks and behaves like a rails eShop (you can find link further down below). Before I go into technical details some intro – this website was created for friends of mine and although it was kind of experimental for me (first real app), it turned out to be quite decent as one might say so.
As with every customer-oriented project, there was a lot of JavaScript involved, which is by far not my strongest language. It started calm and nicely with pure ruby on rails, I could use a lot of stuff I learned from this book – Agile Web Development With Rails. But then… as more and more wished appeared I realized that there is no way around some front-end development.
Enough smalltalk, let’s get straight to the data:
- Bootstrap Theme used – „Minimal„
- Notable gems: figaro (managing global variables), devise (users), carrierwave (images uploads), sendgrid (for sending any kinds of mails)
- Database: postgresql (I think it’s pretty a must-have for shops because of hstore – which, as far as I understand makes postgres almost non-relational, because hstore columns allow to store hash/value pairs)
- Deployment: capistrano, puma, nginx (you can check out my article about deployment here)
- Other features: protected admin namespace
[readolog_dropcap ]M[/readolog_dropcap]ost notable code
JavaScript – a calculator for end value of a product based on amount, delivery method and size (plus additional services, which are to be differentiated between one for each product and one for a full order). There is some hard coded stuff and of course this piece of code might not be the state of the art, however it works pretty well.
$(document).ready ->
if $('a.size_input').length != 0
$('a.size_input')[0].click()
jQuery ->
$('li a').click (event) ->
# event.preventDefault()
$('a').removeClass('active')
$(this).addClass('active')
$('form').on 'click', '.add_fields', (event) ->
time = new Date().getTime()
regexp = new RegExp($(this).data('id'), 'g')
$(this).before($(this).data('fields').replace(regexp, time))
event.preventDefault()
get_multiplier = ->
amount = $('#amount').val()
multiplier = 1.5
multiplier = switch
when amount <= 99 then multiplier = 1.5
when amount >= 100 and amount < 300 then multiplier = 1.4
when amount >= 300 and amount < 500 then multiplier = 1.3
when amount >= 500 and amount < 1000 then multiplier = 1.25
when amount >= 1000 and amount < 3000 then multiplier = 1.23
when amount >= 3000 and amount < 7000 then multiplier = 1.2
return multiplier
get_delivery = ->
delivery = $('#delivery').val()
return parseFloat(delivery).toFixed(2)
get_size = ->
# in this method we either select the price value based on the flash
# drive size, or, if it has a basic price, just simply the basic price
# from the hidden #basicprice id
selected_size = $('input[name=volume]:checked').val()
if (selected_size == undefined)
return parseFloat($('#basicprice')[0].innerText)
else
array = selected_size.split(",")[1].replace(']','')
return parseFloat(array).toFixed(2)
get_all_addservices = ->
# This method delivers all checked addservice price
# so we can first substract it before applying any multiplications
addservices = new Array()
addservicesParty = new Array()
console.log('inside get all addservices:')
checkboxes = document.getElementsByName("addservices[]")
for checkbox in checkboxes
addserviceprice_splitted = (checkbox.value).split(" ")
# console.log(addserviceprice_splitted[2])
# Here we need to check if the 'apply to whole party is true or false'
party = addserviceprice_splitted[2]
addserviceprice = parseFloat(addserviceprice_splitted[1]).toFixed(2)
if checkbox.checked
parsedPrice = parseFloat(addserviceprice)
if party == 'true'
addservicesParty.push(parsedPrice)
else
addservices.push(parsedPrice)
if addservices.length > 0
sum = addservices.reduce((a,b) => a+b)
else
sum = 0
if addservicesParty.length > 0
sumParty = addservicesParty.reduce((a,b) => a+b)
else
sumParty = 0
console.log('Addservices combined: ' + addservices + '| Addservice to whole party: '+ addservicesParty)
return [parseFloat(sum), parseFloat(sumParty)]
recalculate = ->
# This function recalculates the price
base = get_size()
deliveryCoefficient = get_delivery()
amountCoefficient = get_multiplier()
addservices = get_all_addservices()
addserviceOne = addservices[0]
addserviceParty = addservices[1]
amount = $('#amount').val()
console.log('base - ' + base + '| delivery - ' + deliveryCoefficient + ' | add service sum - ' + addservices + ' | amount coefficient - ' + amountCoefficient)
endPriceOne = (base*deliveryCoefficient*amountCoefficient) + addserviceOne
endPriceParty = (base * deliveryCoefficient * amountCoefficient) * amount
endPriceAll = endPriceOne * amount + addserviceParty
endPriceServices = (addserviceOne * amount) + addserviceParty
$('#priceValue').text(endPriceAll.toFixed(2))
$('#priceForOne').text(endPriceOne.toFixed(2))
$('#priceForAll').text(endPriceParty.toFixed(2))
$('#priceForAddservices').text(endPriceServices.toFixed(2))
$('.size_input').click (event) ->
recalculate()
$('.addservice_checkbox').change (event) ->
recalculate()
$('#amount').on('input', (event) ->
recalculate()
)
$('#delivery').change (event) ->
recalculate()
$("#thediv").click (event) ->
$("#addservices").toggleClass("reveal-closed").toggleClass("reveal-open")
$('#load_more_btn').click (event) ->
event.preventDefault()
Here we have some fancy helper methods which I grabbed from Ryan Bates screencasts. They allow you to add fields on the go:
module ApplicationHelper
def hidden_div_if(condition, attributes = {}, &block)
if condition
attributes["style"] = "display: none"
end
content_tag('div', attributes, &block)
end
def link_to_add_fields(name, f, association)
new_object = f.object.send(association).klass.new
id = new_object.object_id
fields = f.fields_for(association, new_object, child_index: id) do |builder|
render(association.to_s.singularize + "_fields", f: builder)
end
link_to(name, '#', class: 'add_fields', data: {id: id, fields: fields.gsub("\n", "")})
end
[readolog_dropcap ]L[/readolog_dropcap]ast, but not least
we have of course a fully functional rails eShop with a working cart, order placement, sending confirmation letters to customers and admins, an administration panel for managing products etc. etc.
Unfortunately the owners decided to put this page on ice, therefore it’s unlikely that you will receive some flash drives. However you can check the page out all by yourself – http://souvenirnya-produktsiya.ru/