Ruby for Rails 最佳实践十六

第十六章 改进控制器和视图

第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

R4RMusic 最终版下载:http://kerryas.googlecode.com/files/r4rmusic.rar

 

一、为视图模板定义辅助方法

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>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值