70
Web Development with Ruby On Rails Pedro Cunha

Ruby on Rails at PROMPT ISEL '11

  • View
    1.670

  • Download
    6

Embed Size (px)

DESCRIPTION

An introduction to Ruby on Rails. Presentation made at PROMP, a pos-graduation on ISEL university at Portugal.

Citation preview

Page 1: Ruby on Rails at PROMPT ISEL '11

Web Development with

Ruby On Rails

Pedro Cunha

Page 2: Ruby on Rails at PROMPT ISEL '11

Ruby

Yukihiro "Matz" Matsumoto

Ruby is designed for programmer productivity and fun

Created February 1993

Page 3: Ruby on Rails at PROMPT ISEL '11

Ruby

Everything is an object

true.class # TrueClassnil.class # NilClass

Dynamic Typing

class Foo
 def initialize(x, y)
 @x = x @y = y endend

class Foo2end

Foo.new(2, Foo2.new)

Page 4: Ruby on Rails at PROMPT ISEL '11

Rubyclass Foo # Parenthesis can be omitted def method puts "Hello World" end # Default params def method2(x = 2) puts x end # Operator overload def +(x) endend

class Bar # Use ! if you change self def method! end # Use ? if you return a boolean def method? end

# Only conventionsend

Page 5: Ruby on Rails at PROMPT ISEL '11

Ruby

a = "Hello"b = "Hello"

a.equal? b # false

x = :helloy = :hello

x.equal? y # true

"hello".class # String:hello.class # Symbol

# Convention# Use string if you plan to compute text

# Use symbols if you want to define or/and set a behaviour which is not expected to change

Page 6: Ruby on Rails at PROMPT ISEL '11

Rubya = {}a[:first] = 2a[:things] = 3a[:foo] = "bar"

b = { :first => 2, :things => 3, :foo => "bar"}

b[:first] # 2

Page 7: Ruby on Rails at PROMPT ISEL '11

Ruby

x = [1,4,5,2,5,8,10]

x.sort # returns a copy of x sorted [1,2,4,5,5,8,10]x.sort! # modifies self

x.map{ |i| i + 4 } # [5,6,8,9,9,12,14]x.map! do |i| i + 4end # [5,6,8,9,9,12,14]

Page 8: Ruby on Rails at PROMPT ISEL '11

Ruby

class String def +() # override string default + operator endend

Monkey Patching

“With great power comes great responsability”Uncle Ben, Amazing Spiderman nº1

Page 9: Ruby on Rails at PROMPT ISEL '11

Ruby on Rails

Page 10: Ruby on Rails at PROMPT ISEL '11

Ruby on Rails

Created by David Heinemeir Hansson

• CEO at 37th Signals

• Personal Project, 2004

Present

• Rails 3.1

• Growing community

Page 11: Ruby on Rails at PROMPT ISEL '11

Ruby on Rails

Convention vs Configuration

MVC Architecture

REST routing

Page 12: Ruby on Rails at PROMPT ISEL '11

Convention vs Configuration

Page 13: Ruby on Rails at PROMPT ISEL '11

• Don’t Repeat Yourself

• Increased productivity through conventions. Ex.: following a pattern for foreign key columns.

• Take advantage of singular and plural word meanings

Page 14: Ruby on Rails at PROMPT ISEL '11

MVC

Page 15: Ruby on Rails at PROMPT ISEL '11

RoR

Page 16: Ruby on Rails at PROMPT ISEL '11

RESTRepresentational State Transfer

Page 17: Ruby on Rails at PROMPT ISEL '11

POST GET PUT DELETE

/posts/posts/1/posts/1/posts/1

CREATE READ UPDATE DELETE

CRUD REST ROUTES

Page 18: Ruby on Rails at PROMPT ISEL '11

# routes.rbBlog::Application.routes.draw do resources :postsend

Page 19: Ruby on Rails at PROMPT ISEL '11

Starting development

RoR

Page 20: Ruby on Rails at PROMPT ISEL '11

pcunha:prompt$ rails new Blog -d mysql

Blog /app /controllers /mailers /models /views /config database.yml /db /migrate Gemfile /public /javascripts /stylesheets

Page 21: Ruby on Rails at PROMPT ISEL '11

# config/database.ymldevelopment: adapter: sqlite3 database: db/development.sqlite3 test: adapter: sqlite3 database: db/test.sqlite3 production: adapter: sqlite3 database: db/production.sqlite3

Page 22: Ruby on Rails at PROMPT ISEL '11

development: adapter: mysql2 encoding: utf8 database: Blog_development username: root password:

test: adapter: mysql2 encoding: utf8 database: Blog_test username: root password:

production: adapter: mysql2 encoding: utf8 database: Blog_production username: root password: rails new with mysql option

Page 23: Ruby on Rails at PROMPT ISEL '11

pcunha:Blog$ rails server

=> Booting WEBrick=> Rails 3.0.7 application starting in development on http://0.0.0.0:3000=> Call with -d to detach=> Ctrl-C to shutdown server

Page 24: Ruby on Rails at PROMPT ISEL '11

localhost:3000

Page 25: Ruby on Rails at PROMPT ISEL '11

ModelDatabase Schema

Page 26: Ruby on Rails at PROMPT ISEL '11

pcunha:Blog$ rails generate scaffold Post title:string body:text

invoke active_record create db/migrate/20110715102126_create_posts.rb create app/models/post.rb invoke test_unit create test/unit/post_test.rb create test/fixtures/posts.yml invoke scaffold_controller create app/controllers/posts_controller.rb invoke erb create app/views/posts create app/views/posts/index.html.erb create app/views/posts/edit.html.erb create app/views/posts/show.html.erb create app/views/posts/new.html.erb create app/views/posts/_form.html.erb

Page 27: Ruby on Rails at PROMPT ISEL '11

# config/db/migrate/20110715102126_create_posts.rbclass CreatePosts < ActiveRecord::Migration def self.up create_table :posts do |t| t.string :title t.text :body

t.timestamps end end

def self.down drop_table :posts endend

Page 28: Ruby on Rails at PROMPT ISEL '11

pcunha:Blog$ rake db:createpcunha:Blog$ rake db:migrate== CreatePosts: migrating-- create_table(:posts) -> 0.0015s== CreatePosts: migrated (0.0018s)

Page 29: Ruby on Rails at PROMPT ISEL '11

pcunha:Blog$ rails generate model Comment body:text

invoke active_record create db/migrate/20110715103725_create_comments.rb create app/models/comment.rb invoke test_unit create test/unit/comment_test.rb create test/fixtures/comments.yml

pcunha:Blog$ rails generate migration AddPostIdToComments post_id:integer

invoke active_record create db/migrate/20110715103834_add_post_id_to_comments.rb

Page 30: Ruby on Rails at PROMPT ISEL '11

# 20110715103834_add_post_id_to_comments.rbclass AddPostIdToComments < ActiveRecord::Migration def self.up add_column :comments, :post_id, :integer end

def self.down remove_column :comments, :post_id endend

Page 31: Ruby on Rails at PROMPT ISEL '11

pcunha:Blog$ rake db:migrate== CreateComments: migrating-- create_table(:comments) -> 0.0011s== CreateComments: migrated (0.0012s)

== AddPostIdToComments: migrating-- add_column(:comments, :post_id, :integer) -> 0.0011s== AddPostIdToComments: migrated (0.0041s)

Page 32: Ruby on Rails at PROMPT ISEL '11

rake db:createrake db:migraterake db:migrate:redorake db:rollback

blog_db.schema_migrations - keeps the version number of all migrations already runned

Page 33: Ruby on Rails at PROMPT ISEL '11

Relations

Model

Page 34: Ruby on Rails at PROMPT ISEL '11

# app/models/post.rbclass Post < ActiveRecord::Base has_many :commentsend

Post.allPost.find(1).commentsComments.find(1).postPost.order(:created_at)Post.limit(5).offset(2)

# app/models/comment.rbclass Comment < ActiveRecord::Base belongs_to :postend

Page 35: Ruby on Rails at PROMPT ISEL '11

Validations

Model

Page 36: Ruby on Rails at PROMPT ISEL '11

# app/models/post.rbclass Post < ActiveRecord::Base has_many :comments validates_presence_of :title validates_format_of :title, :with => /\ASLB.*\z/ end

p = Post.newp.save # falsep.errors.full_messages # ["Title can't be blank", "Title is invalid"]

p.title = "SLB is the best"p.save # true

Page 37: Ruby on Rails at PROMPT ISEL '11

validates_presence_of :nif

validates_format_of :name

validates_acceptance_of :terms_and_conditions, :on => :create

validates_numericality_of :age, :greater_than_or_equal_to => 18

validates_uniqueness_of :model_fk_key, :scope => :model_fk_key2

validates_length_of :minimum => 5

Page 38: Ruby on Rails at PROMPT ISEL '11

ControllersManaging the CRUD

Page 39: Ruby on Rails at PROMPT ISEL '11

# app/controllers/posts_controller.rbclass PostsController < ApplicationController # GET /posts def index ...

# GET /posts/1 def show ...

# GET /posts/new def new ...

# GET /posts/1/edit def edit ...

# POST /posts def create ...

# PUT /posts/1 def update ...

# DELETE /posts/1 def destroy ...end

Generated with scaffold

Page 40: Ruby on Rails at PROMPT ISEL '11

# POST /posts # POST /posts.xml def create @post = Post.new(params[:post])

respond_to do |format| if @post.save format.html { redirect_to(@post, :notice => 'Post was successfully created.') } format.xml { render :xml => @post, :status => :created, :location => @post } else format.html { render :action => "new" } format.xml { render :xml => @post.errors, :status => :unprocessable_entity } end end end

Page 41: Ruby on Rails at PROMPT ISEL '11

def index @posts = Post.all

respond_to do |format| format.html # index.html.erb format.xml { render :xml => @posts } end end

pcunha:Blog$ curl http://localhost:3000/posts.xml<?xml version="1.0" encoding="UTF-8"?><posts type="array"> <post> <created-at type="datetime">2011-07-15T13:39:51Z</created-at> <body>This is the body of the first post</body> <title>The first very post of this blog</title> <updated-at type="datetime">2011-07-15T13:39:51Z</updated-at> <id type="integer">1</id> </post></posts>

Page 42: Ruby on Rails at PROMPT ISEL '11

Views

Page 43: Ruby on Rails at PROMPT ISEL '11

# app/views/posts/new.html.erb<h1>New post</h1>

<%= form_for(@post) do |f| %> <div class="field"> <%= f.label :title %><br /> <%= f.text_field :title %> </div> <div class="field"> <%= f.label :body %><br /> <%= f.text_area :body %> </div> <div class="actions"> <%= f.submit %> </div><% end %>

<%= link_to 'Back', posts_path %>

# app/controllers/posts_controller.rb def new @post = Post.new respond_to do |format| format.html # new.html.erb} end end

Page 44: Ruby on Rails at PROMPT ISEL '11

# app/controllers/posts_controller.rb def edit @post = Post.find(params[:id]) respond_to do |format| format.html # edit.html.erb} end end

# app/views/posts/edit.html.erb<h1>Edit post</h1>

<%= form_for(@post) do |f| %> <div class="field"> <%= f.label :title %><br /> <%= f.text_field :title %> </div> <div class="field"> <%= f.label :body %><br /> <%= f.text_area :body %> </div> <div class="actions"> <%= f.submit %> </div><% end %>

<%= link_to 'Show', @post %> |<%= link_to 'Back', posts_path %>

Page 45: Ruby on Rails at PROMPT ISEL '11

Rails builds the route for you

link_to 'Show', @post # GET posts/@post.id

form_for(@post)

if @post.new_record? POST /posts else PUT /posts/@post.id end

Page 46: Ruby on Rails at PROMPT ISEL '11

Partials

Views

Page 47: Ruby on Rails at PROMPT ISEL '11

# app/views/posts/edit.html.erb<h1>Edit post</h1>

<%= form_for(@post) do |f| %> <div class="field"> <%= f.label :title %><br /> <%= f.text_field :title %> </div> <div class="field"> <%= f.label :body %><br /> <%= f.text_area :body %> </div> <div class="actions"> <%= f.submit %> </div><% end %>

<%= link_to 'Show', @post %> |<%= link_to 'Back', posts_path %>

# app/views/posts/new.html.erb<h1>New post</h1>

<%= form_for(@post) do |f| %> <div class="field"> <%= f.label :title %><br /> <%= f.text_field :title %> </div> <div class="field"> <%= f.label :body %><br /> <%= f.text_area :body %> </div> <div class="actions"> <%= f.submit %> </div><% end %>

<%= link_to 'Back', posts_path %>

Bad pattern

Page 48: Ruby on Rails at PROMPT ISEL '11

# app/views/posts/new.html.erb<h1>New post</h1>

<%= render "form" %>%><%= link_to 'Back', posts_path %>

# app/views/posts/edit.html.erb<h1>Edit post</h1>

<%= render "form" %>%><%= link_to 'Show', @post %> |<%= link_to 'Back', posts_path %>

# app/views/posts/_form.html.erb<%= form_for(@post) do |f| %> <div class="field"> <%= f.label :title %><br /> <%= f.text_field :title %> </div> <div class="field"> <%= f.label :body %><br /> <%= f.text_area :body %> </div> <div class="actions"> <%= f.submit %> </div><% end %>

The right way

Page 49: Ruby on Rails at PROMPT ISEL '11

AJAXImprove user experience

Page 50: Ruby on Rails at PROMPT ISEL '11

Improve user experience by not having the whole page reload when submitting a form or simple pagination link

Also save resources used (SQL queries, memory, more bandwidth usage,... etc)

Page 51: Ruby on Rails at PROMPT ISEL '11

Changing default forms to AJAX

AJAX

Page 52: Ruby on Rails at PROMPT ISEL '11

# config/routes.rbBlog::Application.routes.draw do resources :posts do resources :comments, :only => [:create] endend

POST /posts/:post_id/comments

Limiting actions is always the best practice

Page 53: Ruby on Rails at PROMPT ISEL '11

# app/controllers/comments_controller.rbclass CommentsController < ApplicationController

def create @post = Post.find(params[:post_id]) @comment = @post.comments.new(params[:comment]) respond_to do |format| if @comment.save format.html { redirect_to(@post } else format.html { render :template => "posts/show.html.erb" } end end endend

Page 54: Ruby on Rails at PROMPT ISEL '11

# app/views/posts/show.html.erb...<h1>Comments</h1><div id="comments"> <%= render :partial => "comments/comment", :collection => @post.commments %></div>

<%= render :partial => "comments/form", :locals => { :post => @post, :comment => @comment || Comment.new } %>

Page 55: Ruby on Rails at PROMPT ISEL '11

# app/views/comments/_form.html.erb<%= form_for [post,comment] do |f| %> <div class="field"> <%= f.label :body %><br /> <%= f.text_area :body %> <p><%= f.submit %></p> </div><% end %>

Our HTML formWhat needs to change?

Page 56: Ruby on Rails at PROMPT ISEL '11

# app/views/comments/_form.html.erb<%= form_for [post,comment], :remote => true do |f| %> <div class="field"> <%= f.label :body %><br /> <%= f.text_area :body %> <p><%= f.submit %></p> </div><% end %>

That’s it? Not yet!

Page 57: Ruby on Rails at PROMPT ISEL '11

# app/controllers/comments_controller.rbclass CommentsController < ApplicationController

def create @post = Post.find(params[:post_id]) @comment = @post.comments.new(params[:comment]) respond_to do |format| if @comment.save format.html { redirect_to(@post, :notice => 'Comment was successfully created.') } format.js else format.html { render :action => "new" } format.js end end endend

Page 58: Ruby on Rails at PROMPT ISEL '11

# app/views/comments/create.js.erb//Dump javascript here!document.getElementById...

Notice:- create.js.erb- writing native javascript is not optimal:

1. You will forget something about IE2. We are at 21st Century3. Lots of good frameworks

Page 59: Ruby on Rails at PROMPT ISEL '11

Rails 2.X and 3.0.X

- Prototype JS Framework as default

Rails 3.1 (released 2011)

- jQuery JS Framework as default

Page 60: Ruby on Rails at PROMPT ISEL '11

# app/views/comments/create.js.erb<% if @comment.new_record? %>

<% content = render(:partial => "comments/form", :locals => { :post => @post, :comment => @comment }) content = escape_javascript(content) %>

$('new_comment').replace("<%= content %>");

<% else %> <% comment_content = render(:partial => "comments/comment", :object => @comment) comment_content = escape_javascript(comment_content) %> $('comments').insert({ bottom : '<%= comment_content %>' }) $('new_comment').reset();<% end %>

Page 61: Ruby on Rails at PROMPT ISEL '11

Almost there... but

- Complex code- We can do better with Rails

Page 62: Ruby on Rails at PROMPT ISEL '11

RJSRuby (to) JavaScript Templates

Page 63: Ruby on Rails at PROMPT ISEL '11

# app/views/comments/create.js.rjsif @comment.new_record? page.replace :new_comment, :partial => "comments/form", :locals => { :post => @post, :comment => @comment }else page.insert_html :bottom, :comments, :partial => "comments/comment", :object => @comment page[:new_comment].resetend

Page 64: Ruby on Rails at PROMPT ISEL '11

Gems

Page 65: Ruby on Rails at PROMPT ISEL '11

GemsExtend Rails framework

Easy installation and usage

Increasing community

• Github

• Gemcutter

Page 66: Ruby on Rails at PROMPT ISEL '11

Bundler gem# Gemfilegem "rails", "2.3.10"gem "will_paginate"gem "authlogic"

gem "pg"gem "postgis_adapter", "0.7.8"gem "GeoRuby", "1.3.4"

# Sphinxgem "thinking-sphinx", "1.4.5"

group :development do gem "capistrano" gem "capistrano-ext" gem "ruby-debug" gem "wirble" gem "mongrel"end

Page 67: Ruby on Rails at PROMPT ISEL '11

Questions ?

Page 68: Ruby on Rails at PROMPT ISEL '11

References

http://rubyonrails.org/

http://railsapi.com/

http://railscasts.com/

http://railsforzombies.org/

Page 69: Ruby on Rails at PROMPT ISEL '11

References