Goal: Teaching ruby to newbies
We will build a self-checkout machine used in a supermarket together.
Checkout to the start point of Task 3
git checkout task3
Use ActiveRecord to persist data to SQLite
- Install ActiveRecord and SQLite
- Setup ActiveRecod to talk to SQLite memory storage
- Define database tables to save products, discounts, and purchases
- Extend Product, Discount, and Purchase classes to ActiveRecord::Base
- Set relationships of each tables in models
- purchase_summary shows hour:minute:second applying New Zealand Timezone
store.purchase_summary() #=>
[
["Time","Number of Products","Cost"],
["17/07/2015 00:00:00",2,20.0],
["18/07/2015 00:00:00",1,15.99]
]
- Raise an error if product and discount are not valid
Product: barcode and name should exist and cost should be bigger than 0
Discount: amount should be bigger than 0
Tell ruby which gems (libraries) to use (activerecord and sqlite3 gems in this case)
# create ./Gemfile to top folder and type below
source 'https://rubygems.org'
gem 'sqlite3', '1.3.10'
gem 'activerecord', '4.2.0'
install gems
# type this command on the top folder in termimal
bundle install
load active_record gem to use
require 'active_record'
setup connection to SQLite memory storage
ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
ActiveRecord::Schema.define do
# create cars table with 5 fields (actually 6 in total including id)
create_table "cars" do |t|
t.string "name", limit: 255
t.integer "year", limit: 4
t.integer "owner_id", limit: 4
t.decimal "price", precision: 8, scale: 2
t.datetime "purchased_at"
end
# create table connect cars and drivers in many to many relationship
# set {id: false} if no need direct access to this table
create_table "cars_drivers", id: false do |t|
t.integer "car_id", limit: 4
t.integer "driver_id", limit: 4
end
create_table "drivers" do |t|
t.string "name", limit: 255
end
create_table "ownsers" do |t|
t.string "name", limit: 255
end
end
class Car < ActiveRecord::Base
has_and_belongs_to_many :drivers
belongs_to :owner
end
class Driver < ActiveRecord::Base
has_and_belongs_to_many :cars
end
class Owner < ActiveRecord::Base
has_many :cars
end
Usage
Car.first.drivers # get drivers of the first car in the database through cars_drivers table
# find a car having id = 1
Car.find(1)
# find a car having name = "A4"
Car.find_by(name: "A4")
# find cars having name = "A4"
Car.where(name: "A4")
Car.where("cars.name = ?", "A4")
Car.where("cars.name = :name", name: "A4")
# use relations
Car.find(1).drivers
Car.find(1).owner
Owner.find(1).cars
# find cars which owner's name = "bob"
Car.joins(:owner).where(owners: {name: "bob"})
# count records
Car.count
Car.where(name: "A4").count
set validation
class Car < ActiveRecord::Base
validates :name, presence: true # name should exist
validates :price, numericality: {greater_than: 0}
end
check validation
Car.new(name: "", price: 0).valid? #=> false
validation is also called in save and update methods
Car.new(name: "", price: 0).save #=> false
Car.first.update(name: "", price: 0) #=> false
ActiveRecord saves time data to database as UTC (+0 hours). Time zone is required when we display data to the end user. Once you set time zone, time.zone methods (example: Time.zone.now) and active record's time data methods (example: Car.first.purchased_at) return the time with that time zone.
Even you set Time.zone, ruby time methods like Time.now (without 'zone') will return the time with time zone set in your OS.
Use time zone to ActiveRecord (aleady set in Rails)
ActiveRecord::Base.time_zone_aware_attributes = true
Set time zone
Time.zone = "Pacific/Auckland"
Useage
Time.zone.now # Mon, 27 Jul 2015 19:00:19 NZST +12:00
Car.first.purchased_at # Mon, 27 Jul 2015 19:00:19 NZST +12:00
Implement new requirements to store.rb and store_test.rb. Reading Ruby syntax will be useful to do this task.
-
Receipts show costs including cents (example: apple $5.00)
-
Customers can purchase products which returns the receipt
store.purchase(["0001", "0002"]) #=>
apple $5.00
orange $10.00
total $15.00
- Owner can view purchase summary as data array
store.purchase_summary() #=>
[
["Time","Number of Products","Cost"],
["17/07/2015",2,20.0],
["18/07/2015",1,15.99]
]
- Owner can add discount to a product
store.add_discount("0001", 1) #=> $1 discount to product "0001"
- Owner can delete discount to a product
store.delete_discount("0001") #=> delete discount to product "0001"
- Extra Task: Save products, purchases, discounts to SQL database using ActiveRecord with SQLite. ActiveRecord is M in MVC from Ruby on Rails framework. We will go through how to use this library in next class
"display float %.1f" % 1 #=> display float 1.0
Time.now #=> 2015-07-19 20:32:52 +1200
Time.new(2015,7,19) #=> 2015-07-19 00:00:00 +1200
Time.new(2015,7,19).strftime("%Y-%m-%d") #=> "2015-07-19"
As a tester, remove store_test.rb and recreate it from store.rb
As a developer, remove store.rb and recreate it to pass store_test.rb
As a passinate person, remove store_test.rb and store.rb and create them using TDD
User can add an item to store
store.add_item("0001", "apple", 10) # add $10 apple with barcode 0001
User can count stored items
store.item_count() #=> 10 (10 items)
User can calculate total cost of given items
store.calculate_cost(["0001", "0001"]) #=> 20 (two apples costs $20)
User can print receipt of given items
store.print_receipt(["0001", "0001"]) #=>
apple $10
apple $10
total $20
- Install railsinstaller from http://railsinstaller.org/en (WINDOWS RUBY 2.1)
- Install Rubymine from https://www.jetbrains.com/ruby/ (Not free but students can get 1 year license)
- Clone this project
- Run Rubymine
- Welcome page -> check out from Version Control -> Git
- Fill Repository info and click clone
- Git Repository URL "https://github.com/ducktyper/countdown.git"
- Parent Directory "choose where you want to save this project in your computer"
- Directory Name "countdown"
- Run test
- toolbar -> click 'run' -> click 'Run' -> choose 1. All tests in countdown
- Install railsinstaller from http://railsinstaller.org/en (WINDOWS RUBY 2.1)
- Install Sublime Text from https://http://www.sublimetext.com
- Clone this project
- Open Terminal
- Move to where you want to clone the project (Example: C:\Users\daniel\Desktop\projects)
- git clone https://github.com/ducktyper/countdown.git
- Open cloned folder from Sublime Text
- Open score_test.rb file and click Ctrl-b to run test
Here is basic ruby syntax useful to this task.
class Item
def initialize()
end
end
class Item
def initialize(name)
end
end
item1 = Item.new()
item2 = Item.new("apple")
require "./item" # read file item.rb under the current folder and insert it
class Item
def initialize(name)
@name = name
end
def print()
puts @name
end
end
Item.new("apple").print() #=> "apple"
- declear method
class Item
def initialize()
end
def price(quantity)
10 * quantity # result of the last line returns automatically
end
end
- call method
price = Item.new().price(5) #=> 50
- if statement
if (1 == 1)
puts "case1"
elsif (2 == 2)
puts "case2"
else
puts "case3"
end
- switch statement
case 1
when 1
puts "1"
when 2
puts "2"
end
- create string
name = "apple"
- append string
name = "apple"
puts name + "orange" #=> apple orange
- combine strings
name = "apple"
puts "having #{name}" #=> having apple
- create empty array
array = []
- add item to array
array = []
array << "milk"
- get item by index
array = ["apple", "orange"]
array[0] #=> "apple"
- find index of item
array = ["apple", "orange"]
array.index("apple") #=> 0
- loop through each item
array = ["apple", "orange"]
array.each do |item|
puts item # execute this line for each item
end #=> "apple\norange"
array.each { |item| puts item } # same as above
- map to create a new array with different value
array = [1,2,3]
array.map do |item|
item * 3
end #=> [3,6,9]
A Hash is a dictionary-like collection of unique keys and their values. Same as 'Dictionary' in C#, 'Object Properties' in Javascript.
- create empty hash
hash = {}
- add key and value pair
hash = {}
hash["key"] = "value"
- get value from key
hash = {"key" => "value"}
hash["key"] #=> "value"
Symbol is like string but faster to compare equality. It is popular using Symbol as key in Hash.
- Use Symbol in Hash
hash = {:name => "apple", :price => 10}
- Short syntax using Symbol in Hash
hash = {name: "apple", price: 10}
- Inject to compose data
# update 0 5 times (0 -> 1 -> 3 -> 6 -> 10 -> 15) and return
[1, 2, 3, 4, 5].inject(0) do |total, number|
total + number
end #=> 15
file name ends with "_test.rb"
- basic format
require 'minitest/autorun'
describe "math" do # 'describe' block wraps 'it' blocks
it "can sum two numbers" do # 'it' block represents single test
assert_equal(2, 1 + 1) # assertion
end
end
- assertion
expected = true
actual = (1 + 1 == 2)
assert_equal(expected, actual)