Code Academy Week 6 Notes

2012-02-14_08.04.44
product has many reviews
There is a proxy for the data – it is a subclass of Array

@review = Review.new
@review.product = Product.find(params[:product_id])

What is there is no product with that ID?
The “find” method will raise an exception (actually params[:product_id] is nil)
How to make it more forgiving

if params[:product_id].present?
    @review.product = Product.find_by_id(params[:product_id])
end

the find_by_* methods (aka dynamic finders) are more forgiving
You could even get rid of if

How to hide the product pull down in the review form?
Hidden form field
In _form.html.erb for review:

<% if @review.product.present? %>
    <%= f.hidden_field :product_id %>
<% else %>
<div>
    <%= f.collection_select :product_id, Product.all, :id, :name %>
</div>
<% end %>

Why do we need an equal sign for if it’s a hidden field?

<%= f.hidden_field :product_id %>

It still needs to be emitted to the HTML
In Rails, we have 2 methods: present? and blank?
blank? is like nil?
if a string is ”  “, that is not nil, but it is blank
present? is the opposite of blank?
Why have both? Just negate one of them.
!a.blank? is “not the Ruby way” according to Jeff

[1,2,4].map{ |n| n*2 } is just like [1,2,4].collect{ |n| n*2 }

Matz does not like minimal interface. Sometimes map will feel right, and sometimes collect will.

[1,3,5].each do |n|
    puts n
end

map is an iterator, like each

w = [1,3,5].map do |n|
    n * 2
end

map wants a return value from each iteration of the block

<% if @review.product.present? %>
could also be
<% if @review.product %>

show.html.erb for reviews has this at the top:

<%= notice %>

This method will emit what ever is in the :notice key in the redirect from your controller
This will only live long enough to be redirected
This is called a flash message in Rails
it is a hash
the “flash hash”
You could also set the notice just before the redirect_to

flash[:notice] = "Way to go!"

You could make it flash[:zebra]
and call

<%= flash[:zebra] %>

in the html.erb file

http is stateless, so each request has to provide all the information the next request needs
Putting the query string in the URL has been one way to keep information
User story:
as a user, I want to add several products into a shopping cart
Jeff does it:
Carts and Products
This is many to many
But many carts will want to have products
Make a join model in between
CartItem
Cart has_many :cartItems
cart_item belongs_to a single :cart
A product will have many cart items
a cart item will belong_to a product
cart_itme will have cart_id and product_id

rails generate model Cart name:string
rails generate model CartItem cart_id:integer product_id:integer quantity:integer
rake db:migrate
class Cart
    has_many :cart_items
end
class CartItem
    belongs_to :cart
    belongs_to :product
end

add has_many :cart_item to product
The file name is Pascal case. In code or console, you use underscores

Jeff says the modelling is the hard part of Rails
In the console, you can do this:

cart = Cart.new
cart.cart_items
item = CartItem.new

in console, this will reload items from database
cart.cart_items(true)
or: cart.cart_items.create :product => Product.last
cart.cart_items.build will make a new cart in memory only.
cart.cart_items.new does not exist on the proxy

Add item to a cart. How to add another item to same cart?
Use cookies
Session data
Each has a name and a value
name/value pair, like a hash in Ruby
Expiration is optional – session by default
cookie data lives in the browser
How to add cookies:

session[:name] = value

Each cookie is limited to 4K of data
Do not put an ActiveRecord object into a cookie
Cookies are stored on the client machine
deal with session hash in your controllers
It is defined in ApplicationController’s parent
session[:cart_id] = cart.id
The session data is encrypted
In the create method for Cart controller:

if session[:cart_id]
    cart = Cart.find(session[:cart_id])
else
    cart = Cart.create
end

Put the cart stuff in a partial that everything can see.
app/views/share/_cart.html.erb

<%= render 'shared/cart >

Instead of accessing session hash inside view, make a helper method
Use view helpers

Seed file
Pagination
Seed file will help you pull in test data

Vince did pagination

Checkout: cart becomes an order
Cart partial:

<%= button_to "Check out", orders_url %>

In routes:
resources for :orders
Create an orders controller

def create
    # do stuff
    # now clear session data
    reset_session
    redirect_to products_url, :notice => "Thanks for your order"
end

Look at guides for sessions and cookies

2012-02-16_08.20.30
In grandma example, they used a form_tag, not a form_for since they did not use a model.
form_tag(“/ask”, :method => “get”) do
put form tags here

routes:
get ‘ask’ => ‘grandmas#ask’

What we started:

input = ""
output = ""
puts "Talk to grandma"
while input != "BYE"
    input gets.chomp
    puts "What?"
    if input == input.uppercase
        puts "No, not since #{rand(1930..1950)}"
    end
end

Jeff recommends irb or rails console

Two methods:
a.upcase will check if the string contained in a is uppercase
a.upcase! will change the string to uppercase

Some people used regular expressions

Now going back to the shopping cart.
New models: Order, OrderItem

reset_session will reset the whole session
how to just get rid of cart?

session[:cart_id] = nil

Cycle through the cart creating order items, and call order.save afterwards. Fewer transactions.

New app for users

rails g model Item name:string value:integer
rails g model User name:string email:string password:string

Do not store passwords as strings. We will go over that later.

You could put this in the seeds.rb:

Item.create :name => "Hockey Stick", :value => 100

etc, etc, etc,

rake db:seed

To wipe out existing items in database, you could add this at the top of seeds.rb
Item.destroy_all
He added users and items to routes with resources

rails g controller Items new create show
rails g controller users new create show

Signup is easy: Just add user to database
Signin and signout are session management
If they are in database, when they sign in just make a session cookie

We create a sessions controller, inherits from application controller

SessionsController
    def new
    end

    def create
    user = User.find_by_name(params[:name])
        if user.present?
            if user.password == params[:password]
                session[:user_id] = user.id
                redirect_to items_url, :notice => "hello"
            else
                redirect_to items_url, :notice => "Nope"
            end
        else
            redirect_to items_url, :notice => "Nope"
        end
    end

    def destroy
        # reset_session
        # Or
        session[:user_id] = nil
        redirect_to items_url
    end
end

There will also be a folder for sessions, with a new.html.erb
We use form_tag instead of form_for since we have no model

<%= form_tag "/sessions" %> (or sessions_url)
    <% label_tag :name  %> <%= text_field :name  %>
    <% label_tag :password  %> <%= text_field :password %>
    <% submit_tag 'Sign In', new_session_url %>
<% end %>

Now in app layout:
if session[:user_id].present?
Hello, User.find(session[:user_id]).name

In routes:
post “/logout” => ‘sessions#destroy’, :as => :logout
link_to “Sign Out”,
button_to will create a post

Make the password a password_field_tag
To do password confirmation:
add a text field in form: password_confirmation, which you can do even though we are in a form_for
Then in model for User:
validates :password, :confirmation => true

For password security:
add “bcrypt-ruby” to Gemfile, run “bundle install”
To user, add a column called “password_digest” as a string
add line “has_secure_password” to the model

rails g scaffold User name:string email:string favorite_color:string password_digest:string
rake db:migrate

If you make a mistake,

rake db:migrate rollback STEP=1

Put line “has_secure_password” into the model
For user form partial:
Remove the password digest field from the form
Put in password and password_confirmation

Create an account and sign in at the same time
In users controller in create

if @user.save
    session[:user_id] = @user.id
end

So how do people sign in? We don’t want them to know their digest
In console:

u = User.find_by_name("Joe")
u.authenticate("hockey")

That should work
It will encrypt the input, it will not decrypt the string in the database

Image from Wikimedia, assumed allowed under Fair Use. Image from the Ambrosian Iliad, a 5th century manuscript of the Iliad.

1 thought on “Code Academy Week 6 Notes”

  1. I figured that the more I play the piano, the smarter I get. You might want to add it to your post

Comments are closed.