Ruby for Rails 最佳实践十六
2013-10-08 14:42
381 查看
第十六章 改进控制器和视图
第2版的 R4RMusic 的控制器动作及相应模版总结控制器 | 描述 | 动作方法名 | 主模板 |
Customer | 登录 注销 注册一个新帐号 给购物车添加一个版本 查看购物车 结帐(购买完毕) | login logout signup add_to_cart view_cart check_out | main/welcome.rhtml main/welcome.rhtml main/welcome.rhtml customer/view_cart.rhtml customer/view_cart.rhtml customer/check_out.rhtml |
Main | 欢迎访问者 显示给定时代的所有作品 | welcome show_period | main/welcome.rhtml main/show_period.rhtml |
Composer | 显示某作曲者作品的所有版本 | show | composer/show.rhtml |
Edition | 显示版本的详细出版信息 | show | edition/show.rhtml |
Instrument | 显示给定乐器的所有作品 | show | instrument/show.rhtml |
Work | 显示给定作品的所有版本 | show | work/show.rhtml |
一、为视图模板定义辅助方法
1. 组织和访问定制的辅助方法在 app/helper 目录下是对于每个控制器的辅助文件 controller_helper.rb,例如,composer_helper.rb 包含下面内容
module ComposerHelper
end
把方法定义到辅助文件中可以有助于减少重复代码,可以编写一个自动产生链接的辅助方法
module ComposerHelper
def link_to_composer(composer)
link_to (composer.whole_name,
:controller => "composer",
:action => "show",
:id => composer.id)
end
end
下面是如何在模版中使用这个新的 link_to_composer 方法的例子
<ul>
<% @composers.each do |composer| %>
<li><%= link_to_composer(composer) %></li>
<% end %>
</ul>
使用其它辅助文件中的方法
方法一:在辅助方法的控制器中,将该方法声明为 helper,例如
class MainController < ApplicationController
helper :composer
# etc.
end
方法二:把辅助文件放到通用的辅助文件 application_helper.rb 中,这样对于所有的控制器和模版来说这些方法都是可见的。
2. 为 R4RMusic 定制的辅助方法
方法 | 定义于 | 通过 helper 包含到这些控制器中 |
link_to_composer link_to_work link_to_edition link_to_edition_title link_to_instrument two_dec | composer_helper.rb work_helper.rb edition_helper.rb edition_helper.rb instrument_helper.rb application_helper.rb | customer, edition, main composer, customer, edition, instrument, main customer, work composer, instrument main 可被所有控制器访问 |
def link_to_composer(composer)
link_to(composer.whole_name,
:controller => "composer",
:action => "show",
:id => composer.id)
end
def link_to_edition(edition)
link_to edition.description,
:controller => "edition",
:action => "show",
:id => edition.id
end
def link_to_edition_title(edition)
link_to edition.nice_title,
:controller => "edition",
:action => "show",
:id => edition.id
end
def link_to_work(work)
link_to(work.nice_title,
:controller => "work",
:action => "show",
:id => work.id)
end
def link_to_instrument(instrument)
link_to instrument.name,
:controller => "instrument",
:action => "show",
:id => instrument.id
end
第六个辅助方法将一个浮点数转换为一个包含两个十进制小数的字符串
module ApplicationHelper
def two_dec(n)
sprintf("%.2f", n)
end
end
二、编码和部署部分视图模板
1. 剖析主模板首先查看 composer/show.html.erb 模板文件
<% @page_title = "Editions of works by #{@composer.whole_name}" %>
<h2 class="info"><%= @page_title %></h2>
<p>Click on any edition to see details.</p>
<%= render :partial => "editions" %>
其中 render 方法检查部分模版名字,在它前面添加一下划线,在它后面添加一个 .html.erb 后缀,下面是 composer/_editions.html.erb
<ul>
<% @composer.editions.map do |edition| %>
<li><%= link_to_edition_title(edition) %>
(<%= edition.publisher.name %>, <%= edition.year %>)</li>
<% end %>
</ul>
2. 在欢迎视图模板中使用部分模版
(1)生成作曲者链接清单
创建作曲者链接清单 composer/_list.html.erb
<ul>
<% @composers.each do |composer| %>
<li><%= link_to_composer(composer) %></li>
<% end %>
</ul>
将下面这一行放到主模板 main/welcome.html.erb 中
<%= render :partial => "composer/list" %>
(2)生成乐器链接清单
部分模版 instrument/_list.html.erb
<ul>
<% @instruments.each do |instrument|%>
<li><%= link_to_instrument(instrument) %></li>
<% end %>
</ul>
创建 instrument 控制器:
F:\ruby_project\R4Rmusic>ruby script/generate controller instrument show
完成 instrument_controller.rb 文件:
class InstrumentController < ApplicationController
helper :work, :edition
def show
@instrument = Instrument.find(params[:id])
end
end
(3)生成音乐时代的链接清单
产生音乐时代的链接清单 main/_period_list.html.erb
<ul>
<% @periods.each do |period| %>
<li><%= link_to period,
:controller => "main",
:action => "show_period",
:id => period %>
<% end %>
</li>
</ul>
我们也需要为在 welcome 模板中加入部分模板
<%= render :partial => "period_list" %>
(4) 完整的欢迎模板(views/main/welcome.html.erb)
<% if @c %>
<h3>Welcome, <%= @c.first_name %>.</h3>
<% end %>
<h2 class="info">Browse works by...</h2>
<table>
<tr>
<th>...Period</th>
<th>...Composer</th>
<th>...Instrument</th>
</tr>
<tr>
<td>
<%= render :partial => "period_list" %>
</td>
<td>
<%= render :partial => "composer/list" %>
</td>
<td>
<%= render :partial => "instrument/list" %>
</td>
</tr>
</table>
<% if @c %>
<%= render :partial => "favorites" %>
<% else %>
<h2 class="info">Log in or create an account</h2>
<table border="1">
<tr>
<th>Log in to your account</th>
<th>Sign up for an account</th>
</tr>
<tr>
<td><%= render :partial => "customer/login" %></td>
<td><%= render :partial => "customer/signup" %></td>
</tr>
</table>
<% end %>
三、更新主控制器
Welcome 动作新面孔(main_controller.rb):class MainController < ApplicationController
helper :work, :composer, :instrument
def welcome
@composers = Composer.find(:all).sort_by do |composer|
[composer.last_name, composer.first_name, composer.middle_name]
end
@periods = Work.all_periods
@instruments = Instrument.find(:all, :order => "name ASC" )
end
def show_period
@period = params[:id]
works = Work.find(:all).select do |work|
(work.period == @period) || (work.century == @period)
end
@editions = Edition.of_works(works)
end
end
四、加入顾客注册的登录动作
1. 登录和注册部分模板(1)注册模板 customer/_signup.html.erb
<% form_tag :controller => "customer",
:action => "signup" do %>
<p>First name: <%= text_field "customer", "first_name" %> </p>
<p>Last name: <%= text_field "customer", "last_name" %> </p>
<p>User name: <%= text_field "customer", "nick" %> </p>
<p>Password: <%= password_field "customer", "password" %> </p>
<p>Email address: <%= text_field "customer", "email" %> </p>
<p><input type="Submit" value="Sign up"/></p>
<% end %>
(2)登录模板 customer/_login.html.erb
<% form_tag :controller => "customer",
:action => "login" do %>
<p>User name: <%= text_field "customer", "nick" %></p>
<p>Password: <%= password_field "customer", "password" %></p>
<p><input type="Submit" value="Log in"/></p>
<% end %>
2. 登录和保存会话状态
def login
pw,nick = params[:customer].values_at(*%w{password nick})
c = Customer.find_by_nick(nick)
if c && Digest::SHA1.hexdigest(pw) == c.password
@session['customer'] = c.id
redirect_to :controller => "main", :action => "welcome"
else
report_error("Invalid login")
end
end
3. 用 before_filter 看守动作(ApplicationController.rb)
class ApplicationController < ActionController::Base
layout("base")
before_filter :get_customer
def get_customer
if session['customer']
@c = Customer.find(session['customer'])
end
end
end
如果 session 中已经保存了用户的 ID ,那么数据库中可以查找出该对象则 @c 不为空,否则 @c 为空那么,主界面中显示提示用户登录:
<% if @c %>
<%= render :partial => "favorites" %>
<% else %>
<h2 class="info">Log in or create an account</h2>
#
# display of login and signup forms handled here
#
<% end %>
我们还希望过滤器能在顾客没有登录的情况下拦截请求动作(CustomerController.rb)
before_filter :authorize, :except => ["signup","login"]
def authorize
return true if @c
report_error("Unauthorized access; password required")
end
其中 report_error 是一个***的、通过错误报告方法,被定义在 application.rb 中
class ApplicationController < ActionController::Base
# prior code here, then:
private
def report_error(message)
@message = message
render("main/error")
return false
end
end
该方法把传入的消息赋值给 @message 实例变量,呈现在 app/views/main/error.html.erb
4. 实现注册功能(CustomerController.rb)
def signup
c = Customer.new(params[:customer])
c.password = Digest::SHA1.hexdigest(c.password)
c.save
session['customer'] = c.id
redirect_to :controller => "main", :action => "welcome"
end
使用 before_filter 技术,进行表单数据的验证,创建一个 new_customer 的过滤器,仅让它在 signup 动作之前作为过滤器执行
before_filter :new_customer, :only => ["signup"]
def new_customer
applicant = params[:customer]
if Customer.find_by_nick(applicant['nick'])
report_error("Nick already in use. Please choose another.")
elsif Customer.find_by_email(applicant['email'])
report_error("Account already exists for that email address")
end
end
5. 编写顾客注销脚本(在 app/view/layout/base.html.erb 中加入注销按钮,加在 body 中)
<table>
<tr>
<td><%= link_to "Home",
:controller => "main",
:action => "welcome" %></td>
<% if @c %>
<td><%= link_to "View cart",
:controller => "customer",
:action => "view_cart" %></td>
<td><%= link_to "Log out",
:controller => "customer",
:action => "logout" %></td>
<% end %>
</tr>
</table>
五、处理顾客订单
首先为 app/controllers/customer_controller.rb 添加一个动作def view_cart
end
然后实现 view_cart.html.erb 视图
<% @page_title = "Shopping cart for #{@c.nick}" %>
<%= render :partial => "cart" %>
以下是购物车视图的 customer/_cart.html.erb 部分模板
<table border="1">
<tr>
<th>Title</th>
<th>Composer</th>
<th>Publisher</th>
<th>Price</th>
<th>Copies</th>
<th>Subtotal</th>
</tr>
<% @c.editions_on_order.each do |edition| %>
<% count = @c.copies_of(edition) %>
<tr>
<td><%= link_to_edition_title(edition) %></td>
<td>
<% edition.composers.each do |composer| %>
<%= link_to_composer(composer) %>
<% end %></td>
<td><%= edition.publisher.name %></td>
<td class="price"><%= two_dec(edition.price) %>
<td class="count"><%= count %></td>
<td class="price"><%= two_dec(edition.price * count) %></td>
</tr>
<% end %>
<tr><td colspan="5">TOTAL</td>
<td class="price"><%= two_dec(@c.balance) %></td>
</tr>
</table>
<p><%= link_to("Complete purchases",
:controller => "customer",
:action => "check_out") %></p>
2. 查看和购买一个版本
查看版本的主模板,edition/show.html.erb
<% @page_title = @edition.nice_title %>
<h2 class="info"><%= @page_title %></h2>
<%= render :partial => "details" %>
查看版本的部分模板,edition/_details.html.erb 部分模板
<ul>
<li>Edition: <%= @edition.description %></li>
<li>Publisher: <%= @edition.publisher.name %></li>
<li>Year: <%= @edition.year %></li>
<li>Price: <%= two_dec(@edition.price) %></li>
<% if @c %>
<li><%= link_to "Add to cart",
:controller => "customer",
:action => "add_to_cart",
:id => @edition.id %></li>
<% end %>
</ul>
<h3>Contents:</h3>
<ul>
<% @edition.works.each do |work| %>
<li><%= link_to_work(work) %> (<%= link_to_composer(work.composer) %>)</li>
<% end %>
</ul>
3. 定义 add_to_cart 动作(CustomerController.rb)
def add_to_cart
e = Edition.find(params[:id])
order = Order.create(:customer => @c,
:edition =>
e)
if order
redirect_to :action => "view_cart"
else
report_error("Trouble with saving order")
end
end
4. 完成订单
在 CustomerController.rb 中定义
def check_out
@c.check_out
end
再定义 app\views\customer\check_out.html.erb 作为结算完成视图,内容如下
<% @page_title = "Orders complete" %>
<h2>Thanks for your order, <%= @c.first_name %>!</h2>
六、通过动态代码使页面人性化
1. 从排名到喜好在 app/models/customer.rb 中定义
def rank(list)
list.uniq.sort_by do |a|
list.select {|b| a == b }.size
end.reverse
end
def composer_rankings
rank(edition_history.map {|ed| ed.composers }.flatten)
end
def instrument_rankings
rank(work_history.map {|work| work.instruments }.flatten)
end
def favorites(thing,options)
limit = options[:count]
rankings = send("#{thing}_rankings")
return rankings[0,limit].compact
end
其中 favorites 方法根据 thing 参数动态选择用户喜好,给定的 count 选择数组相应的条目。如果该值比排名方法返回的数组 size 要大,那么用 nil 进行填充,最后 compact 将删除这些 nil。
2. 实际使用中的 favorites 特性
最后让我们来实现 main/_favorites.html.erb 部分模板
<h2 class="info">Your favorites</h2>
<% fav_composers = @c.favorites :composer,
:count => 3 %>
<% fav_instruments = @c.favorites :instrument,
:count => 3 %>
<ul>
<% fav_composers.each do |composer| %>
<li><%= link_to_composer(composer) %></li>
<% end %>
<% fav_instruments.each do |instrument| %>
<li><%= link_to_instrument(instrument) %></li>
<% end %>
</ul>
相关文章推荐
- Ruby for Rails 最佳实践Ⅵ
- Ruby for Rails 最佳实践Ⅸ
- Ruby for Rails 最佳实践Ⅹ
- Ruby for Rails 最佳实践七
- Ruby for Rails 最佳实践Ⅺ
- Ruby for Rails 最佳实践八
- Ruby for Rails 最佳实践Ⅻ
- Ruby for Rails 最佳实践九
- Ruby for Rails 最佳实践Ⅻ
- Ruby for Rails 最佳实践十
- Ruby for Rails 最佳实践十一
- Ruby for Rails 最佳实践ⅩⅢ
- Ruby for Rails 最佳实践十二
- Ruby for Rails 最佳实践Ⅰ
- Ruby for Rails 最佳实践十三
- Ruby for Rails 最佳实践Ⅱ
- Ruby for Rails 最佳实践ⅩⅤ
- Ruby for Rails 最佳实践十四
- Ruby for Rails 最佳实践Ⅲ
- Ruby for Rails 最佳实践ⅩⅥ