Upload
michal-orman
View
2.780
Download
1
Tags:
Embed Size (px)
Citation preview
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!