趁着上班前写一段 @@
那么从前面的教程中我们学习了如何创建一个简单的博客应用, 我个人觉得无论你是新手还是从rails2过来, rails3还是比较容易上手的, 现在我们就来看下rails3相比rails2, 进步在哪里, 优势又在什么地方. (本来这章打算写ujs的, 无奈工作繁忙只能推到周日了)
1. 脚本命令
旧的命令 新的用法
script/generate rails g
script/console rails c
script/server rails s
script/dbconsole rails db
2. 配置文件
rails2: config/environment.rb
- Rails::Initializer.run do |config|
- config.load_paths += %W(
- config.gem "bj"
- config.gem "sqlite3-ruby", :lib => "sqlite3"
- config.gem "aws-s3", :lib => "aws/s3"
- config.plugins = [ :exception_notification ]
- config.time_zone = 'UTC'
- end
Rails::Initializer.run do |config|
config.load_paths += %W( #{RAILS_ROOT}/extras )
config.gem "bj"
config.gem "sqlite3-ruby", :lib => "sqlite3"
config.gem "aws-s3", :lib => "aws/s3"
config.plugins = [ :exception_notification ]
config.time_zone = 'UTC'
end
rails3:config/application.rb
- module APP_NAME
- class Application < Rails::Application
- config.load_paths += %W(
- config.plugins = [ :exception_notification ]
- config.time_zone = 'UTC'
- end
- end
module APP_NAME
class Application < Rails::Application
config.load_paths += %W( #{RAILS_ROOT}/extras )
config.plugins = [ :exception_notification ]
config.time_zone = 'UTC'
end
end
这样就变成了一种架构式的应用, 我们可以根据方便的对config进行操作
3. 路由
在rails3中, 已经的路由可以继续工作, 而新的路由方式更加简洁.
在 rails2 中:
- map.resources :posts do |post|
- post.resources :comments
- end
map.resources :posts do |post|
post.resources :comments
end
而在rails3中, 表达更为形象:
- resources :posts do
- resources :comments
- end
resources :posts do
resources :comments
end
对于一些复杂的路由, rails2:
- post.resources :comments,
- :member => { :preview => :post },
- :collection => { :archived => :get }
post.resources :comments,
:member => { :preview => :post },
:collection => { :archived => :get }
在rails3中可以这样表达:
- resources :comments do
- member do
- post :preview
- end
- collection do
- get :archived
- end
- end
resources :comments do
member do
post :preview
end
collection do
get :archived
end
end
不够简洁? 我们还可以这样做:
- resources :comments do
- post :preview, :on => :member
- get :archived, :on => :collection
- end
resources :comments do
post :preview, :on => :member
get :archived, :on => :collection
end
对于基本路由, rails2:
- map.connect 'login', :controller => 'session', :action => 'new'
map.connect 'login', :controller => 'session', :action => 'new'
那么在rails3中:
- match 'login' => 'session#new'
match 'login' => 'session#new'
对于具名路由, rails2:
- map.login 'login', :controller => 'session', :action => 'new'
map.login 'login', :controller => 'session', :action => 'new'
在rails3中:
- match 'login' => 'session#new', :as => :login
match 'login' => 'session#new', :as => :login
对于程序根路由, rails2:
- map.root :controller => 'users', :action => 'index'
map.root :controller => 'users', :action => 'index'
rails3:
- root :to => 'users#index'
root :to => 'users#index'
对于遗留路由, rails2:
- map.connect ':controller/:action/:id'
- map.connect ':controller/:action/:id.:format'
map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id.:format'
那么在rails3中写法更优雅:
- match ':controller(/:action(/:id(.:format)))'
match ':controller(/:action(/:id(.:format)))'
对于路由参数, rals2:
- map.connect '/articles/:year/:month/:day', :controller => 'posts', :action => 'index'
map.connect '/articles/:year/:month/:day', :controller => 'posts', :action => 'index'
rails3:
- match '/articles/:year/:month/:day' => "posts#index"
match '/articles/:year/:month/:day' => "posts#index"
那么对于存档请求, 比如rails2:
- map.connect '/articles/:year/:month/:day', :controller => 'posts', :action => 'index'
- map.connect '/articles/:year/:month', :controller => 'posts', :action => 'index'
- map.connect '/articles/:year', :controller => 'posts', :action => 'index'
map.connect '/articles/:year/:month/:day', :controller => 'posts', :action => 'index'
map.connect '/articles/:year/:month', :controller => 'posts', :action => 'index'
map.connect '/articles/:year', :controller => 'posts', :action => 'index'
在rails3中:
- match '/articles(/:year(/:month(/:day)))' => "posts#index"
match '/articles(/:year(/:month(/:day)))' => "posts#index"
指定请求方式, rails2:
- map.connect '/articles/:year', :controller => 'posts', :action => 'index',
- :conditions => {:method => :get}
map.connect '/articles/:year', :controller => 'posts', :action => 'index',
:conditions => {:method => :get}
在rails3中:
- match '/articles/:year' => "posts#index", :via => :get
-
- get '/articles/:year' => "posts#index"
match '/articles/:year' => "posts#index", :via => :get
#或者更简单的:
get '/articles/:year' => "posts#index"
对于跳转, rails3:
- match 'signin', :to => redirect("/login")
- match 'users/:name', :to => redirect {|params| "/#{params[:name]}" }
- match 'google' => redirect('http://www.google.com/')
match 'signin', :to => redirect("/login")
match 'users/:name', :to => redirect {|params| "/#{params[:name]}" }
match 'google' => redirect('http://www.google.com/')
路由约束: rails2中实际上使用了 :requirements 符号
- map.connect '/:year', :controller => 'posts', :action => 'index',
- :requirements => { :year => /\d{4}/ }
map.connect '/:year', :controller => 'posts', :action => 'index',
:requirements => { :year => /\d{4}/ }
在rails3中:
- match '/:year' => "posts#index", :constraints => {:year => /\d{4}/}
match '/:year' => "posts#index", :constraints => {:year => /\d{4}/}
- :constraints => { :user_agent => /iphone/ }
- :constraints => { :ip => /192\.168\.1\.\d{1,3}/ }
- constraints(:host => /localhost/) do
- resources :posts
- end
- constraints IpRestrictor do
- get 'admin/accounts' => "queenbee#accounts"
- end
:constraints => { :user_agent => /iphone/ }
:constraints => { :ip => /192\.168\.1\.\d{1,3}/ }
constraints(:host => /localhost/) do
resources :posts
end
constraints IpRestrictor do
get 'admin/accounts' => "queenbee#accounts"
end
对于Rack应用, rails3:
- get 'hello' => proc { |env| [200, {}, "Hello Rack"] }
-
- get 'rack_endpoint' => PostsController.action(:index)
-
- get 'rack_app' => CustomRackApp
get 'hello' => proc { |env| [200, {}, "Hello Rack"] }
get 'rack_endpoint' => PostsController.action(:index)
get 'rack_app' => CustomRackApp
4. Bundler与ActionController
一个典型的rails应用, 我们一般需要在 environment.rb 指定你的 gems:
- config.gem "haml"
- config.gem "chronic", :version => '0.2.3'
config.gem "haml"
config.gem "chronic", :version => '0.2.3'
然后我们运行 $ rake gems:install, 该命令会取得并下载然后安装编译这些gems到你的系统RubyGems目录中.
之后我们运行 $ rake gems:unpack:dependencise, 把这些gem打包到你应用程序的vendor/gems目录中去.
这样做产生的问题:
1. 它直接绑定到Rails中
2. 没有从本质上解决依赖问题
3. 运行时容易发生冲突
在rails3中, 使用了 bundle 命令:
直接在你的 gemfile 中指定你的 gem
- gem "haml"
- gem "chronic", '0.2.3'
gem "haml"
gem "chronic", '0.2.3'
然后运行 $ bundle, 该命令会会取得并下载然后安装编译这些gems
然后运行 $ bundle package 把gem源移到/vendor/cache中去.
这样rails应用中的gem与系统中的gem就不会相冲突.
一般的控制器语法:
- class UsersController < ApplicationController
- def index
- @users = User.all
- respond_to do |format|
- format.html
- format.xml { render :xml => @users.to_xml }
- end
- end
-
- def show
- @user = User.find(params[:id])
- respond_to do |format|
- format.html
- format.xml { render :xml => @user }
- end
- end
-
- ...
class UsersController < ApplicationController
def index
@users = User.all
respond_to do |format|
format.html
format.xml { render :xml => @users.to_xml }
end
end
def show
@user = User.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @user }
end
end
...
改进的语法:
- class UsersController < ApplicationController
- respond_to :html, :xml, :json
- def index
- @users = User.all
- respond_with(@users)
- end
- def show
- @user = User.find(params[:id])
- respond_with(@user)
- end
- ...
class UsersController < ApplicationController
respond_to :html, :xml, :json
def index
@users = User.all
respond_with(@users)
end
def show
@user = User.find(params[:id])
respond_with(@user)
end
...
5. ActionMailer
rails2: $ script/generate mailer UserMailer welcome forgot_password
这将创建 app/models/user_mailer.rb
那么在rails3中: $ rails g mailer UserMailer welcome forgot_password
这将创建 app/mailers/user_mailer.rb
在实现部分, rails2:
- def welcome(user, subdomain)
- subject 'Welcome to TestApp'
- recipients user.email
- from '[email protected]'
- body :user => user, :subdomain => subdomain
- end
def welcome(user, subdomain)
subject 'Welcome to TestApp'
recipients user.email
from '[email protected]'
body :user => user, :subdomain => subdomain
end
- UserMailer.deliver_welcome(user, subdomain)
UserMailer.deliver_welcome(user, subdomain)
在rails3中:
- def welcome(user, subdomain)
- @user = user
- @subdomain = subdomain
- mail(:from => "[email protected]",
- :to => user.email,
- :subject => "Welcome to TestApp")
- end
def welcome(user, subdomain)
@user = user
@subdomain = subdomain
mail(:from => "[email protected]",
:to => user.email,
:subject => "Welcome to TestApp")
end
- UserMailer.welcome(user, subdomain).deliver
UserMailer.welcome(user, subdomain).deliver
相比rails2, 我们在rails3下实现一个mail要简单的多:
- class UserMailer < ActionMailer::Base
- default :from => "[email protected]",
- :reply_to => "[email protected]",
- "X-Time-Code" => Time.now.to_i.to_s
- def welcome(user, subdomain)
- @user = user
- @subdomain = subdomain
- attachments['test.pdf'] = File.read("#{Rails.root}/public/test.pdf")
- mail(:to => @user.email, :subject => "Welcome to TestApp") do |format|
- format.html { render 'other_html_welcome' }
- format.text { render 'other_text_welcome' }
- end
- end
- end
class UserMailer < ActionMailer::Base
default :from => "[email protected]",
:reply_to => "[email protected]",
"X-Time-Code" => Time.now.to_i.to_s
def welcome(user, subdomain)
@user = user
@subdomain = subdomain
attachments['test.pdf'] = File.read("#{Rails.root}/public/test.pdf")
mail(:to => @user.email, :subject => "Welcome to TestApp") do |format|
format.html { render 'other_html_welcome' }
format.text { render 'other_text_welcome' }
end
end
end
6. ActiveRelation 以及 ActiveModel
在rails2中, 我们经常使用下面的方法来进行查询:
- @posts = Post.find(:all, :conditions => {:published => true})
@posts = Post.find(:all, :conditions => {:published => true})
该方式将立即查询数据库然后返回Posts数组
而在rails3中:
- @posts = Post.where(:published => true)
@posts = Post.where(:published => true)
该方法不会查询数据库, 仅仅返回一个 ActiveRecord::Relation 对象, 然后:
- @posts = Post.where(:published => true)
- if params[:order]
- @posts = @posts.order(params[:order])
- end
- @posts.each do |p|
- ...
- end
@posts = Post.where(:published => true)
if params[:order]
@posts = @posts.order(params[:order])
end
@posts.each do |p|
... #在这里进行查询, 实现延迟加载
end
对于命名范围, 在rails2中:
- class Post < ActiveRecord::Base
- default_scope :order => 'title'
- named_scope :published, :conditions => {:published => true}
- named_scope :unpublished, :conditions => {:published => false}
- end
class Post < ActiveRecord::Base
default_scope :order => 'title'
named_scope :published, :conditions => {:published => true}
named_scope :unpublished, :conditions => {:published => false}
end
而在rails3中:
- class Post < ActiveRecord::Base
- default_scope order('title')
- scope :published, where(:published => true)
- scope :unpublished, where(:published => false)
- end
class Post < ActiveRecord::Base
default_scope order('title')
scope :published, where(:published => true)
scope :unpublished, where(:published => false)
end
对于查找方法, rails2:
- Post.find(:all, :conditions => {:author => "Joe"}, :includes => :comments, :order => "title", :limit => 10)
Post.find(:all, :conditions => {:author => "Joe"}, :includes => :comments, :order => "title", :limit => 10)
在rails3:
- Post.where(:author => "Joe").include(:comments).order(:title).limit(10).<strong><span style="font-size: medium;">all</span></strong>
Post.where(:author => "Joe").include(:comments).order(:title).limit(10).all
7. 跨站点脚本(XSS)
在rails2中, 一般我们输入一段文本的时候, 我们往往会这样写: <%= h @post.body %>
那么在rails3中, <%= @post.body %> 默认输出的是一段safe html, 如果想输出XSS, 可以在前面加上 raw