Code Academy Week 5 Notes

2012-02-07_08.32.40
He went over SQL in Ruby/Rails
Team has many players, Player belongs to Team

User story:
As a user, I want to see a list of products I can buy
A product has a name, color, and price. A product belongs to a single brand.
Prices are always in exact dollar amounts (no cents).
As a user, I want to add a review to a product
A review has a rating from 1-5 and a brief description

new rails app
products and brands
products belong to brand, brand has many products
Generate scaffold for brand and product
go back to index after create

Go over the scaffold
To get the brand in the collection select
Go to the form partial for the product
Only change the number field, not the label field
brand_id is the foreign key

<%= f.collection_select :brand_id, Brand.all, :id, :name %>

:id is param for Brand.all, :name is the field in the brand. That is what will be displayed
You may not want to always do Brand.all. We will go over that later.
View should not decide what data to show.
Put a variable in the new method in the controller

Now listing the products, we still get the brand ID

Make the description for a form a text field, not string

f.collection_select :product_id, Product.all, :id, :name

For rating, we want a list of  to 5

f.select :rating, [1,2,3,4,5]

You should still add a validates method in the model
What if you want 1 to 100?
There is a way to do a range

:rating, (1..100).to_a

to_a converts it to an array
Make the description box smaller. Go to the form partial
:rows => 5

Gems:
You can have more than one version

gem install rails

You may see:

sudo gem install rails

That will install it with admin rights
With rvm: never use sudo
It will install latest by default
gem will handle dependencies
railties is the guts/engines of rails
You can call
gem sources
to see where it will look for gems

Vince’s app:
We could edit the Gemfile

gem 'gmaps4rails'

Then run

bundle install

Back to Jeff
How to get words to appear on pull-down
Numbers:

f.select:rating, (1..5).to_a

Or

f.select:rating, [1,2,3,4,5]

or

f.select :rating, {"Excellent" => 5, "Very Good" => 4, "Okay" => 3, "Not so good" => 2, "Bad" => 1}

To validate that the rating will be between 1 and 5
Jeff starts with the guides
Jeff used

validates :rating, :numericality => { :only_integer => true }
validates :rating, :numericality => { :only_integer => true, :greater_than_or_equal_to => 1, :less_than_or_equal_to => 5 }

Test in the Rails console

2012-02-09_08.25.05
To support 1 URL: pages/social
One way:

rails g controller pages social

rails new friendBC
cd friendBC

-> Memorize the routes for resources
in routes.rb
get “pages/social”
in app/controllers add a controller: PagesController < ApplicationController
add a method called “social”
add app/views/pages/social.html.erb
get an image, put it in

<% image_tag "logo.png" %>

in assets/global directory
Make the background grey
app/assets/stylesheets/application.css

How to get JSON data?
He put some stuff in his controller

require 'open-uri' and require 'json'
result = JSON.parse(open("Some URL").read)

Put result in view file
In hashes, symbols and strings are not interchangeable. Usually they are.
To get the image:
in the view:

<%= image_tag @first_result["video"]["thumbnailUrl"] =>
 <img src="@first_result["video"]["thumbnailUrl"]">

Now he is calling @result @channel

Now Jeff is going through it
Agile practices: single responsibility, clear intention, DRY
These are the top 3
Rails cries out to help you do those things
When you download someone else’s code, you may need to run “bundle install”
Jeff moved some of the HTML onto a partial
Then change the variable in partial to one called “channel”
Then in the view call

<% render 'ribbon', :channel => @Whatever %>

In the partial, do not use raw “a” and “img” tags

<% link_to "Video goes here", "http://www.youtube.com#{post["video]["hostId"]}" %>

Now add the image:

<% link_to image_tag(post["video]["thunmbnailUrl"], :size => "240x180"), "http://www.youtube.com#{post["video]["hostId"]}", :target => :blank %>

Now back in view, we call the render three times. What if we pull another channel, we will have to add another line.
So in controller, create an array

@channels = [@facebook, @twitter, @youtube]

In the view:

<% @channels.each do |channel_data| %>
    <%= render 'ribbon', :channel => channel_data %>
<% end %>

Now they do not need to be instance variables in the controller
We still have repetition

def get_json_for_source(source)
    return JSON.parse((open("https://")).read)
end

Then in other method:

facebook = get_json_for_source('FACEBOOK')

This is Ruby skillz

A good pattern to know:
An array of strings. You are transforming the array into another array.

@channels = ['a', 'b', 'c'].map do |source|
    get_json_for_source(source)
end

map is like each
Look it up
collect is another good one to look up.

@names = users.collect dp |user|
    user.name
end

Could be

@names = users.collect {|user| user.name}

We saw one-line blocks in scaffolds in index

respond_to do |format|
    format.json {render json: @brands}
end
format.json do
    render json: @brands
end

Look in the dev/tth/shop folder
—————————————————–
Now: git and github
Look up “git – the simple guide”

git init

makes a new repository

git clone /path/to

to checkout a repository
Workflow:
working dir has files, index to stage commits, the head is the last commit
adding and committing

git add <filename>
 git add *
 git commit -m "message"

Now it is in the head, but not remote

git push origin master

afterwards, you can just do git push
To add a branch

git remote add origin <server>
 git checkout -b feature_x
 git pull to update - that pulls down latest changes

to reset:

git fetch origin; git reset --hard origin/master

gitk – is a good GUI
Neal making live changes

git status

He is on master branch

git branch

git status now tells him he made a change

git add .
 git commit -am "updated the Readme"
 git push origin master

or just

git push

There is also heroku

git push heroku master

or

git push heroku

Heroku requires postgres

Neal makes a new app

rails new ca_boat_party

It would be nice to stop an existing rails server
You could also specify a port

rails s -p 4000

It is now live on our local machine

git init

go to github.com
make a new repository

ca_boat_party - same name as directory
 git add .
 git commit -am ""

No good

git remote add origin git@github.com:nealg223/ca_boat_party
 git push -u origin master

Now put it up to heroku

gem install heroku

ssh keys are a pain

heroku create ca-boat-party --stack cedar

They have different stacks. bamboo is the current stack, good for rails up to 3.0. For rails 3.1 use cedar
Now push it to heroku
Update the gem file Gemfile to add postgres

group :production do
     gem 'pg'
 end

Put sqlite3 in group :development
bundle install
now commit changes to git

git add
 git commit -am "changed Gemfile"
 git push

Now push to heroku

git push heroku master

another command: heroku open
assets, like images, there could be some problems
another command is heroku logs
or

  heroku run console

it’s like an irb for heroku

heroku run rake db:migrate

in file app/config/environments/production.rb

set config.assets.compile = true

You can go to gitref.org
————————————————————–
Working with Shop app:
Get the average of reviews for a product

p = Product.find(1)
 p.reviews

Calling product.reviews: It gets rows from our table. It looks like an array, and behave like an array, but it can do more than what an array can do. It returns a proxy class. It is a proxy for the data you want.
So you can say p.reviews.count – it makes an SQL statement
You can do a lot of array stuff: p.reviews.first, p.reviews.sum(“rating”)
Arrays do not have sum method, but ActiveRecord does have sum method
p.reviews.average(“rating”) it gives us a BigDecimal instance
p.reviews.average(“rating”).to_s

Another association concept
A brand has many products.
A product has many reviews.
A brand has many products throught its products.

class Brand < ActiveRecord::Base
     has_many :products
     has_many :reviews, :through => :products
 end

The lines must be in that order
Review does not have brand id, but product has brand id, and review has product ID
So you can just do Brand.reviews without looping
Users want to see average rating for brand
So in brand show page – put it there for each brand
So to brand index page:

<%= brand.name %>

Rating:

<%= brand.reviews.average(:rating) %>

For reviewing the product form, it would be great if it knew which product you were using
No params hash for /reviews/new
Put a placeholder in the route
Put it in URL via ?key=value
Now you have a params hash
In product/show.html.erb

<%= link_to "Review This Product", new_review_url(:product_id => @product.id) %>

So now you see ?product_id=1 in url for new form
so in controller, look at new action in review controller

def new
     @review = Review.new
     @review.product_id = params[:product_id]
     # or @review.product = Product.find(params[:product_id])
     respond_to
 end

so in reviews/new.html.erb
New review for

<%= @review.product.name %>

Next: Leave out the pull-down form. But get product ID
replace collection_select with a hidden field

Image from Wikimedia, assumed allowed under Fair Use. Image from the Vatican Virgil, a 5th century manuscript of poems by Virgil.