我们来看下rails3相比rails2, 进步在哪里, 优势又在什么地方.
1. 脚本命令
旧的命令 新的用法
script/generate rails g
script/console rails c
script/server rails s
script/dbconsole rails db
2. 配置文件
rails2: config/environment.rb
1
2
3
4
5
6
7
8
|
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
1
2
3
4
5
6
7
|
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 中:
1
2
3
|
map.resources
:posts
do
|post|
post.resources
:comments
end
|
而在rails3中, 表达更为形象:
1
2
3
|
resources
:posts
do
resources
:comments
end
|
对于一些复杂的路由, rails2:
1
2
3
|
post.resources
:comments
,
:member
=> {
:preview
=>
:post
},
:collection
=> {
:archived
=>
:get
}
|
在rails3中可以这样表达:
1
2
3
4
5
6
7
8
|
resources
:comments
do
member
do
post
:preview
end
collection
do
get
:archived
end
end
|
不够简洁? 我们还可以这样做:
1
2
3
4
|
resources
:comments
do
post
:preview
,
:on
=>
:member
get
:archived
,
:on
=>
:collection
end
|
对于基本路由, rails2:
1
|
map.connect
'login'
,
:controller
=>
'session'
,
:action
=>
'new'
|
那么在rails3中:
1
|
match
'login'
=>
'session#new'
|
对于具名路由, rails2:
1
|
map.login
'login'
,
:controller
=>
'session'
,
:action
=>
'new'
|
在rails3中:
1
|
match
'login'
=>
'session#new'
,
:as
=>
:login
|
对于程序根路由, rails2:
1
|
map.root
:controller
=>
'users'
,
:action
=>
'index'
|
rails3:
1
|
root
:to
=>
'users#index'
|
对于遗留路由, rails2:
1
2
|
map.connect
':controller/:action/:id'
map.connect
':controller/:action/:id.:format'
|
那么在rails3中写法更优雅:
1
|
match
':controller(/:action(/:id(.:format)))'
|
对于路由参数, rals2:
1
|
map.connect
'/articles/:year/:month/:day'
,
:controller
=>
'posts'
,
:action
=>
'index'
|
rails3:
1
|
match
'/articles/:year/:month/:day'
=>
"posts#index"
|
那么对于存档请求, 比如rails2:
1
2
3
|
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中:
1
|
match
'/articles(/:year(/:month(/:day)))'
=>
"posts#index"
|
指定请求方式, rails2:
1
2
|
map.connect
'/articles/:year'
,
:controller
=>
'posts'
,
:action
=>
'index'
,
:conditions
=> {
:method
=>
:get
}
|
在rails3中:
1
2
3
|
match
'/articles/:year'
=>
"posts#index"
,
:via
=>
:get
#或者更简单的:
get
'/articles/:year'
=>
"posts#index"
|
对于跳转, rails3:
1
2
3
|
match
'signin'
,
:to
=> redirect(
"/login"
)
match
'users/:name'
,
:to
=> redirect {|params|
"/#{params[:name]}"
}
|
路由约束: rails2中实际上使用了 :requirements 符号
1
2
|
map.connect
'/:year'
,
:controller
=>
'posts'
,
:action
=>
'index'
,
:requirements
=> {
:year
=> /\d{
4
}/ }
|
在rails3中:
1
|
match
'/:year'
=>
"posts#index"
,
:constraints
=> {
:year
=> /\d{
4
}/}
|
1
2
3
4
5
6
7
8
|
: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:
1
2
3
|
get
'hello'
=> proc { |env| [
200
, {},
"Hello Rack"
] }
get
'rack_endpoint'
=> PostsController.action(
:index
)
get
'rack_app'
=> CustomRackApp
|
4. Bundler与ActionController
一个典型的rails应用, 我们一般需要在 environment.rb 指定你的 gems:
1
2
|
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
1
2
|
gem
"haml"
gem
"chronic"
,
'0.2.3'
|
然后运行 $ bundle, 该命令会会取得并下载然后安装编译这些gems
然后运行 $ bundle package 把gem源移到/vendor/cache中去.
这样rails应用中的gem与系统中的gem就不会相冲突.
一般的控制器语法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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
....
|
改进的语法:
1
2
3
4
5
6
7
8
9
10
11
|
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:
1
2
3
4
5
6
|
def
welcome(user, subdomain)
subject
'Welcome to TestApp'
recipients user.email
body
:user
=> user,
:subdomain
=> subdomain
end
|
1
|
UserMailer.deliver_welcome(user, subdomain)
|
在rails3中:
1
2
3
4
5
6
7
|
def
welcome(user, subdomain)
@user
= user
@subdomain
= subdomain
:to
=> user.email,
:subject
=>
"Welcome to TestApp"
)
end
|
1
|
UserMailer.welcome(user, subdomain).deliver
|
相比rails2, 我们在rails3下实现一个mail要简单的多:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
class
UserMailer < ActionMailer::Base
"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中, 我们经常使用下面的方法来进行查询:
1
|
@posts
= Post.find(
:all
,
:conditions
=> {
:published
=>
true
})
|
该方式将立即查询数据库然后返回Posts数组
而在rails3中:
1
|
@posts
= Post.where(
:published
=>
true
)
|
该方法不会查询数据库, 仅仅返回一个 ActiveRecord::Relation 对象, 然后:
1
2
3
4
5
6
7
|
@posts
= Post.where(
:published
=>
true
)
if
params[
:order
]
@posts
=
@posts
.order(params[
:order
])
end
@posts
.
each
do
|p|
...
#在这里进行查询, 实现延迟加载
end
|
对于命名范围, 在rails2中:
1
2
3
4
5
|
class
Post < ActiveRecord::Base
default_scope
:order
=>
'title'
named_scope
:published
,
:conditions
=> {
:published
=>
true
}
named_scope
:unpublished
,
:conditions
=> {
:published
=>
false
}
end
|
而在rails3中:
1
2
3
4
5
|
class
Post < ActiveRecord::Base
default_scope order(
'title'
)
scope
:published
, where(
:published
=>
true
)
scope
:unpublished
, where(
:published
=>
false
)
end
|
对于查找方法, rails2:
1
|
Post.find(
:all
,
:conditions
=> {
:author
=>
"Joe"
},
:includes
=>
:comments
,
:order
=>
"title"
,
:limit
=>
10
)
|
在rails3:
1
|
|
1
2
|
Post.find(
:all
,
:conditions
=> {
:author
=>
"Joe"
},
:includes
=>
:comments
,
:order
=>
"title"
,
:limit
=>
10
)
|
7. 跨站点脚本(XSS)
在rails2中, 一般我们输入一段文本的时候, 我们往往会这样写: <%= h @post.body %>
那么在rails3中, <%= @post.body %> 默认输出的是一段safe html, 如果想输出XSS, 可以在前面加上 raw