- Match routes:
Rails 3:
match '/item/:id/purchase', to: 'items#purchase'
Rails 4(Ruby>=1.9.3):
Error:
You should not use the 'match' method in your router without specifying an HTTP method.(RuntimeError)
post '/item/:id/purchase', to: 'items#purchase'
match '/item/:id/purchase', to: 'items#purchase', via: [:get, :post]
match '/item/:id/purchase', to: 'items#purchase', via: [:all]
- Patch verb(用于局部更新):
PUT:
"The PUT method requests that the enclosed entity be stored under the supplied Request-URI."-RFC 2616/HTTP 1.1
PATCH:
"(...)the entity contains a list of differences between the original version of the resouces and the desired content of the resource after the PATCH action has been applied."-RFC 2068/HTTP 1.1
config/routes.rb
resources :items
Rails 3:
rake routes显示的资源路径为7条。
Rails 4:
rake routes显示的资源路径为8条。
会多出一条路径:
item PATCH /items/:id(.:format) items#update
Controller Tests:
test "updates item with PATCH" do
patch :update, id: @item, item: { description: @item.description }
assert_redirected_to item_url(@item)
end
- Routing concerns:
Rails 3:
resources :messages do
resources :comments
resources :categories
resources :tags
end
resources :posts do
resources :comments
resources :categories
resources :tags
end
resources :items do
resources :comments
resources :categories
resources :tags
end
Rails 4:
e.g.
concern :sociable do
resources :comments
resources :categories
resources :tags
end
resources :messages, concern: :sociable
resources :posts, concern: :sociable
resources :items, concern: :sociable
e.g.
concern :sociable do |options|
resources :comments, options
resources :categories, options
resources :tags, options
end
resources :messages, concerns: :sociable
resources :posts, concerns: :sociable
resources :items do
concern: :sociable, only: :create
end
e.g.
concern :sociable, Sociable
resources :messages, concerns: :sociable
resources :posts, concerns: :sociable
resources :items do
concerns :sociable, only: :create
end
app/concerns/sociable.rb
class Sociable
def self.call(mapper, options)
mapper.resources :comments, options
mapper.resources :categories, options
mapper.resources :tags, options
end
end
- 去除了烦人的nil:
e.g.
@post = Post.where(title: title).first
@post.id
Rails 3:
RuntimeError: Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id
Rails 4:
NoMethodError: undefined method 'id' for nil:NilClass
Thread-safety:
Rails 3:
config/environments/production.rb
MyApp::Application.configure do
# Enable threaded mode
# config.threadsafe! (此方法已被遗弃)
end
Rails 4(安全线程默认打开):
config/environments/production.rb
MyApp::Application.configure do
config.cache_classes = true # 确保在请求之间加载时,Rack::Lock不被包含在Rack::Lock不被包含在中间件栈内。
config.eager_load = true # 在新线程创建前加载所有代码
end
- Finders:
Rails 3:
Post.find(:all, conditions: { author: 'admin' })
Post.find_all_by_title('Rails')
Post.find_last_by_author('Devin')
Rails 4:(上述特性一杯包含入activerecord-deprecated_finders gem)
Post.where(author: 'admin')
Post.where(title: 'Rails 4')
Post.where(author: 'admin').last
- Find_by:
Rails 3:(activerecord-deprecated_finders gem)
Post.find_by_title('Rails 4')
Post.find_by_title('Rails 4', conditions: { author: 'admin' })
Rails 4:
Post.find_by_title('Rails 4') --->better: Post.find_by(title: 'Rails 4')
Post.find_by(title: 'Rails 4', author: 'admin')
post_parms = { title: 'Rails 4', author: 'admin' }
Post.find_by(post_parms)
Post.find_by("published_on < ?", 2.weeks.ago) # 可以上where一样使用
- Find_or_*:
Rails 3:(activerecord-deprecated_finders gem)
Post.find_or_initialize_by_title('Rails 4')
Post.find_or_create_by_title('Rails 4')
Rails 4:
Post.find_or_initialize_by(title: 'Rails 4')
Post.find_or_create_by(title: 'Rails 4')
- Update & Update_column:
Rails 3:
@post.update_attributes(post_parms)
@post.update_attribute(:title, 'Rails 4')
@post.update_column(:title, 'Rails 4')
Rails 4:
@post.update(post_parms)
# 以下这些方法将会跳过验证逻辑
@post.update_attribute(:title, 'Rails 4')
@post.update_column(:title, 'Rails 4')
@post.Update_columns(post_parms)
- Model.all:
Rails 3:
@tweets = Tweet.scoped
Rails 4:
@tweets = Tweet.all # return ActiveRecord::Relation
- Scopes:
Rails 3:
scope :sold, where(state: 'sold')
default_scope where(state: 'available')
scope :recent, where(published_at: 2.weeks.ago)
scope :recent_red, recent.where(color: 'red')
Rails 4:
scope :sold, ->{ where(state: 'sold') }
default_scope {where(state: 'available')} or default_scope ->{where(state: 'available')}
scope :recent,->{ where(published_at: 2.weeks.ago) }
scope :recent_red, ->{ recent.where(color: 'red') }
- Relation#none
Rails 3:
class User < ActiveRecord::Base
def visible_posts
case role
when 'Country Manager'
Post.where(country: country)
when 'Reviewer'
Post.published
when 'Bad User'
[]
end
end
end
@posts = current_user.visible_posts
if @posts.any?
@posts.recent
else
[]
end
Rails 4:
class User < ActiveRecord::Base
def visible_posts
case role
when 'Country Manager'
Post.where(country: country)
when 'Reviewer'
Post.published
when 'Bad User'
Post.none # return ActiveRecord::Relation不操作数据库
end
end
end
@posts = current_user.visible_posts
@posts.recent
- Relation#not:
Rails 3:
if author
Post.where('author != ?', author)
else
Post.where('author is not null')
end
Rails 4:
Post.where.not(author: author)
- Relation#order
Rails 3:
class User < ActiveRecord::Base
default_scope { order(:name) }
end
User.order("created_at DESC")
sql: select * from users order by name asc, created_at desc
User.order(:name, 'created_at DESC')
Rails 4:
User.order(created_at: :desc)
sql: select * from users order by created_at desc, name asc
User.order(:name, created_at: :desc)
- Relation#references
Rails 4:
Post.includes(:comments).where("comments.name = 'foo'").references(:comments) #此种情况需要使用references声明引用表
Post.includes(:comments).where(comments: { name: 'foo' })
Post.includes(:comments).where('comments.name' => 'foo')
Post.includes(:comments).order('comments.name')
- ActiveModel::Model:
Rails 3:
class SupportTicket
include ActiveModel::Conversion
include ActiveModel::Validation
extend ActiveModel::Naming
attr_reader :title, :description
validates_presence_of :title
validates_presence_of :description
end
Rails 4:
activemodel/lib/active_model/model.rb
def self.included(base)
base.class_eval do
extend ActiveModel::Naming
extend ActiveModel::Translation
extend ActiveModel::Conversion
extend ActiveModel::Validation
end
end
class SupportTicket
include ActiveModel::Model
attr_reader :title, :description
validates_presence_of :title
validates_presence_of :description
end
- Strong parameters:
Rails 3:
models/user.rb
class User < ActiveRecord::Base
attr_accessible :name
end
controllers/users_controller.rb
def update
if @user.update_attributes(params[:user])
redirect_to @user, notice: 'Updated'
else
render action: 'edit'
end
end
Error:
parameters: {"user"=>{"name"=>"Cowzombie", "admin"=>"1"}}
ActiveModel::MassAssignmentSecurity::Error Cannot mass-assign protected attributes: admin
Rails 4:(attr_reader等在protected_attributes gem中)
models/user.rb
class User < ActiveModel::Base
end
controllers/users_controller.rb
def update
user_params = params.require(:user).permit(:name)
if @user.update(user_params)
redirect_to @user, notice: 'Updated'
else
render action: 'edit'
end
end
config/applcation.rb(默认开启日志)
config.action_controller.action_on_unpermitted_parameters = :raise
Error:
ActionController::ParameterMissing: param not found:user
返回400状态码
Permit types:
String,Symbol, NilClass, Numeric, TrueClass, FalseClass, Date, Time, DateTime, StringIO, IO, ActionDispatch::Http::UploadedFile and Rack::Test::UploadedFile
- Protect from forgery:
protect_from_forgery with: :exception # ActionController::InvalidAuthenticityToken
pretect_from_forgery with: :null_session # an empty session(until the next valid request)
pretect_from_forgery with: :reset_session # a new session(destroying the old one)
- Action Controller Filters/Actions
Rails 3:
before_filter :set_person, except: [:index, :new, :create]
before_filter :ensure_permission, only: [:edit, :update]
Rails 4:(filter 方法仍然可以用)
before_action :set_person, except: [:index, :new, :create]
before_action :ensure_permission, only: [:edit, :update]
- Session:
Rails 3:
cookie-based session store
require 'rack'
cookie = '...'
Rack::Session::Cookie::Base64::Marshal.new.decode(cookie) # 可获取信息
Rails 4:
采用Encrypted cookie session store存储,无法通过解码获取cookie数据
require 'rack'
cookie = '...'
Rack::Session::Cookie::Base64::Marshal.new.decode(cookie) # nil
Securing secret key base:
config/initializers/secret_token.rb
MyApp::Application.config.secret_key_base = ENV['SECURE_KEY_BASE']
- Flash:
Rails 3 & 4:
<p id="notice"><%= flash[:notice] %></p>
<p id="notice"><%= notice %></p>
<p id="notice"><%= flash[:alert] %></p>
<p id="notice"><%= alert %></p>
Rails 4:
controllers/application_controller.rb
class ApplicationController < ActionController::Base
add_flash_types :grunt, :snarl
end
controllers/users_controller.rb
flash[:grunt] = "Successfully created..."
views/users/show.html.erb
<p id="grunt"><%= grunt %></p>
- Collection form helpers:
class Owner < ActiveRecord::Base
has_many :items
end
class Item < ActiveRecord::Base
belongs_to :owner
end
Rails 3 & 4:
collection_select(:item, :owner_id, Owner.all, :id, :name)
Rails 4:
collection_radio_buttons(:item, :owner_id, Owner.all, :id, :name)
collection_check_boxes(:item, :owner_id, Owner.all, :id, :name)
- Date field:
Rails 3 & 4:
<%= f.date_select :return_date %>
Rails 4:(显示为一个控件,根据浏览器不同而不同。)
<input id="item_return_date" name="item[return_date]" type="date"></input>
- Response type from controller:
Rails 4:
views/owners/index.json.ruby
owners_hashes = @owners.map { |owner|
{ name: owner.name, url: owner_url(owner) }
}
owners_hashes.to_json
- Memcache store uses dalli:
config/environments/production.rb
config.cache_store = :mem_cache_store
Rails 3:
Uses memcache-client
Rails 4:
Uses the Dalli client to connect to memcached
gem 'dalli'
Cache Store API:(Rails 3 & 4)
Rails.cache.read('key')
Rails.cache.write('key', value)
Rails.cache.fetch('key'){ value }
config/environments/production.rb
config.action_controller.perform_caching = true
Rails 3:
app/views/comments/_comment.html.erb
<% cache ['v1', comment] do %>
<li><%= comment %></li>
<% end %>
app/views/comments/_comment.html.erb
<% cache ['v2', comment] do %>
<li><%= comment %> - <% comment.author %></li>
<% end %>
Rails 4:
app/views/comments/_comment.html.erb
<% cache comment do %>
<li><%= comment %></li>
<% end %>
app/views/comments/_comment.html.erb
<% cache comment do %>
<li><%= comment %> - <% comment.author %></li>
<% end %>
Rails 3 & 4:
app/views/projects/show.html.erb
<%= render @project.documents %>
app/views/documents/_document.html.erb
<% cache document do %>
<article>
<h3><%= document.title %></h3>
<ul>
<%= render document.comments %>
</ul>
<%= link_to 'View details', document %>
</article>
<% end %>
app/views/comments/_comment.html.erb
<% cache comment do %>
<li><%= comment %></li>
<% end %>
app/models/comment.rb
class Comment < ActiveRecord::Base
belongs_to :document, touch: true
end
Rails 4:
app/views/projects/show.html.erb
<%= render @project.documents %>
app/views/documents/_document.html.erb
<% cache document do %>
<article>
<h3><%= document.title %></h3>
<ul>
<%= render partial: "comments/comment", collection: document.recent_comments %> # recent_comments是一个帮助方法
<%# Template Dependency: comments/comment %> # 1
<%= render document.recent_comments %> # 2
<%= render document.comments %>
</ul>
<%= link_to 'View details', document %>
</article>
<% end %>
app/views/comments/_comment.html.erb
<% cache comment do %>
<li><%= comment %></li>
<% end %>
- ActionController::Live:(Rails 4)
controller/items_controller.rb:
class ItemsController < ApplicationController
include ActionController::Live
def show
response.headers["Content-Type"] = "text/event-stream"
3.times {
response.stream.write "Hello, browser!\n"
sleep 1
}
response.stream.close
end
def events
response.headers["Content-Type"] = "text/event-stream"
redis = Redis.new
redis.subscribe('item.create') do |on|
on.message do |event, data|
response.stream.write("data: #{data}\n\n")
end
end
response.stream.close
end
end
Gemfile:
gem 'puma'
gem 'jquery-turbolinks'
assets/javascripts/application.js
//= require jquery.turbolinks
views/owners/show.html.erb
<ul id="items"></ul>
assets/javascripts/owners.js(设置监听事件)
$(document).ready(initialize);
$(document).on('page:load', initialize);
function initialize(){
var source = new EventSource('/items/events');
source.addEventListener('message', update);
$('#owner_active').click(function(){
alert(this.checked);
});
};
function update(event) {
var item = $("<li>").text(event.data);
$("#item").append(item);
}
views/layouts/application.html.erb
<div id="loading">Loading...</div>
<script>
$(document).on('page:fetch', function(){
$('#loading').show();
});
$(document).on('page:change', function(){
$('#loading').show();
});
</script>
assets/stylesheets/application.css
#loading {
font-size: 24px;
color: #9900FF;
display: nont;
}