Code Academy Week 3 Notes Post 01

2012-01-24_08.10.45

This week:
Arrays and blocks
MVC Recipe
CRUD your data
Rails resources
Model Associations

2 skills building: RoR coding and how to think about architecture of application

Arrays and blocks:
Arrays and collections

5.times do |n|
     puts "The Edens is ridiculous #{n + 1}"
 end

The times method will let you increment a block variable

Mate: command-control-shift-E will execute code

names = ["Marcel", "Chris", "Fabian", "Adam", "Jimmy"]

class Person
    attr_accessor :name
    # attr_accessor :favorite_color
    def favorite_color=(a_color)
        @favorite_color = a_color
    end
    def favorite_color
        return @favorite_color
    end
end

x = Person.new
x.name = "Marcel"
x.favorite_color = "Blue"
y = Person.new
y.name = "Chris"
y.favorite_color = "Red"
people = [x,y]

people.each do |person|
    puts person.name
    puts person.favorite_color
    puts "----"
end

# you could do names.length or name.size
names.each do |n|
    puts "#{n}"
end

 

Better way: Use .each method

MVC Recipe file is on Backpack – look it up and save it

Simple page demo:
display a list of train stations
1. Route definition
2. Controller class
3. Action method

1. Define a route that connects a url path to a controller and an action

MyApp::Application.routes.draw do
     get ‘stations’ => ‘stations#index’
 end

2. Write a controller class

# app/controllers/stations_controller.rb
class StationsController < ApplicationController

end

Follow conventions: Class is StationsController and file is stations_controller
3. Add an action method that generates your HTML

def index
     render :text => “<h1>Station List</h1>”
 end

Do not give your app the same name as one of your controllers
render method in the controller can take a file name, or :text => “Some text”

render :text => “<h1>I am awesome</h1>”

In response, you can give some HTML or redirect the user

redirect_to method “http://en.wikipedia.org/main/Illinois”
redirect_to “http://en.wikipedia.org/main/Illinois”

redirect will give a 302 http status code
In controller, either render some HTML or redirect

class StationsController < ApplicationController
     def index
         render :text => "<html>
        <body>
        <h1>Station List</h1>
         <ul>
         <li>Chicago</li>
         <li>Belmont</li>
        </ul>
         </body>
        </html>"
         # redirect_to "http://en.wikipedia.org/wiki/Illinois"
     end
end

 

But we could have nothing in the controller’s index method. Or we could put something in the views folder:
apps/views/$NAME_OF_CONTROLLER/$NAME_OF_ACTION.html

added an array @stations in the stations controller
Put this in the index.html.erb file:

<% @stations.each do |station|  %>
     <p>Name: <%= station %></p>
 <% end %>

You do not need to make an attr_accessor line in a controller to see it in the corresponding view. You can just use those variables in the view.

rails generate GENERATOR [args] [options]
 Usage: rails generate GENERATOR [args] [options]
General options:
 -h, [--help]     # Print generator's options and usage
 -p, [--pretend]  # Run but do not make any changes
 -f, [--force]    # Overwrite files that already exist
 -s, [--skip]     # Skip files that already exist
 -q, [--quiet]    # Suppress status output
Please choose a generator below.
Rails:
 assets
 controller
 generator
 helper
 integration_test
 mailer
 migration
 model
 observer
 performance_test
 plugin
 resource
 scaffold
 scaffold_controller
 session_migration
Coffee:
 coffee:assets
Jquery:
 jquery:install
Js:
 js:assets

 

For a model:

ca-imac1-2: ~/dev/tth/TrainStationApp$ rails generate model
Usage:
 rails generate model NAME [field:type field:type] [options]

Options:
     [--skip-namespace]  # Skip namespace (affects only isolated applications)
     [--old-style-hash]  # Force using old style hash (:foo => 'bar') on Ruby >= 1.9
 -o, --orm=NAME          # Orm to be invoked
                         # Default: active_record

ActiveRecord options:
     [--migration]            # Indicates when to generate migration
                              # Default: true
     [--timestamps]           # Indicates when to generate timestamps
                              # Default: true
     [--parent=PARENT]        # The parent class for the generated model
     [--indexes]              # Add indexes for references and belongs_to columns
                              # Default: true
 -t, [--test-framework=NAME]  # Test framework to be invoked
                              # Default: test_unit

TestUnit options:
     [--fixture]                   # Indicates when to generate fixture
                                   # Default: true
 -r, [--fixture-replacement=NAME]  # Fixture replacement to be invoked

Runtime options:
 -f, [--force]    # Overwrite files that already exist
 -p, [--pretend]  # Run but do not make any changes
 -q, [--quiet]    # Supress status output
 -s, [--skip]     # Skip files that already exist

Description:
   Create rails files for model generator.

Upgrade rails:
gem install rails --no-doc --no-ri
ca-imac1-2: ~/dev/tth/TrainStationApp$ gem install rails --no-rdoc --no-ri
Fetching: activesupport-3.2.0.gem (100%)
Fetching: activemodel-3.2.0.gem (100%)
Fetching: rack-1.4.1.gem (100%)
Fetching: journey-1.0.0.gem (100%)
Fetching: sprockets-2.1.2.gem (100%)
Fetching: actionpack-3.2.0.gem (100%)
Fetching: arel-3.0.0.gem (100%)
Fetching: activerecord-3.2.0.gem (100%)
Fetching: activeresource-3.2.0.gem (100%)
Fetching: mail-2.4.1.gem (100%)
Fetching: actionmailer-3.2.0.gem (100%)
Fetching: railties-3.2.0.gem (100%)
Fetching: rails-3.2.0.gem (100%)
Successfully installed activesupport-3.2.0
Successfully installed activemodel-3.2.0
Successfully installed rack-1.4.1
Successfully installed journey-1.0.0
Successfully installed sprockets-2.1.2
Successfully installed actionpack-3.2.0
Successfully installed arel-3.0.0
Successfully installed activerecord-3.2.0
Successfully installed activeresource-3.2.0
Successfully installed mail-2.4.1
Successfully installed actionmailer-3.2.0
Successfully installed railties-3.2.0
Successfully installed rails-3.2.0
13 gems installed

That was if you are using rvm
If not, you might need to use sudo

Models are singular, controllers are plural

ca-imac1-2: ~/dev/tth/TrainStationApp$ rails generate model station name:string address:string style:string
 invoke  active_record
 create    db/migrate/20120124163015_create_stations.rb
 create    app/models/station.rb
 invoke    test_unit
 create      test/unit/station_test.rb
 create      test/fixtures/stations.yml

Look at the migration file

class CreateStations < ActiveRecord::Migration
     def change
         create_table :stations do |t|
             t.string :name
             t.string :address
             t.string :style
             t.timestamps
         end
     end
 end

 

We can add columns before we do the migration
Migrations allow you to evolve your database. If you change your tables, add tables, etc

In model class, you start with a line like this:

class Station < ActiveRecord::Base

It will give you attr_accessor methods for the fields. Do not put them in.

To do the migration, do this:

rake db:migrate

To get  the version of the database.

rake db:version

To delete database:

rake db:drop

To drop database and start over fresh

rake db:migrate:reset

If you get errors, try “bundle exec” in front:

bundle exec rake db:migrate:reset

Some logs:

ca-imac1-2: ~/dev/tth/TrainStationApp$ rails console
 Loading development environment (Rails 3.1.3)
 1.9.3-p0 :001 > Station
 => Station(id: integer, name: string, address: string, style: string, created_at: datetime, updated_at: datetime)
 1.9.3-p0 :002 > x = Station.new
 => #<Station id: nil, name: nil, address: nil, style: nil, created_at: nil, updated_at: nil>
 1.9.3-p0 :003 > x.name='Chicago Ave'
 => "Chicago Ave"
 1.9.3-p0 :004 > x.style = 'Elevated'
 => "Elevated"
 1.9.3-p0 :005 > x.save
 SQL (4.8ms)  INSERT INTO "stations" ("address", "created_at", "name", "style", "updated_at") VALUES (?, ?, ?, ?, ?)  [["address", nil], ["created_at", Tue, 24 Jan 2012 16:44:13 UTC +00:00], ["name", "Chicago Ave"], ["style", "Elevated"], ["updated_at", Tue, 24 Jan 2012 16:44:13 UTC +00:00]]
 => true
Station.create :name => "Jackson", :style => "Subway"
 SQL (0.5ms)  INSERT INTO "stations" ("address", "created_at", "name", "style", "updated_at") VALUES (?, ?, ?, ?, ?)  [["address", nil], ["created_at", Tue, 24 Jan 2012 16:44:57 UTC +00:00], ["name", "Jackson"], ["style", "Subway"], ["updated_at", Tue, 24 Jan 2012 16:44:57 UTC +00:00]]

 

You could do Student.create!
This will throw an exception if something went wrong
We would need begin and rescue, and a rescue handler

So now in stations_controller, you can put the Station objects in a variable:

@all_stations = Stations.all

So put this in index:

<ul>
 <% @all_stations.each do |the_station|  %>
     <li>Name: <%= the_station.name %> which is <%= the_station.style %></li>
 <% end %>
 </ul>

He went over RailsGuides
Jeff prefers the Guides

Now let’s do something with one train station – the simplest way possible
Define a route that connects a URL path to a controller and action
In routes.rb:

get ‘stations/:id’ => ‘stations#show’

Add a controller action method

def show
     station_id = params[:id]
     @the_station = Station.find(station_id)
 end

You could also do Station.find_by_id(station_id)
Create an html.erb page for the action in app/views/stations

In controller: If we do find_by_id, we get a nil return value
if we do find, we get a better error message
We could handle errors in the application_controller class

In show, put in a link that goes back to the list:

<a href=”/stations”>

Or

<%=  link_to “List of stations”, “http://www.google.com” %>
 <%= link_to What_user_sees, where_link_goes %>
 <%=  link_to “List of stations”, “/stations” %>

In the list:

<ul>
<% @all_stations.each do |the_station|  %>
    <li>Name: <%= link_to the_station.name, "/stations/#{the_station.id}" %> which is <%= the_station.style %></li>
<% end %>
</ul>

Create a form so the user can enter something:
This would be the new action

<%= form_for @station do |f|  %>
     <p>
     <%= f.label :name %>
     <%= f.text_field :name %>
     </p>
 <% end %>

In form_for call, you provide the name of a model

The data we receive will come through the params hash
Recipe: route, controller, action, view

For the form:

get "stations/new" => "stations#new"

Put this one above the “stations/:id” route
In controller:

def new
     @station = Station.new
 end

Create a file app/views/stations/new.html.erb

To handle the form, put in another route

post “/stations” => “stations#create”

Create in controller:

def create
     form_data = params[:station]
     x = Station.new
     x.name = form_data[:name]
     x.save
     redirect_to "/stations"
 end

Here is the form:

<h1>Add a new station</h1>

<%= form_for @station do |form| %>
    <p> <%= form.label :name %> <%= form.text_field :name %> </p>
    <p> <%= form.label :style %> <%= form.text_field :style %> </p>
    <p> <%= form.label :address %> <%= form.text_field :address %> </p>
    <p><%=form.submit %> </p>
<% end %>

Returning JSON data

Example: Twitter

(BTW Brian Hogan)

There’s a URL you can use to get a JSON representation of someone’s twitter feed for instance.

Represents Objects in Text:

Some Common JSON Notation:

[ ] – array
{ } – hash
: – in between a key-value pair

in the index method of controller, use the respond_to method to specify what we could respond to

respond_to do |format|
     format.html # nothing else, Rails will look for template
     format.json { render :json => @all_stations }
 end

To request json from the browser:
http://localhost:3000/stations.json

also works for XML

respond_to do |format|
     format.html # nothing else, Rails will look for template
     format.xml  { render :xml => @all_stations }
     format.json { render :json => @all_stations }
 end

To request xml from the browser:
http://localhost:3000/stations.xml

To delete a station:
in show:

<p><%= link_to "Delete this station" "/stations/#{@the_station.id}", :method => :delete %></p>

So in routes:

delete "/stations/:id" => "stations#destroy"

So add a destroy method in controller

def destroy
     the_station_id = params[:id]
     @the_station = Station.find(the_station_id)
     @the_station.destroy
     redirect_to "/stations"
 end

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