#WDI Week 10 Notes
#Monday
##Burning Airlines demos
https://github.com/amysimmons/burning-airlines
##Closures
<button>Click me</button>
$(document).ready(function(){
var msg = "hello world";
var counter = 0
console.log(msg);
$('button').on('click', function(){
$('body').append(msg);
counter++;
console.log(counter);
});
});
console.log(msg);
the last console.log msg is undefined here because the whole function doesnt run until the document is ready, whereas the console.log will run straight away they're also in different function scopes
to fix you could do var msg outside, above the function, and just use msg = 'hello world' inside the function
var greeter = function(greeting, name){
console.log(greeting + ' ' + name + '!');
};
var greeterMaker = function(greeting){
var customGreeting = greeting;
var greeter = function(name){
console.log(customGreeting + ' ' + name + '!');
};
return greeter;
};
greeter('Howdy', 'Barry');
greeterMaker('Gday');
var gdayGreeter = greeterMaker('Gday');
var howdyGreeter = greeterMaker('Howdy');
gdayGreeter('Norm');
howdyGreeter('Barry');
by defining the greeter function within another function and returning it, i can access the variables in customGreeting
first i create a factory that knows how to make other functions
greetermake itself returns another function
and now i can call each of those functions as htough i defined them that way in the first place
var idMaker = function (prefix) {
var id = 0;
return function () {
return prefix + id++;
};
};
var projectID = idMaker('PJ');
var itemID = idMaker('IT');
console.log(projectID());
console.log(itemID());
projectID();
projectID();
projectID();
console.log(projectID());
console.log(itemID());
Functional JavaScript:
http://shop.oreilly.com/product/0636920028857.do
This covers closures really well in the first chapter.
##Regexp
anything in between / and / is considered to be the regular expression
if you think about all the possible strings in the world, all a regular expression does it divides these strings into two groups
either they match, or they don't match
/at/
if i took the string hat, it does match this patters, but if i took the string ab or b, these don't match the above pattern
Ruby is really good at regular expressions
Regular Expressions are incredibly powerful and you can get a lot done without much code, despite that, lots of people don't know how to do them
With Ruby, regular expressions are literals
In other languages you have to put quotes around your regular expressiona nd pretend it is a string, but not with Ruby, same with JavaScript
[1] pry(main)> /at/
=> /at/
Does the pattern /at/ match anywhere here?
[2] pry(main)> "cat" == /at/
=> false
[3] pry(main)> "cat" =~ /at/
=> 1
[4] pry(main)> "panama" =~ /at/
=> nil
[5] pry(main)> /at/ =~ 'cat'
=> 1
If you get back a number, it measn it matches, if you get back a nil, it means it doesn't match.
The regular expressions are case sensitive.
So we have a nice way of saying, does a string contain another string?
[6] pry(main)> 'your majesty' =~ /maj/
=> 5
Reuturns the index of the letter where the match begins.
But this is the same as Ruby's index method
[8] pry(main)> "your majesty".index("maj")
=> 5
Where regular expressions are most powerful is
[9] pry(main)> "cat" =~ /.t/
=> 1
[10] pry(main)> "tea" =~ /.t/
=> nil
This regular expression says there has to be any character in front pf assword for it to return a match:
[11] pry(main)> "gimme your password" =~ /password/
=> 11
[12] pry(main)> "gimme your password" =~ /.assword/
=> 11
[13] pry(main)> "assword" =~ /.assword/
=> nil
The first example below returns nil because there is no character in the string, whereas in the second example the full stop is the character:
[14] pry(main)> "" =~ /./
=> nil
[15] pry(main)> "." =~ /./
=> 0
The escape character:
[16] pry(main)> "hello Mr. Giggles" =~ /Mr./
=> 6
[17] pry(main)> "hello Mrs. Giggles" =~ /Mr./
=> 6
[18] pry(main)> "hello Mrs. Giggles" =~ /Mr\./
=> nil
[19] pry(main)> "hello Mr. Giggles" =~ /Mr\./
=> 6
Metacharacters:
- . any character
Quantifiers:
? 0 or 1
+ 1 or more
* 0 or more
The quantifier applies to the character immediately before it.
BamBam example:
[20] pry(main)> "BamBam" =~ /BamBam/
=> 0
[21] pry(main)> "Bam-Bam" =~ /Bam-Bam/
=> 0
[22] pry(main)> "Bam-Bam" =~ /Bam.Bam/
=> 0
[23] pry(main)> "BamBam" =~ /Bam.Bam/
=> nil
[24] pry(main)> "Bam+Bam" =~ /Bam.Bam/
=> 0
What I want to do is to tsay that the dash chracter is optional...
So this regular expression says that the dash can appear 0 or 1 times.
[25] pry(main)> "Bam-Bam" =~ /Bam-?Bam/
=> 0
[26] pry(main)> "BamBam" =~ /Bam-?Bam/
=> 0
Note that this doesn't match Bam--Bam:
[27] pry(main)> "Bam--Bam" =~ /Bam-?Bam/
=> nil
So I can use the 0 or more quantifier:
[28] pry(main)> "Bam--Bam" =~ /Bam-*Bam/
=> 0
[29] pry(main)> "Bam------------Bam" =~ /Bam-*Bam/
=> 0
But this won't work if there is any random char inside the dashes:
[30] pry(main)> "Bam------=------Bam" =~ /Bam-*Bam/
=> nil
So the way to solve this is to use this quantifier, which says Bam followed by any character any number of times, they can be the same character or completely different characters.
[31] pry(main)> "Bam------=------Bam" =~ /Bam.*Bam/
=> 0
[32] pry(main)> "BamBam" =~ /Bam.*Bam/
=> 0
[33] pry(main)> "Bam-Bam" =~ /Bam.*Bam/
=> 0
Note that this only works if there is two Bams, and any old junk inbetween:
[34] pry(main)> "Bam" =~ /Bam.*Bam/
=> nil
This says I am looking for one or more digits. This says match be the number, rather than just returning the index.
[41] pry(main)> m = ' $ 890 or best offer'.match(/\d+/)
=> #<MatchData "890">
You can use the OR pipe in your expression:
[42] pry(main)> /Fred|Wilma/ =~ "I hate you Fred"
=> 11
[43] pry(main)> /Fred|Wilma/ =~ "I killed Wilma"
=> 9
[44] pry(main)> /Fred|Wilma/ =~ "I killed Wilma and Fred"
=> 9
[45] pry(main)> /Fred|Wilma|Pebbles/ =~ "I abducted Pebbles and ate her skin"
=> 11
You can look for a pipe in your string:
[46] pry(main)> "some|string|with|pipes" =~ /\|/
=> 4
Looking for upper and lower case or both:
[47] pry(main)> "mr hummerdunner" =~ /[mM]r/
=> 0
[48] pry(main)> "Mr hummerdunner" =~ /[mM]r/
=> 0
[49] pry(main)> "MR hummerdunner" =~ /[mM]r/
=> nil
[50] pry(main)> "MR hummerdunner" =~ /[mM][rR]/
=> 0
[51] pry(main)> "Mr hummerdunner" =~ /[mM][rR]/
=> 0
[52] pry(main)> "mR hummerdunner" =~ /[mM][rR]/
=> 0
[53] pry(main)> /[a-z]r/ =~ "Tr"
=> nil
[54] pry(main)> /[a-z]r/ =~ "rr"
=> 0
[55] pry(main)> /[A-Z]r/ =~ "RR"
=> nil
[56] pry(main)> /[A-Za-z]r/ =~ "pr"
=> 0
[57] pry(main)> /[A-Za-z]r/ =~ "7r"
=> nil
[58] pry(main)> /[0-9]r/ =~ "7r"
=> 0
[59] pry(main)> /[0-9]r/ =~ "Xr"
=> nil
Back references:
This is called a capturing group, with brackets around whatever you want...
[64] pry(main)> "mat mat" =~/(.at) \1/
=> 0
[65] pry(main)> "cat mat" =~/(.at) \1/
=> nil
The 1 is telling you which group of brackets to use
it refers to something that you have found earlier
so with this simple example im saying anything followed by at, followed by that simple patter again
Regular expressions practice:
https://gist.github.com/wofockham/e3db2d0e21e7201f76a0
http://jessicaaustin.github.io/regexquest/game/
http://www.amazon.com/Mastering-Regular-Expressions-Jeffrey-Friedl/dp/0596528124
##Rake
###RakeFiles
rake => Ruby make
# create directory
directory "tmp"
# the task of creating my hello.tmp file depends on this tmp folder
file "tmp/hello.tmp" => "tmp" do
# shell command, tells it to echo hello and append it to tmp/hello.tmp
# because this taks depends on having tmp folder exist,
# it will create the tmp folder for me first
sh "echo 'Hello' >> 'tmp/hello.tmp'"
end
# in the terminal, we run rake hello.tmp
# =>mkdir -p tmp
# =>echo 'Hello' >> 'tmp/hello.tmp'
#now a tmp folder is created with a file hello.tmp and the file contains the word Hello
# now all these tasks are going to be prefixed by morning
# eg morning:turn_off alarm
namespace :morning do
desc "Turn off the goddam alarm"
task :turn_off_alarm do
puts "Turning off the alarm.."
end
desc "make myself pretty"
task :groom_self do
puts "Washing hair.."
puts "Brushing teeth.."
puts "Showering.."
end
#to make cups dynamic, we can use environment variables
desc "love coffee"
task :make_coffee do
cups = ENV["COFFEE"] || 2
puts "Making #{(cups)} cups of coffee."
end
desc "hate little dogs"
task :walk_dog do
puts "Walking horrible little dog..."
end
# get ready now depends on these other tasks, so it will run all the others first, then
# run get_ready
desc "another miserable day"
task :get_ready => [:turn_off_alarm, :groom_self, :make_coffee, :walk_dog] do
puts "oh god, ready to face the day"
end
end
#this sets the default for rake
#now i can just run rake and it will perform this task
task :default => 'morning:get_ready'
#not brushing beard will be included in my groom_self method
#so you can extend a task if you want by adding additional stuff to an existing one
#this is useful if you don't have access to a method
namespace :morning do
task :groom_self do
puts 'Brushing beard...'
end
end
namespace :afternoon do
desc "making more coffee later in day"
task :make_coffee do
#task obejct inside of rake namespace
#invoke will cause the code to be triggered
# in the afternoon i can make coffee,
# and it will find the morning task and run that for me
Rake::Task['morning:make_coffee'].invoke
puts 'adding some rum...'
puts 'ready for the afternoon...'
end
end
# Additional notes:
# in the console run rake turn_off_alarm
# => Turning off the alarm..
# file_example $ rake make_coffee
# Making 2 cups of coffee.
# file_example $ rake get_ready
# Turning off the alarm..
# Washing hair..
# Brushing teeth..
# Showering..
# Making 2 cups of coffee.
# Walking horrible little dog...
# oh god, ready to face the day
# [1] pry(main)> ENV['USER']
# => "amysimmons"
# [2] pry(main)> ENV['RUBY_VERSION']
# => "ruby-2.1.4"
# coffee now dynamic:
# file_example $ COFFEE=5 rake get_ready
# Turning off the alarm..
# Washing hair..
# Brushing teeth..
# Showering..
# Making 5 cups of coffee.
# Walking horrible little dog...
# oh god, ready to face the day
# export COFFEE=23 would save the variable for every time you run rake get_ready,
# you wouldn't need to set it again each time like this COFFEE=5 rake get_ready
# What the desc line does:
# if I run rake -T it will now list all the descriptions for each method
# file_example $ rake afternoon:make_coffee
# Making 2 cups of coffee.
# adding some rum...
# ready for the afternoon...
##Fish eater and Factory Girl and Faker
have a system that loads tweets from twitter
write a rake task that will populate this with actual tweets
you want to have a rake command that will be something like rake tweet:seed(Nike)
so when the front end poeple hook it up, all the fish in the tank will have tweets with Nike in in them
If you need to reseed the database, you could swap out the word Nike for Rebock and it would still work
Testing the rships in pry:
Last login: Mon Mar 16 14:14:40 on ttys003
You have new mail.
Hi Amy, welcome to the terminal!
tweeteater $ rails c
Loading development environment (Rails 4.2.0)
2.1.4 :001 > u = User.create :name => 'george', :email => '[email protected]'
(0.3ms) begin transaction
SQL (1.6ms) INSERT INTO "users" ("name", "email", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["name", "george"], ["email", "[email protected]"], ["created_at", "2015-03-16 04:57:31.465764"], ["updated_at", "2015-03-16 04:57:31.465764"]]
(1.3ms) commit transaction
=> #<User id: 1, name: "george", email: "[email protected]", created_at: "2015-03-16 04:57:31", updated_at: "2015-03-16 04:57:31">
2.1.4 :002 > t = Tweet.create :post => "twett post yes"
(0.1ms) begin transaction
SQL (1.0ms) INSERT INTO "tweets" ("post", "created_at", "updated_at") VALUES (?, ?, ?) [["post", "twett post yes"], ["created_at", "2015-03-16 04:58:23.790412"], ["updated_at", "2015-03-16 04:58:23.790412"]]
(1.2ms) commit transaction
=> #<Tweet id: 1, user_id: nil, post: "twett post yes", created_at: "2015-03-16 04:58:23", updated_at: "2015-03-16 04:58:23">
2.1.4 :003 > u.tweets << t
(0.2ms) begin transaction
SQL (0.5ms) UPDATE "tweets" SET "user_id" = ?, "updated_at" = ? WHERE "tweets"."id" = ? [["user_id", 1], ["updated_at", "2015-03-16 04:58:44.582788"], ["id", 1]]
(1.1ms) commit transaction
Tweet Load (0.3ms) SELECT "tweets".* FROM "tweets" WHERE "tweets"."user_id" = ? [["user_id", 1]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Tweet id: 1, user_id: 1, post: "twett post yes", created_at: "2015-03-16 04:58:23", updated_at: "2015-03-16 04:58:44">]>
2.1.4 :004 > u.tweets
=> #<ActiveRecord::Associations::CollectionProxy [#<Tweet id: 1, user_id: 1, post: "twett post yes", created_at: "2015-03-16 04:58:23", updated_at: "2015-03-16 04:58:44">]>
2.1.4 :005 >
Created a factories.rb file in test folder
FactoryGirl.define do
factory :user do |f|
name "George Best"
email "[email protected]"
# this will give a unique custom email
f.sequence(:email) {|n| "george#{n}@best.com"}
end
end
then run n = FactoryGirl.create :user in the console
Gems for this lab:
Factory Girl gem - https://github.com/thoughtbot/factory_girl_rails
Faker gem - https://github.com/stympy/faker
Once Faker is installed, you can run this in the console
Faker::Name.name and Faker::Internet.email
and it will generate a random name or a random email
Faker::Lorem.sentence will generate a random lorem ipsum sentence
Faker::Hacker.say_something_smart generates random geek sentences
Generating random name, email and post data with Faker:
FactoryGirl.define do
factory :user do |f|
f.sequence(:name) {Faker::Name.name}
f.sequence(:email) {Faker::Internet.email}
# creates a user, and creates tweets, and associates them with the user
factory :user_with_tweets do |f|
after(:create) do |u|
#after creating the user, it will create a tweet, a random number of tweets
# and all of these tweets are associated with the user
FactoryGirl.create_list :tweet, Random.rand(10..100), :user => u
end
end
end
factory :tweet do |f|
f.sequence(:post) {Faker::Hacker.say_something_smart}
end
end
# Commands in the console
# FactoryGirl.create :user
# FactoryGirl.create :tweet
# FactoryGirl.create :user_with_tweets
#Tuesday
##Warmup
##Tweeteater Demos
##REGEXP II
Metacharacters:
[a-z] - character in this range
[^a-z] - char not in this range
[0-9] => \d
[^0-9] - any char that isn't a digit
Quantifiers:
* 0 or more
? 0 or 1
+ 1 or more
"AUD 12.78"
.match /[A-Z][A-Z][A-Z]/
is the same as
.match /[A-Z]{3}
Capturing:
[19] pry(main)> if (m = line.match /([A-Z]{3})\s+(\d+\.\d+)/)
[19] pry(main)* puts "#{m[1]} trades at #{m[2]}"
[19] pry(main)* end
[2] pry(main)> "Fred and Wilma".match /wilma/i
=> #<MatchData "Wilma">
[3] pry(main)> "AUD GBP and JPN and USD".match /[A-Z]{3}/
=> #<MatchData "AUD">
[35] pry(main)> pattern = /
[35] pry(main)* (?<currency>[A-Z]{3}) # matches a three letter currency code
[35] pry(main)* \s+ # followed by one or more spaces
[35] pry(main)* (?<amount>\d+\.\d+) # the dollar amount, as a decimal
[35] pry(main)* /x
=> /
(?<currency>[A-Z]{3}) # matches a three letter currency code
\s+ # followed by one or more spaces
(?<amount>\d+\.\d+) # the dollar amount, as a decimal
/x
[36] pry(main)> line
=> " USD 0.000124 "
[37] pry(main)> line.match pattern
=> #<MatchData "USD 0.000124" currency:"USD" amount:"0.000124">
[38] pry(main)>
##Interview Qs
##Portfolio
##Homework
##JS Hangman
#Wednesday
##Warmup
##Hangman demos
https://github.com/amysimmons/wdi8_homework/tree/master/amy/hangman
##RSPEC with Rails
###Fruitstore
rails new fruitstore_app -T
Included rpsec gem in development
rails generate rspec:install
rails generate model Fruit
there should be a spec folder with an rspec file for the fruit model
to run rspec we sometimes need to remove warnings from the .rspec file
it's normal to get an error saying we need to run migrations
delete line about pending in fruit_spec.rb and write some tests
require 'rails_helper'
RSpec.describe Fruit, type: :model do
describe "An apple" do
before do
@apple = Fruit.new
end
it 'should not be squishy' do
expect(@apple.squishy?).to == false
# other ways of writing the test:
# expect(@apple.squishy?).to be_false
# expect(@apple.squishy?).to eq(false)
end
end
describe "A pear" do
before do
@pear = Fruit.new
end
it 'should be kind of squishy' do
expect(@pear.squishy?).to == true
# other ways of writing the test:
# expect(@apple.squishy?).to be_true
# expect(@apple.squishy?).to eq(true)
end
end
end
to run a rest, you can either run rspec, which will run all the tests, or rspec spec/models/fruit_spec.rb
created separate models for pear and apples
in the console:
fruitstore_app $ rails c
Loading development environment (Rails 4.2.0)
2.1.4 :001 > pear = Pear.new :name => 'Gnashi'
=> #<Pear id: nil, created_at: nil, updated_at: nil, name: "Gnashi">
2.1.4 :002 > apple = Apple.new :name => 'golden delicious'
=> #<Apple id: nil, created_at: nil, updated_at: nil, name: "golden delicious">
2.1.4 :003 > apple.class
=> Apple(id: integer, created_at: datetime, updated_at: datetime, name: string)
2.1.4 :004 > pear.class
=> Pear(id: integer, created_at: datetime, updated_at: datetime, name: string)
2.1.4 :005 > pear.is_a? Pear
=> true
2.1.4 :006 > apple.is_a? Apple
=> true
2.1.4 :007 > pear.is_a? Apple
=> false
2.1.4 :008 > pear.save
(0.3ms) begin transaction
SQL (1.0ms) INSERT INTO "fruits" ("name", "created_at", "updated_at") VALUES (?, ?, ?) [["name", "Gnashi"], ["created_at", "2015-03-18 00:24:11.539751"], ["updated_at", "2015-03-18 00:24:11.539751"]]
(9.3ms) commit transaction
=> true
2.1.4 :009 > apple.save
(0.1ms) begin transaction
SQL (0.4ms) INSERT INTO "fruits" ("name", "created_at", "updated_at") VALUES (?, ?, ?) [["name", "golden delicious"], ["created_at", "2015-03-18 00:24:13.629400"], ["updated_at", "2015-03-18 00:24:13.629400"]]
(8.9ms) commit transaction
=> true
But Pear.all will give me back the apple, and Apple.all will give me back the pear
So we want to use STI - Single Table Inheritance
rails generate migration AddTypeToFruits type:string
this is the only time you can use type in your table
2.1.4 :002 > p = Pear.new :name => 'gnashi'
=> #<Pear id: nil, created_at: nil, updated_at: nil, name: "gnashi", type: "Pear">
2.1.4 :003 > a = Apple.new :name => 'golden delicious'
=> #<Apple id: nil, created_at: nil, updated_at: nil, name: "golden delicious", type: "Apple">
2.1.4 :004 > p.save
(0.1ms) begin transaction
SQL (0.5ms) INSERT INTO "fruits" ("type", "name", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["type", "Pear"], ["name", "gnashi"], ["created_at", "2015-03-18 00:29:04.454557"], ["updated_at", "2015-03-18 00:29:04.454557"]]
(8.6ms) commit transaction
=> true
2.1.4 :005 > a.save
(0.1ms) begin transaction
SQL (0.4ms) INSERT INTO "fruits" ("type", "name", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["type", "Apple"], ["name", "golden delicious"], ["created_at", "2015-03-18 00:29:05.513830"], ["updated_at", "2015-03-18 00:29:05.513830"]]
(9.5ms) commit transaction
=> true
2.1.4 :006 >
Now we can say Pear.all and it will only get me the ones where the type is equal to pear
Same for Apple.all
And Fruit.all will return everything
So we can have one table for all our fruits, which will autmoaticaally add the type when saved as pear, or apple, etc
2.1.4 :009 > p = Pear.first
Pear Load (0.4ms) SELECT "fruits".* FROM "fruits" WHERE "fruits"."type" IN ('Pear') ORDER BY "fruits"."id" ASC LIMIT 1
=> #<Pear id: 3, created_at: "2015-03-18 00:29:04", updated_at: "2015-03-18 00:29:04", name: "gnashi", type: "Pear">
2.1.4 :010 > p.class
=> Pear(id: integer, created_at: datetime, updated_at: datetime, name: string, type: string)
2.1.4 :011 > p.is_a? Pear
=> true
2.1.4 :012 > p.is_a? Apple
=> false
2.1.4 :013 > f1 = Fruit.first
Fruit Load (0.3ms) SELECT "fruits".* FROM "fruits" ORDER BY "fruits"."id" ASC LIMIT 1
=> #<Pear id: 3, created_at: "2015-03-18 00:29:04", updated_at: "2015-03-18 00:29:04", name: "gnashi", type: "Pear">
2.1.4 :014 > f1.class
=> Pear(id: integer, created_at: datetime, updated_at: datetime, name: string, type: string)
2.1.4 :015 >
add gem 'shoulda-matchers' to development group
bundle
rails generate model Shelf
now we can add associations in our shelf and fruit spec files
it {should belong_to :shelf}
it {should have_many :fruits}
rails generate migration addShelfIdToFruits shelf_id:integer
the id will go on the fruit so multiple fruits can have a shelf
then add the associations to the models
the tests should now pass because our associations exist, even though we haven't created any associations yet
rake stats will tell me the tests and my lines of code
100 per cent of my lines of code should be covered by tests
add this to gemfile and rebundle
gem 'simplecov', :require => false, :group => :test
https://github.com/colszowka/simplecov
add this to spec helper.rb
require 'simplecov'
SimpleCov.start
now if we run spec, everything should be as it was before, but simplecov is also running behind the scenes watching these tests pass or fail, and it will generate a report for us
we can run open coverage/index.html to see our test coverage
###Scaffy
rails new scaffy -T
add rspec gem to gemfile
bundle
rails generate rspec:install
rails generate scaffold Post title:string content:text
this gives you an example of the testing that rails can write for you
##Cucumber
##SydJS
#Thursday
##Warmup
##SYDjs Recap
##RSPEC Functional Testing
Fruit.all.to_sql
The first line here makes the database do the work, and let's Ruby be lazier:
@fruits = Fruit.order('id DESC')
# @fruits = Fruit.all.reverse
we want to reverse the data at the sql level, not the ruby level, which is what the second line does.
##1pm guest speaker Lawrence
##Bonus Reviews
##Interview questions
##G from Lookahead
##Homework
use jquery animate for the slider
and a jquery method called stop with special parameters to get it to finish the old animation before it starts the next one
another problem is what to do when you get to the last image
it can also go get a copy of the first image and move it to the end, so it's like its on a loop, you can create a new element or do it with the existing one by changing the css
#Friday
##Warmup
##Carousel Demos
##jQuery plugins
http://learn.jquery.com/plugins/basic-plugin-creation/
Read: Functional JavaScript book
##Backbone
###Secrets
Steps:
Secret model
content:string
scaffold
API
From the start:
rails new whisper -T
rails generate scaffold Secret content:text
rake db:migrate
annotate
go to http://localhost:3000/secrets
create some new secrets
i can go to http://localhost:3000/secrets.json to see the new secrets in json format
they have ids, content, etc
what we want to make realtime about this is to load the page once, and continue requesting from the server saying can you give me the new secrets here
so when you visit this url / it will load everything, and talk behind the scenes to the /secrets urls
in congig routes, set the root to root :to => 'pages#index'
rails generate controller Pages index
now we can go to http://localhost:3000/ and the index page will be there
delete pages.coffee and index.coffee files
cd vendor/assets/javascripts
get backbone and underscore
curl http://underscorejs.org/underscore.js > underscore.js
curl http://backbonejs.org/backbone.js > backbone.js
(backbone development link)
we should now be able to go to http://localhost:3000/assets/backbone.js
we should now be able to go to http://localhost:3000/assets/underscore.js
to see the files
remove //= require turbolinks from application.js file
add backbone and underscore
//= require jquery
//= require jquery_ujs
//= require underscore
//= require backbone
//= require_tree .
create new whisper.js file
create folders for my other js files:
whisper $ cd app/assets/javascripts
javascripts $ mkdir models
javascripts $ mkdir collections
javascripts $ mkdir views
javascripts $ mkdir routers
dont forget to include handlebars in vendor/assets/javascripts
curl http://builds.handlebarsjs.com.s3.amazonaws.com/handlebars-v3.0.0.js > handlebars.js
and require it
//= require jquery
//= require jquery_ujs
//= require underscore
//= require handlebars
//= require backbone
//= require_tree .
create secret.js in my javascript models folder
var whisper = whisper || {};
whisper.secret = Backbone.Model.extend({
urlRoot: '/secrets',
defaults: {
content:''
}
});
//= require models/secret in application.js
reload the homepage
the first thing this lets us do is have the ability to create secrets from the console
in the console run
s1 = new whisper.secret({content: 'Backbone created content'});
this means the secret is in the browser but it's not saved
backbone can solve this problem for me
if i create it here in the browser, i can then save it to the server
s1 = new whisper.secret({content: 'Backbone created content'});
child {cid: "c1", attributes: Object, _changing: false, _previousAttributes: Object, changed: Object…}
s1.attributes
Object {content: "Backbone created content"}
s1.save()
Object {readyState: 1, getResponseHeader: function, getAllResponseHeaders: function, setRequestHeader: function, overrideMimeType: function…}
so now i can create a secret and save it from the server in two lines of code
http://localhost:3000/secrets.json i can see my new secret
in my console i can do something like this to get my secrets
$.ajax('/secrets', {dataType: "json"}).done(function(results){
whisper.secrets = results;
});
but this is garbage ajax!
i dont want to have to write this every time i want to get new things from the server
so to make this work we want a collection.
so we make secrets.js in our js collections folder
var whisper = whisper || {};
whisper.Secrets = Backbone.Collection.extend({
url: '/secrets',
model: whisper.Secret
});
now we can go to http://localhost:3000/
and create a new instance of the secrets collection
and fetch all the secrets
whisper.secrets = new whisper.Secrets()
child {length: 0, models: Array[0], _byId: Object, constructor: function, url: "/secrets"…}
whisper.secrets.fetch()
Object {readyState: 1, getResponseHeader: function, getAllResponseHeaders: function, setRequestHeader: function, overrideMimeType: function…}
whisper.secrets
child {length: 4, models: Array[4], _byId: Object, constructor: function, url: "/secrets"…}
so if somebody goes and creates a new secret, if backbone every wants to get me all the things on the server
backbone can just run.fetch again
when i call fetch it goes and gets the updated list
the next thing we want to do is create a view
create AppView.js in the js views folder
in index.html.erb
<h1>Tell me all your secrets</h1>
<div id="main"></div>
<script type="text/x-handelbars-template" id="appView">
<div id="new-secret"></div>
<div id="secrets"></div>
</script>
so i will know that this view is working when these two divs appear in the main
the job of the render function is to take this script tag, get the content, and stick it in here
so add the following two lines to the render function
var whisper = whisper || {};
whisper.AppView = Backbone.View.extend({
el: '#main',
render: function(){
// get some html from the dom
var html = $('#appView').html();
// shove it into the element associated with this view
this.$el.html(html);
}
});
to get this showing on the page we create router.js in the js router folder
var whisper = whisper || {};
whisper.Router = Backbone.Router.extend({
routes: {
// as soon as i load the page, look at the index, and run the index function
'': 'index'
},
index: function(){
var appView = new whisper.AppView();
appView.render();
}
});
now we need to create our actual router, and start the hisotry
in whisper.js
$(document).ready(function(){
// create new instance of the class
whisper.router = new whisper.Router();
Backbone.history.start();
});
create SecretsView.js
var whisper = whisper || {};
whisper.SecretsView = Backbone.View.extend({
el: '#secrets',
render: function(){
console.log('rendering the secrets');
}
});
go back to the AppView.js and add this:
var secretsView = new whiseper.SecretsView();
secretsView.render();
but this still won't work because we havent fetched the secrets at any point
so in whisper.js,
lets make the first thing we do get the secrets
so in the whisper.js file
$(document).ready(function(){
whisper.secrets = new whisper.Secrets();
whisper.secrets.fetch().done(function(){
// create new instance of the class
whisper.router = new whisper.Router();
Backbone.history.start();
});
});
in AppView.js we want to pass in our secrets collection to the new secretsView
var secretsView = new whisper.SecretsView({collection: whisper.secrets});
now in SecretsView.js we can render the collection
var whisper = whisper || {};
whisper.SecretsView = Backbone.View.extend({
el: '#secrets',
render: function(){
console.log('rendering the secrets');
// #the collection is the collection that we passed in in appview.js
var view = this
this.collection.each(function(secret){
console.log('secret', secret);
var $li = $('<li>').text(secret.get('content'));
view.$el.prepend($li);
})
}
});
the next thing we want to do is have the form with the create new secret button
In our index.html.erb file we adda new template for a new secret
<script type="text/x-handelbars-template" id="newSecretTemplate">
<form>
<textarea placeholder="Confess your secret..."></textarea>
<button>Shh...</button>
</form>
</script>
so now in the js views folder, we want a new file called newSecretView.js
var whisper = whisper || {};
whisper.NewSecretView = Backbone.View.extend({
el:'#new-secret',
render: function(){
// make the view available
var html = $('#newSecretTemplate').html();
this.$el.html(html);
}
})
add this to appview.js
// within the app view render the new secret form
var newSecretView = new whisper.NewSecretView();
newSecretView.render();
now we need to handle the actual form submission
this.$('textarea') will get me the textarea within this view
this.$('textarea').val() will get me its value
so i can use this to create a new secret
in NewSecretView.js:
var whisper = whisper || {};
whisper.NewSecretView = Backbone.View.extend({
el:'#new-secret',
events: {
'submit form': 'createNewSecret'
},
render: function(){
// make the view available
var html = $('#newSecretTemplate').html();
this.$el.html(html);
},
createNewSecret: function(event){
event.preventDefault();
var userContent = this.$('textarea').val();
var secret = new whisper.Secret({content: userContent})
secret.save();
}
})
now i can create a new secret, and reload the page, and it will appear
but i dont want to have to reload the page
to fix this i can add the following three lines to the NewSecretsView.js
// makes the secret show on page without refresh
whisper.secrets.add(secret);
var secretsView = new whisper.SecretsView({collection: whisper.secrets});
secretsView.render();
Note: we added this.$el.empty(); to the secretsView so the list clears each time a new li is added
Note: we also added this.$('textarea').val(''); to the NewSecretsView so the form clears after submission
At this point Joel got https://ngrok.com/
This is so he could make a port available to everyone in the class
The final problem to fix is polling of the secrets
If we add a secret, Joel can't see it without refreshing the page.
to do this, we make an initialize function in secrets.js
and we moved the rendering of the secrets collection from AppView
secrets.js changes:
var whisper = whisper || {};
whisper.Secrets = Backbone.Collection.extend({
url: '/secrets',
model: whisper.Secret,
initialize:function(){
this.on('sync', function(){
console.log('sync successful');
// within the app view render the secrets collection
var secretsView = new whisper.SecretsView({collection: whisper.secrets});
secretsView.render();
});
var secrets = this
secrets.fetch();
setInterval(function(){
secrets.fetch();
}, 3000);
}
});
we also made the following changes to whisper.js
$(document).ready(function(){
// create new instance of the class
whisper.router = new whisper.Router();
Backbone.history.start();
whisper.secrets = new whisper.Secrets();
});
In NewSecretView.js we replaced these lines:
// secret.save();
// makes the secret show on page without refresh
// whisper.secrets.add(secret);
with these lines:
secret.save();
whisper.secrets.add(secret);
We also added the ability to stop polling in the newSecretView file.
##Atlassian visit
##YSlow
minimise bandwidth
small file sizes
reduce round trips
You can run this on any website, and it will give you pointers as to how to make it faster
Google's page speed insights does something similar