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.