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.
I figured that the more I play the piano, the smarter I get. You might want to add it to your post