Code Academy Week 8 Notes

2012-02-28_08.18.14
undefined method `model name` for NilClass:Class
Means there is a problem with the model
So instantiate it in the controller from the session

How to override application.html.erb for a controller or action

For orders controller, app/views/layouts/order.html.erb
To specify layout:
in controller:

layout 'application', :only => :index

or in def new:

def new
   @cart = Cart.find(session[:cart_id])
   @order = Order.new
   render :layout => 'orders'
 end

we need to encrypt the credit card number. Look in config/application.rb
We will look at
config.filter_parameters +=[:password]
This will filter password out of the log
So we can add to the array
config.filter_parameters +=[:password, :card_number]

asset pipeline: We will talk about it throughout the week. Look in the assets folder. Images, javascript and stylesheets.
It takes all the javascript and puts it in one file, so the browser only has to make one request
Same for css
For css, you should go to application.css and specify files and/or directories

*= require_self
 *= require_tree

require_tree will do everything aplhabetically. You can put them in your own order.

*= require brands
 *= require products
 *= require reviews
 *= require scaffolds
 *= require users

In development mode, there is no asset pipeline. You use it in production.

In config/environments/development.rb

config.assets.debug=false

if you set that to false, you won’t see the GET requests in the logs for EVERY js and css file

Jeff can get to other order pages as well as his own
We have authentication. What about authorization?
In orders controller:

def show
   @order = @user.orders.find(params[:id])
 end

WHat if that ID is not in the order table?

def show
   @order = @user.orders.find(params[:id])
   if order.nil?
     redirect_to root_url, :notice => "Nice try"
   end
 end

You could do this:

redirect_to root_url, :notice => "Nice try" if order.nil?

But then it looks like redirect is the default.
Or

redirect_to root_url, :notice => "Nice try" unless order

Moving this to a one-liner could make it a but confusing
You could also do

if order.blank?
   redirect_to root_url, :notice => "Nice try"
 end

or

unless @order
   redirect_to root_url, :notice => "Nice try"
 end

Stay at same level of abstraction throughout the html.erb files
if you have render tags with low-level html, that could look bad

Class method that makes an ActiveRecord call
Use the scope facility
Instead of:

def self.most_recent
   order("updated_at desc").limit(3)
 end

 

scope :most_recent, order("updated_at desc").limit(3)

scope creates a class-level method, and returns rows
You cannot just return one row

Maybe we want to pass a param from the view to the class-level method or scope

Easy for method.
For a scope:

create a lambda: create an unnamed method on-the-fly
 scope :most_recent, lambda { |n| order("updated at desc").limit(n) }

Orders Controller:
We have a before_filter
We have model callbacks in cart_item.rb
Those are sort of like lambdas as well.
We do not call it, Rails calls it for us at some point.

Let’s do a scope on price for products:

scope :expensive, where('price > 50')

Then in the view you could chain the scopes

Lambda with a default value for min:

scope :expensive, lambda { |min=80| where('price > #{min}') }

Getting the data you want:

.find
 .find_by_*
 .where(hash)
 .where("SQL")
 .where("SQL", ....)
.where("color = BLUE''") # sql syntax - single = sign is comparison
 .where("color like 'BLUE'")

You can also use % for wildcards

.where("name like 'M%'")
 .where("name like ?", "M%")

In Products Controller:

@products = Product.where("name like '#{params[:search]}'")

or

@products = Product.where("name like '#{params[:search]}%'")

So why not do this? Jeff will explain
You could get SQL injection
So do this:

a = params[:search]
 @products = Product.where("name like '%#a%'")

Use the question mark placeholder

@products = Product.where("name like ?", params[:search])

ActiveRecord will create the SQL for you
Fuzzy search:

@products = Product.where("name like ?", "%#{params[:search]}%")

Then in the controller you could chain the methods

@products = @products.limit(1000).order('name asc')

It is not until you get to the @products.each (or a .all) in the view that you run the query

Should you use the products controller for searching, or make a search controller?
the form_tag goes to ‘/products’ with a get, which will go to the index action
—-
New app:
User Story Analysis
1. Identify resources
2. Create Models
3. Implement business rules
4. Create User Interface

Some people go in the opposite order

Air Academy User story
1. As a visitor, list the flights

In Rails 3.2, when you generate model, if you do not specify the types it will assume that it is a string
rails g model Airport code:string city:string
ericm@finance:~/ruby/ca_files/air$ more db/migrate/20120228174012_create_airports.rb

class CreateAirports < ActiveRecord::Migration
  def change
    create_table :airports do |t|
      t.string :code
      t.string :city

      t.timestamps
    end
    add_index :airports, :code
  end
end
rails g model Airport code:string city:string

emacsnw db/migrate/20120228174012_create_airports.rb

For flights, we need two airports, not just one departure_airport_id, arrival_airport_id

class Flight
  belongs_to :departure_airport, :class_name => 'Airport'
  belongs_to :arrivale_airport, :class_name => 'Airport'
end

If you say belongs_to :blah, the table needs a column blah_id
If that is the only belongs_to, Rails will assume there is a Blah model

rails g model Flight departure_airport_id:integer, arrival_airport_id:integer number:string distance:integer duration:integer departs_at:time

2012-03-01_08.38.49
Pagination:
Kaminari, Will_paginate, roll your own

User story: As a user, I want to make a reservation
As opposed to doing something “as a visitor”
Different permissions, etc
So for “As a user” that user story needs an account
User story analysis
1. Identify resources
2. Create models
3. Implement business rules
4. Create user interface

reservation model has user, a flight, chosen departure date, credit card number
Associations:
user can have many reservations
flight has many reservations
reservation belongs to flight and belongs to user

rails g scaffold Reservation user_id:integer flight_id:integer credit_card_number:string departs_on:date

departs_on for dates, departs_at for times

How to decide to model or scaffold? How much of scaffold will you use?

add_index on the reservation table
add_index is outside the create_table block

add_index :reservations, :user_id
 add_index :reservations, :flight_id

Good to have indexes for foreign keys
Then run

rake db:migrate

rake db:migrate:reset will drop database and recreate tables
rake db:seed
You could do

rake db:migrate:reset db:seed
rails g model User first last email passwod_digest

——————
Now JC from DevMynd is talking
database: structured set of data on a computer, accessible in various ways
five categories: relational, graph, key-value, document, column family databases
Relational database: built around relational theory (first-order predicate calculus), schema defined in tables of columns and rows, data is related through matching keys
Graph: based on graph theory. These DBs use nodes, properties and edges to describe a “web” of data. They can be powerful for complex ancestral queries (Amazon: People who bought X bought Y)
Key-value: primary way of retrieving objects is by a single key. What can be stored as a value varies.
Redis is used for queing systems
Document database: storing document-oriented, semi-structured data. Normally within a loose schema and the ability to store/retrieve nested structures
Column-family database: inverse of relational model. Big table.
Choosing data store can be pretty complex.
Considerations: indexing, querying, scaling, modeling, mapping, analyzing, recovering
Indexes: a parallel description of your data optimized for fast lookup
If you will ever do an order_by or a conditional query
Finding by department and last name is different than index that finds by last name and first name
Querying: getting info from db. Do we use a template object, a query language, or a string key?
Mongo can use query syntax, or you can pass in a template doc
scaling: scale up (faster, bigger hardware) or out (distributing data, indices and queries across more machines, aggregating the results)
mongo can shard well, so can redis
modeling: is your schema fixed or flexible? a few large things, lots of small things
Mapping (ORM) – ActiveRecord for RDBMS, Mongoid for MongoDB, Redis ruby driver
how will you interact with db?
analysis: what does the database provide?
recovery: relational: replication, push log to another system
Mongo: backup and restore tools
Heroku, other cloud backups are not that great

optimization: this is usually the first bottleneck
universal: careful indexing, duplication
relational: de-normalization, pivoting, materlialized views
normalized: each concept in a different table, less repetition
pivot: making rows into columns, and vice versa
materialized views:
non-relational: nest relationships, parallel queries
downside of indexing: it can take a LOT of space, plus index might be held in memory instead of disk
Put query logic in your model, not your controller
So if you change the datastore, you can just change it in the model class
relational tables good for complex joins
Think carefully about uniqueness and nullability early on

——————
Back to Jeff
For flight page, give some info about flight
the user is part of the session
reservation for
So in reservation controller, in new method: @user = User.find(session[:user_id])
You could do it from show page for a flight
@user = User.find()
Get stuff from flight show page
Reuse the flight_details partial

But we could get a Nil
So in the reservations controller
We will do something in def new
We could have a link from flight show page
@flight.id) %>
So in new,

@flight = Flight.find(params[:flight_id])

So we want to put that link in an if on the flight show page
Put a filter on the reservations new controller

before_filter :require_login, :only => :new
   def require_login
   if session[:user_id].blank?
     redirect_to root_url, notice: "Get your act together"
   end
 end

@flight.id) %>

In reservation form:

in reservation controller.new:

@reservation.flight = @flight

validation on reservations:

create method in reservationcontroller:

def create
   @reservation = Reservation.new()
   @reservation.user = User.find(session[:user_id])
 end

Sending email in Rails – it could depend on your host

rails g mailer ReservationMailer
 rails g mailer ReservationMailer

In our reservation controller, we will use this in respond_to block

email = ReservationMailer.confirmation(@reservation)
 email.deliver

in ReservationMailer

def confirmation(reservation)
   @reservation = reservation
   mail(:subject => "Thanks for your reservation",
   :from => "reservations@academyair.com",
   :to => reservation.user.email )
 end

So in app/views/reservation_mailer/confirmation.text.erb
Hello, ,
Thanks for sending us $300.

Flight details
———————
Flight number:

Image from World Digital Library, assumed allowed under Fair Use. Image from the Ashburnham Pentateuch, or Tours Pentateuch, a Latin manuscript of the first five books of the Old Testament from the 6th century or 7th century. Its place of origin is unknown.

There are 1822 words in this article.