ruby on rails at prompt isel '11
Post on 12-Sep-2014
1.666 views
DESCRIPTION
An introduction to Ruby on Rails. Presentation made at PROMP, a pos-graduation on ISEL university at Portugal.TRANSCRIPT
Web Development with
Ruby On Rails
Pedro Cunha
Ruby
Yukihiro "Matz" Matsumoto
Ruby is designed for programmer productivity and fun
Created February 1993
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)
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
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
Rubya = {}a[:first] = 2a[:things] = 3a[:foo] = "bar"
b = { :first => 2, :things => 3, :foo => "bar"}
b[:first] # 2
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]
Ruby
class String def +() # override string default + operator endend
Monkey Patching
“With great power comes great responsability”Uncle Ben, Amazing Spiderman nº1
Ruby on Rails
Ruby on Rails
Created by David Heinemeir Hansson
• CEO at 37th Signals
• Personal Project, 2004
Present
• Rails 3.1
• Growing community
Ruby on Rails
Convention vs Configuration
MVC Architecture
REST routing
Convention vs Configuration
• Don’t Repeat Yourself
• Increased productivity through conventions. Ex.: following a pattern for foreign key columns.
• Take advantage of singular and plural word meanings
MVC
RoR
RESTRepresentational State Transfer
POST GET PUT DELETE
/posts/posts/1/posts/1/posts/1
CREATE READ UPDATE DELETE
CRUD REST ROUTES
# routes.rbBlog::Application.routes.draw do resources :postsend
Starting development
RoR
pcunha:prompt$ rails new Blog -d mysql
Blog /app /controllers /mailers /models /views /config database.yml /db /migrate Gemfile /public /javascripts /stylesheets
# config/database.ymldevelopment: adapter: sqlite3 database: db/development.sqlite3 test: adapter: sqlite3 database: db/test.sqlite3 production: adapter: sqlite3 database: db/production.sqlite3
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
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
localhost:3000
ModelDatabase Schema
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
# 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
pcunha:Blog$ rake db:createpcunha:Blog$ rake db:migrate== CreatePosts: migrating-- create_table(:posts) -> 0.0015s== CreatePosts: migrated (0.0018s)
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
# 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
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)
rake db:createrake db:migraterake db:migrate:redorake db:rollback
blog_db.schema_migrations - keeps the version number of all migrations already runned
Relations
Model
# 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
Validations
Model
# 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
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
ControllersManaging the CRUD
# 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
# 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
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>
Views
# 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
# 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 %>
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
Partials
Views
# 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
# 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
AJAXImprove user experience
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)
Changing default forms to AJAX
AJAX
# 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
# 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
# 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 } %>
# 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?
# 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!
# 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
# 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
Rails 2.X and 3.0.X
- Prototype JS Framework as default
Rails 3.1 (released 2011)
- jQuery JS Framework as default
# 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 %>
Almost there... but
- Complex code- We can do better with Rails
RJSRuby (to) JavaScript Templates
# 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
Gems
GemsExtend Rails framework
Easy installation and usage
Increasing community
• Github
• Gemcutter
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
Questions ?
References
http://rubyonrails.org/
http://railsapi.com/
http://railscasts.com/
http://railsforzombies.org/
References