Transcript

The Rails WayApproach to modern web applications with

Rails 3.2

80% of end-users response time is downloading all the assets.

The Performance Golden Rule

The Rails Way to managing assets:

HTTP StreamingThe Assets Pipeline

Assets Pipeline

● Assets concatenation● Assets minification● Support for high-level languages

○ CoffeeScript○ SASS

● Assets fingerprinting

Syntactically Awesome Stylesheets$blue: #3bbfce;$margin: 16px;

.content-navigation { border-color: $blue; color: darken($blue, 9%);}

.border { padding: $margin / 2; margin: $margin / 2; border-color: $blue;}

CoffeeScriptclass ProfileCompetences extends Backbone.View

tagName: 'ul'

className: 'inputs-select'

initialize: ->

@collection.on('reset', @render, this)

render: ->

competences = @collection.competences()

_.each competences, (competence) =>

view = new AutosaveSelectOption(model: @model, dict: competence)

$(@el).append(view.render().el)

this

Serving Static Assets

Rails by default doesn't serve static assets in production environment.

Deploymentict-ref-cloud/

current -> ~/ict-ref-cloud/releases/20120904134910

releases/

20120904134910/

app/

config/

database.yml -> ~/ict-ref-cloud/shared/config/database.yml

public/

assets -> ~/ict-ref-cloud/shared/assets

shared/

assets/

application-8e3bd046319a574dc48990673b1a4dd9.js

application-8e3bd046319a574dc48990673b1a4dd9.js.gz

application.css

application.css.gz

config/

database.yml

Deployment

nginx ~/ict-ref-cloud/current/public

/tmp/unicorn.ict-ref-cloud.sock

unicorn ~/ict-ref-cloud/current

OWASP Top Ten Security Risk

1. Injection2. Cross Site Scripting3. Broken Authentication and Session Management4. Insecure Direct Object Reference5. Cross Site Request Forgery6. Security Misconfiguration7. Insecure Cryptographic Storage8. Failure To Restrict URL Access9. Insufficient Transport Layer Protection

10. Unvalidated Redirects and Forwards

OWASP Top Ten Security Risk

1. Injection2. Cross Site Scripting3. Broken Authentication and Session Management4. Insecure Direct Object Reference5. Cross Site Request Forgery6. Security Misconfiguration7. Insecure Cryptographic Storage8. Failure To Restrict URL Access9. Insufficient Transport Layer Protection

10. Unvalidated Redirects and Forwards

Problems related specifically to view layer.

The Rails Way to security:

CSRF protectionXSS protection

Cross Site Request Forgery

/app/controllers/application_controller.rb

class ApplicationController < ActionController::Base

protect_from_forgery

end

/app/views/layouts/application.html.erb

<head>

<%= csrf_meta_tags %>

</head>

<meta content="authenticity_token" name="csrf-param">

<meta content="KklMulGyhEfVztqfpMn5nRYc7zv+tNYb3YovBwOhTic="

name="csrf-token">

Cross Site Scripting

<div id="comments">

<% @post.comments.each do |comment| %>

<div class="comment">

<h4><%= comment.author %> say's:</h4>

<p><%= comment.content %></p>

</div>

<% end %>

</div>

<%# Insecure! %>

<%= raw product.description %>

The Rails Way to routing:

Non-Resourceful routesResourceful routesSEO friendly URL's

match 'products/:id' => 'products#show'

GET /products/10

post 'products' => 'products#create'

POST /products

namespace :api do

put 'products/:id' => 'api/products#update'

end

PUT /api/products/10

Non-Resourceful Routes

Non-Resourceful Routes

match 'photos/show' => 'photos#show', :via => [:get, :post]

match 'photos/:id' => 'photos#show', :constraints => { :id => /[A-Z]\d{5}/ }

match "photos", :constraints => { :subdomain => "admin" }

match "/stories/:name" => redirect("/posts/%{name}")

match 'books/*section/:title' => 'books#show'

root :to => 'pages#main'

Resourceful Routes

resources :photos

get '/photos' => 'photos#index'

get '/photos/new' => 'photos#new'

post '/photos' => 'photos#create'

get '/photos/:id' => 'photos#show'

get '/photos/:id/edit' => 'photos#edit'

put '/photos/:id' => 'photo#update'

delete '/photos/:id' => 'photo#destroy'

Resourceful Routes

resource :profile

get '/profile/new' => 'profiles#new'

post '/profile' => 'profiles#create'

get '/profile' => 'profiles#show'

get '/profile/edit' => 'profiles#edit'

put '/profile' => 'profile#update'

delete '/profile' => 'profile#destroy'

Named Routes

<%= link_to 'Profile', profile_path %>

=> <a href="/profile">Profile</a>

<%= link_to 'Preview', @photo %>

=> <a href="/photo/10">Preview</a>

SEO Friendy URL's

/products/14-foo-bar

class Product < ActiveRecord::Base

def to_param

"#{id}-#{name.parametrize}"

end

end

<%= link_to product.name, product %>

/products/14-foo-bar

"/products/:id" => "products#show"

{ :id => "14-foo-bar" }

Product.find(params[:id])

Product.find(14)

Will generate

Will call to_i

Example from: http://www.codeschool.com/courses/rails-best-practices

The Rails Way to view rendering:

Response renderingStructuring Layouts

AJAX

/app

/controllers

products_controller.rb

users_controller.rb

Response Rendering

/app

/views

/products

index.html.erb

show.html.erb

/users

index.html.erb

new.html.erb

class UsersController < ApplicationController

def new

@user = User.new

end

def create

@user = User.new(params[:user])

if @user.save

redirect_to :action => :show

else

render :new

end

end

end

Response Rendering

new.html.erb

show.html.erb

new.html.erb

Rendering Response

render :edit

render :action => :edit

render 'edit'

render 'edit.html.erb'

render :template => 'products/edit'

render 'products/edit'

render :file => '/path/to/file'

render '/path/to/file'

Rendering Response

render :inline => '<p><%= @comment.content %></p>'

render :text => 'OK'

render :json => @product

render :xml => @product

render :js => "alert('Hello Rails');"

render :status => 500

render :status => :forbidden

render :nothing => true, :status => :created

Content Negotiation

respond_to do |format|

format.html

format.json { render :json => @product }

format.js

end

Content Negotiation

class ProductsController < ApplicationController

respond_to :html, :json, :js

def edit

respond_with Product.find(params[:id])

end

def update

respond_with Product.update(params[:id], params[:product])

end

end

Structuring Layout

/app

/views

/layouts

application.html.erb (default)

users.html.erb (UsersController)

public.html.erb (layout 'public')

Structuring Layout

<!DOCTYPE html>

<head>

<title>User <%= yield :title %></title>

</head>

<html>

<body>

<%= yield %>

</body>

</html>

Structuring Layout

<% content_for :title, @post.title %>

<div id="post">

<h2><%= @post.title %></h2>

<div><%= @post.content %></div>

</div>

View Helpers

module ApplicationHelper

def title(title)

content_for :title, title

end

end

View Helpers

<% title @post.title %>

<div id="post">

<h2><%= @post.title %></h2>

<div><%= @post.content %></div>

</div>

Partials

/app

/views

/products

_form.html.erb

_product.html.erb

index.html.erb

edit.html.erb

new.html.erb

Partials

/app/views/products/_product.html.erb

<div class="product">

<h2><%= product.name %></h2>

<p><%= product.description %></p>

</div>

Partial parameter

Partials

<h1>Products</h1>

<% @products.each do |product| %>

<% render 'products/product',

:product => product %>

<% end %>

Partials

<h1>Products</h1>

<% @products.each do |product| %>

<% render product %>

<% end %>

Partials

<h1>Products</h1>

<% render @products %>

Partials/app/views/products/_form.html.erb

<%= form_for @user do |f| %>

<div class="input">

<%= f.label :name %>

<%= f.text_field :name %>

</div>

<div class="input">

<%= f.label :description %>

<%= f.text_area :description %>

</div>

<div class="actions">

<%= f.submit %>

</div>

<% end %>

Partials

../products/new.html.erb

<h1>New Product</h1>

<div id="form">

<% render 'form' %>

</div>

../products/edit.html.erb

<h1>Edit Product</h1>

<div id="form">

<% render 'form' %>

</div>

AJAX/app/views/products/_form.html.erb

<%= form_for @user, :remote => true do |f| %>

<div class="input">

<%= f.label :name %>

<%= f.text_field :name %>

</div>

<div class="input">

<%= f.label :description %>

<%= f.text_area :description %>

</div>

<div class="actions">

<%= f.submit %>

</div>

<% end %>

AJAX/app/controllers/products_controller.rb

class ProductsController < ApplicationController

def create

@product = Product.new(params[:product])

respond_to do |format|

if @product.save

format.html { redirect_to @product }

else

format.html { render :action => 'new' }

format.js

end

end

end

end

AJAX

/app/views/products/create.js.erb

$('#form').html("<%= escape_javascript(render 'form') %>");

AJAX

/app/views/products/index.html.erb

<%= link_to "Delete", product, method: :delete, remote: true %>

<a href="/products/1" data-method="delete"

data-remote="true" rel="nofollow">Delete</a>

(Rails is using unobtrusive javascript technique)

/app/views/products/destroy.js.erb

$("#<%= dom_id @product %>").fadeOut();

The Rails Way to caching:

Basic CachingMemoization

class ProductsController < ActionController

caches_page :index

def index

@products = Product.all

end

def create

expire_page :action => :index

end

end

Page caching won't work with filters.

Page Caching

Action Caching

class ProductsController < ActionController

before_filter :authenticate_user!

caches_action :index

def index

@products = Product.all

end

def create

expire_action :action => :index

end

end

Fragment Caching<% cache do %>

All available products:

<% @products.each do |p| %>

<%= link_to p.name, product_url(p) %>

<% end %>

<% end %>

expire_fragment(

:controller => 'products',

:action => 'recent',

:action_suffix => 'all_products'

)

Sweepers

class ProductSweeper < ActionController::Caching::Sweeper

observe Product

def after_create(product)

# Expire the index page now that we added a new product

expire_page(:controller => 'products', :action => 'index')

# Expire a fragment

expire_fragment('all_available_products')

end

end

Conditional GET support

class ProductsController < ApplicationController

def show

@product = Product.find(params[:id])

if stale?(:last_modified => @product.updated_at.utc, :etag => @product)

respond_to do |format|

# ... normal response processing

end

end

end

end

Memoization

class City < ActiveRecord::Base

attr_accesible :name, :zip, :lat, :lon

def display_name

@display_name ||= "#@zip #@name"

end

end

The Rails Way to solve typical problems:

N+1 ProblemFetching object in batches

class User

def recent_followers

self.followers.recent.collect do |f|

f.user.name

end

end

end

Select followers where user_id=1

Select user where id=2

Select user where id=3

Select user where id=4

Select user where id=5

Source: http://www.codeschool.com/courses/rails-best-practices

N+1 Problem

class User

def recent_followers

self.followers.recent.includes(:user).collect do |f|

f.user.name

end

end

end

Select followers where user_id=1

Select users where user_id in (2,3,4,5)

Bullet Gem:

https://github.com/flyerhzm/bulletSource: http://www.codeschool.com/courses/rails-best-practices

N+1 Problem

Fetching objects in Java

List<Tweet> tweets = tweetDao.findAllForUser(user);

for (Tweet tweet : tweets) {

// ...

}

for (Tweet tweet : user.getTweets()) {

// ...

}

Fetching objects in Rails

Tweet.where(user: user).find_each do |tweet|

# ...

end

user.tweets.find_each(batch_size: 5000) do |tweet|

# ...

end

By default pulls batches of 1,000 at a time

Try Rails!

top related