Rails为支持REST开发提供的3个工具:
1. map.resources
map.connect '', :controller=>'home', :action=>'welcome'
map.connect '/post/:id', :controller=>'post', :action=>'show'
map.connect '/weather/:year/:month/:date', :controller=>'weather', :action=>'archive'
但是用这种方式在使用指定路由方式的时候,会给Rails开发人员增加许多重复性的劳动:
在视图中会这么使用代码:
link_to 'Home', :controller=>'home', :action=>'welcome'
link_to 'Show Post', :controller=>'post', :action=>'show', :id=>@post
link_to 'Weather Last Christmas',
:controller=>'weather', :action=>'archive', :year=>'2006', :month=>'12', :date=>'25'
重复性就是出现在手动指定controller, action,以及类似id的这些参数上。那么采用下面的路由方式会带来很大的改观:
map.home '', :controller=>'home',:action=>'welcome'
map.post '/post/:id', :controller=>'post', :action=>'show'
map.weather_archive '/weather/:yaer/:month/:date', :controller=>'weather', :action=>'archive'
这样创建了路由之后,Rails会自动提供两个新的URL方法:{named_route}_path和{named_route}.url例如map.home路由相应的这两个方法即为home_path和home_url, 开发人员可以通过这两个方法来生成指定路由的URL。二者的区别是:home_url方法生成的是绝对路径,包括主机地址和请求地址(例如[url]http://localhost:3000/[/url])而home_path生成的是相对路径(/welcome),因此,使用命名路由后,之前的rhtml里可以写做:
link_to 'Home', home_path
link_to 'Show Post', post_path(@post)
link_to 'Weather Last Christmas', weather_archive_path('2006', '12', '25')
虽然命名路由功能非常强大,但是对于REST开发来说还是显得力不从心,加入每个资源都拥有7个CRUD方法的话,就不得不创建大量的命名路由,不过rails提供了一个新的路由方法:map.resources, 创建REST方式的路由时,它可以提供类似于脚手架(scaffolding)的功能。举例来说:
map.resources :exercises
Rails会根据控制器中的方法自动创建所有对应的路由, 以及相应生成的URL方法:
7个CRUD方法:
  Action HTTP method URL 生成URL的方法
1 index GET /exercises exercises_path
2 show GET /exercises/1 exercises(:id)
3 new GET /exercises/new new_exercise_path
4 edit GET /exercises/1;edit edit_exercise_path(:id)
5 create POST /exercises exercises_path
6 update PUT /exercises/1 exercises_path
7 destroy DELETE /exercises/1 exercises_path(:id)
2. respond_to
在非REST应用中,我们看到的方法可能如下:
def index
@exercises = Exercise.find(:all)
end
默认情况下,这个方法会自动显示相应的index模板文件(index.rhtml),但是如果改为使用REST, 就能够以多种形式表现来自同一资源的数据,为了实现这点,在index方法中添加respond_to方法的调用:
def index
@exercises = Exercise.find(:all)
respond_to do |format|
format.html
format.xml {render :xml => @exercises.to_xml}
end
添加的respond_to 方法,会根据HTTP请求的头信息来返回相应的模板。因此如果用户发送普通的web请求,server返回HTML格式信息,如果是XML请求,server会以XML格式返回exercises对象。
3. scaffold_resource
scaffold_resource的脚手架的语法规则:
script/generate scaffold_resource ModelName [field:type ]…
--------------------------------------------
创建Exercise_app
1. rails exercise
2. .script/generate scaffold_resource exercise name:string user_id:integer
          #用scaffold_resource创建了exercise类及其字段之后,rails会自动在route.rb中添加map.resources :exercises代码,并且会生成相应的migration.在index方法中, respond_to会根据头信息中的请求方式来确定返回的内容,Accept: text/html返回html形式的内容, Accept: text/xml返回xml形式的内容,但是如果发出/exercises.xml的GET请求,即使头信息为Accept: text/html也会返回xml模板,这样可以解决RSS的问题(RSS会发出类似/exercises.xml的GET请求,但是头信息为Accept:text/html)。
---------------------------------------------
使用restful_authentication插件,下载插件拷贝到vender/plugins目录下.
关于restful_authentication插件的详细说明,见里面的readme
---
3..script/generate authenticated user sessions
第一个参数指定了在注册时创建的模型对象(user或者account),在创建模型的同时,还会创建一个基本的,包含create方法的控制器
第二个参数指定了session会话控制器名称。该控制器用来处理站点中的登录和注销功能。
4. 配置路由config/route.rb
ActionController::Routing::Routes.draw do |map|
  map.resources :exercises
  map.home '', :controller=>'sessions', :action=>'new'      #默认首页路由(删除public下的index.html文件), [url]http://localhost:3000/[/url]
  map.resources :users, :sessions                                      
  map.signup '/signup', :controller=>'users', :action=>'new'
  map.login '/login', :controller=>'sessions', :action=>'new'
  map.logout '/logout', :controller=>'sessions', :action=>'destory'

end
5.将users_controller,sessions_controller中的"include AuthenticatedSystem"代码剪切到application_controller中,如果想支持"自动登录"功能,需要在application_controller中添加before_filter :login_from_cookie
6.将sessions视图下的new.rhtml文件第一行代码session_path改成sessions_path,此时打开[url]http://localhost:3000/[/url]就会出现默认首页(登录页面)
7.在layout目录下,删除由scaffold_resource生成的模板文件,新建application.rhtml来改变一下外观,代码:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"[url]http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd[/url]">
<html>
  <head>
    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
    <title><%=@title || "Exercisr"%></title>
    <link rel="stylesheet" type="text/css" href="[url]http://yui.yahooapis.com/2.2.2/build/reset-fonts-grids/reset-fonts-grids.css[/url]">
    <%= stylesheet_link_tag 'styles' %>
    <%= javascript_include_tag :defaults %>
  </head>
  <body>
    <div id="doc2" class="yui-t2">
      <div id="hd" class="box grad blue">
        <%= p_w_picpath_tag 'grad_black.png'%>
        <h1 id="masthead"><%= link_to "Exercisr", home_path%></h1>
      </div>
      <div id="bd">
        <div id="yui-main">
          <div class="yui-b">
            <%= yield%>
          </div>
        </div>
        <%if logged_in?%>
          <div class="yui-b sidebar">
            <ul>
              <li><%=link_to 'Exercises', exercises_path%></li>
              <li><%# link_to 'Workouts', workouts_path%></li>
              <li><%# link_to 'Goals', goals_path%></li>
              <li><%# link_to 'Logout', logout_path%></li>
            </ul>
          </div>
<%else%>
            <div class="yui-b sidebar">
            <ul>
              <li><%= link_to 'signup', signup_path%></li>
            </ul>
          </div>

        <%end%>
      </div>
      <div id="ft" class="box grad blue"><%= p_w_picpath_tag 'grad_white.png'%></div>
    </div>
  </body>
</html>
------下面的步骤用来设计页面导航
在sessions_controller中实现了3个方法中的2个-- create和destory,并且在这两个方法中都调用了redirect_back_or_default方法,现在redirect_back_or_default方法按照默认方式返回登录页面(即使已经登录),而我们现在需要让其返回到本系统的首页。
应该创建一个漂亮的,具有交互性的首页,能够为用户提供演示数据、教程等内容。但是现在我们创建一个静态页面,显示一些欢迎信息即可。同时在session模板中添加相应的模板代码。
8.在session_controller中添加一个welcome的action
def welcome
end
在welcome.rhtml模板中添加:
<h1>Welcome to Exercisr</h1>
<h3>A RESTful place to keep track of your workouts</h3>
之后在/config/routes.rb中添加一个新的命名路由(注意路由顺序):
ActionController::Routing::Routes.draw do |map|
  map.resources :exercises
  map.home '', :controller=>'sessions', :action=>'new'
  map.resources :users, :sessions
  map.welcome '/welcome', :controller=>'sessions', :action=>'welcome'
  map.signup '/signup', :controller=>'users', :action=>'new'
  map.login '/login', :controller=>'sessions', :action=>'new'
  map.logout '/logout', :controller=>'sessions', :action=>'destory'

end
最后修改sessions_controller中默认的跳转页面,使得create方法跳转到新创建的welcome页面,destroy返回到登录页面
def create
...
    redirect_back_or_default(welcome_path)
...   
end
 
#############
def destroy
...
    redirect_back_or_default(login_path)
end
当用户注册之后,也应该跳转到welcome页面,所以修改user_controller的create方法:
def create
        redirect_back_or_default(welcome_path)
end
现在打开[url]http://localhost:3000/signup[/url]注册一个新帐户,测试一下刚设计的功能。
-------------------
创建模型关联
Exercise类:
class Exercise < ActiveRecord::Base
  belongs_to :user
  validates_presence_of :name
  validates_uniqueness_of :name, :scope=>:user_id #一个用户不能输入两个相同的运动名
end
User类:
class User < ActiveRecord::Base
  # ...
  has_many :exercises, :dependent => :destroy, :order=>'name asc'
#...
end
修改Exercise控制器的作用域
之前对每个资源都生成了基本的CRUD操作,这些代码处于全局(Global)作用域中。需要降低代码的作用域。意思是:
譬如在对exercises生成的基本操作中,index这个action的代码是@exercises=Exercise.find(:all),而现在需要的是在用户登录之后只寻找跟登录后的user相关联的exercises,也就是要将@exercises=Exercise.find(:all)写成@exercises=current_user.exercises.build.修改exercise_controller代码:
将其中的Exercise类的操作都换成current_user.exercises,将new方法换成build方法,具体代码如下:
class ExercisesController < ApplicationController
  before_filter :login_required
  # GET /exercises
  # GET /exercises.xml
  def index
    @exercises =current_user.exercises.find(:all)
    respond_to do |format|
      format.html # index.rhtml
      format.xml  { render :xml => @exercises.to_xml }
    end
  end
  # GET /exercises/1
  # GET /exercises/1.xml
  def show
    @exercise =current_user.exercises.find(params[:id])
    respond_to do |format|
      format.html # show.rhtml
      format.xml  { render :xml => @exercise.to_xml }
    end
  end
  # GET /exercises/new
  def new
    @exercise = current_user.exercises.build
  end
  # GET /exercises/1;edit
  def edit
    @exercise = current_user.exercises.find(params[:id])
  end
  # POST /exercises
  # POST /exercises.xml
  def create
    @exercise = current_user.exercises.build(params[:exercise])
    respond_to do |format|
      if @exercise.save
        flash[:notice] = 'Exercise was successfully created.'
        format.html { redirect_to exercise_url(@exercise) }
        format.xml  { head :created, :location => exercise_url(@exercise) }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @exercise.errors.to_xml }
      end
    end
  end
  # PUT /exercises/1
  # PUT /exercises/1.xml
  def update
    @exercise = current_user.exercises.find(params[:id])
    respond_to do |format|
      if @exercise.update_attributes(params[:exercise])
        flash[:notice] = 'Exercise was successfully updated.'
        format.html { redirect_to exercise_url(@exercise) }
        format.xml  { head :ok }
      else
        format.html { render :action => "edit" }
        format.xml  { render :xml => @exercise.errors.to_xml }
      end
    end
  end
  # DELETE /exercises/1
  # DELETE /exercises/1.xml
  def destroy
    @exercise = current_user.exercises.find(params[:id])
    @exercise.destroy
    respond_to do |format|
      format.html { redirect_to exercises_url }
      format.xml  { head :ok }
    end
  end
end
-------------
利用局部模板:
1.在创建或者修改exercise一项时,可以提取出来一个局部模板。在views/exercises下新建一个_form.rhtml文件,代码如下:
<p>
  <label for="exercise-name">Name</label><br />
   <%= f.text_field :name %>
</p>
<p>
   <%= submit_tag "Save" %>
</p>
在new方法和edit视图中调用这个局部模板:
new.rhtml代码:
<h1>New exercise</h1>
<%= error_messages_for :exercise %>
<% form_for(:exercise, :url => exercises_path) do |f| %>
<%= render :partial=>"form", :locals=>{:f=>f}%>
<% end %>
<%= link_to 'Back', exercises_path %>
 
edit.rhtml代码:
<h1>Editing exercise</h1>
<%= error_messages_for :exercise %>
<% form_for(:exercise, :url => exercise_path(@exercise), :html => { :method => :put }) do |f| %>
  <%=render :partial=>'form', :locals=>{:f=>f}%>
<% end %>
<%= link_to 'Show', exercise_path(@exercise) %> |
<%= link_to 'Back', exercises_path %>
把new模板的内容可以放到index页面中,直接可以在index中添加新的运动。修改后的index.rhtml:
 
<%# 介绍性的提示信息%>
<h1>Exercises</h1>
<p>On this page you can create and manage the exercises that you use in your workouts.</p>
<p>You can also view reports on your progress for each exercises</p>
<table id="exercise_details">
  <tr>
    <th>Name</th>
  </tr>
<%= render :partial=>'exercise', :collection=>@exercises%>
</table>
<br /><br />
<h1>Add a New Exercise</h1>
<div id="add_exercise">
  <% form_for(:exercise, :url=>exercises_path, :html=>{:id=>'new_exercise'}) do |f|%>
      <%=render :partial=>'form', :locals=>{:f=>f}%>
        <%end%>
  </div>
---------
workout资源
用来记录用户在某一天锻炼的完成程度,该资源中,不仅应该包括进行锻炼的日期,还包括一个用来描述锻炼类型(上身、腹部或者双臂)的可选文本字段,具体实现过程:
1.ruby script/generate scaffold_resource workout date:date label:string user_id:integer
2.rake db:migrate
模型关系:
user.rb: has_many :workouts
workout.rb: belongs_to :user, :dependent => :destroy
修改控制器workout_controller作用域:
添加before_filter=>:login_required
修改7个方法中的@workout表达式,特别注意的是new和create中间对new方法的调用要改成build:
class WorkoutsController < ApplicationController
  before_filter :login_required
  # GET /workouts
  # GET /workouts.xml
  def index
    @workouts = current_user.workouts.find(:all)
    respond_to do |format|
      format.html # index.rhtml
      format.xml  { render :xml => @workouts.to_xml }
    end
  end
  # GET /workouts/1
  # GET /workouts/1.xml
  def show
   @workout = current_user.workouts.find(params[:id])
    respond_to do |format|
      format.html # show.rhtml
      format.xml  { render :xml => @workout.to_xml }
    end
  end
  # GET /workouts/new
  def new
    @workout = current_user.workouts.build
  end
  # GET /workouts/1;edit
  def edit
    @workout = current_user.workouts.find(params[:id])
  end
  # POST /workouts
  # POST /workouts.xml
  def create
    @workout = current_user.workouts.build(params[:workout])
    respond_to do |format|
      if @workout.save
        flash[:notice] = 'Workout was successfully created.'
        format.html { redirect_to workout_url(@workout) }
        format.xml  { head :created, :location => workout_url(@workout) }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @workout.errors.to_xml }
      end
    end
  end
  # PUT /workouts/1
  # PUT /workouts/1.xml
  def update
   @workout = current_user.workouts.find(params[:id])
    respond_to do |format|
      if @workout.update_attributes(params[:workout])
        flash[:notice] = 'Workout was successfully updated.'
        format.html { redirect_to workout_url(@workout) }
        format.xml  { head :ok }
      else
        format.html { render :action => "edit" }
        format.xml  { render :xml => @workout.errors.to_xml }
      end
    end
  end
  # DELETE /workouts/1
  # DELETE /workouts/1.xml
  def destroy
    @workout = current_user.workouts.find(params[:id])
    @workout.destroy
    respond_to do |format|
      format.html { redirect_to workouts_url }
      format.xml  { head :ok }
    end
  end
end
修改视图:
1.修改workout的index.rhtml页面:
<h1>Listing workouts</h1>
<table>
  <tr>
    <th>Date</th>
    <th>Label</th>

  </tr>
<%= render :partial=>'workout', :collection=>@workouts %>
</table>
<br />
<h1>Add a New Workout</h1>
<div id="add_workout">
  <%form_for(:workout, :url=>workouts_path, :html=>{:id=>'new_workout'}) do |f|%>
    <%= render :partial=>'form', :locals=>{:f=>f}%>
      <%end%>
</div>
2._workout.rhtml局部模板:
<tr>
  <td><%=h workout.date.to_s(:long) %></td>
  <td><%=h workout.label %></td>
  <td><%= link_to p_w_picpath_tag("display.gif", {:title=>"View Workout Details"}),workout_path(workout)%></td>
  <td><%= link_to p_w_picpath_tag("edit_photo.gif", {:title=>"Edit Workout Date/label"}), edit_workout_path(workout) %></td>
  <td><%= link_to p_w_picpath_tag("delete_photo.gif", {:title=>"Delete Workout"}), workout_path(workout), :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
 
3.new.rhtml:
<h1>New workout</h1>
<%= error_messages_for :workout %>
<% form_for(:workout, :url => workouts_path) do |f| %>
<%= render :partial=>'form', :locals=>{:f=>f}%>
<% end %>
<%= link_to 'Back', workouts_path %>
4.edit.rhtml
<h1>Editing workout</h1>
<%= error_messages_for :workout %>
<% form_for(:workout, :url => workout_path(@workout), :html => { :method => :put }) do |f| %>
  <%= render :partial=>'form', :locals=>{:f=>f}%>
<% end %>
<%= link_to 'Show', workout_path(@workout) %> |
<%= link_to 'Back', workouts_path %>
5._form.rhtml局部模板:
<p>
   <b>Date</b><br />
   <%= f.date_select :date %>
</p>
<p>
   <b>Label</b><br />
   <%= f.text_field :label %>
</p>
<p>
   <%= submit_tag "Save" %>
</p>
-----------------
记录锻炼过程中的数据:
在创建完每次的workout对象之后,需要记录如下信息:
1.本次锻炼进行了哪几项运动。(exercises)
2.每项运动完成了几组()
3.每次运动使用的重量或者负荷(resistance)
4.每组运动进行的次数(repitition)
按照这些需求,我们应该新建这样一张表,表中每一行必须表示一组运动,其中记录的数据包括:workoutID(外键,用来查询关联的workout)、exerciseID(外键,用来查询关联的exercise)、该组运动使用的重量以及该组包含的运动次数。
创建这个资源,名字叫做activities(活动).
过程:
1.ruby script/generate scaffold_resource activity workout_id:integer exercise_id:integer resistance:integer repetitions:integer
2.rake db:migrate
3.删除生成的layout文件
---4.创建模型之间的关系:
class Activity < ActiveRecord::Base
  belongs_to :exercise
  belongs_to :workout
  validates_presence_of :resistance, :repetitions

end
class Workout < ActiveRecord::Base
  belongs_to :user
  has_many :activities, :dependent=>:destroy
  has_many :exercises, :through=>:activities

  validates_presence_of :date
end
 
class User < ActiveRecord::Base
  # Virtual attribute for the unencrypted password
  has_many :exercises, :dependent => :destroy, :order=>'name asc'
  has_many :workouts, :dependent => :destroy
  has_many :activities, :through=>:workouts
...
end
 
1
 
修改activity路由
ActionController::Routing::Routes.draw do |map|
# map.resources :activities
  map.resources :workouts do |workout|
    workout.resources :activities
  end
...
这是一个嵌套式路由的写法,简单的来说,先要得到workouts的资源,然后根据workouts得到activities资源,如果不然没有意义。
修改activities_controller
跟以前一样,在activities_controller里首先要加上before_filter :login_required,确保只有用户登录之后访问控制器中的方法,其次,由于使用了嵌套路由,所以在限制查询范围方面需要进行一些修改。在之前的控制器中,我们将查询范围限定为当前用户,而现在对于嵌套资源来说,需要在父资源中进行查询。
 
所以还得首先得出父资源的实例变量值。所以定义一个protected方法,然后在控制器开始加上before_filter :find_workout
这个protected的find_workout方法写在activities_controller的最下面:
 
protected
def find_workout
  @workout= current_user.workouts.find(params[:workout_id])
end
然后再在最上面写上before_filter :find_workout,这样就得到了实例变量@workout
在其他的方法中做出修改如下:
class ActivitiesController < ApplicationController
  before_filter :login_required
  before_filter :find_workout
  # GET /activities
  # GET /activities.xml
  def index
    @activities = @workout.activities.find(:all)
    respond_to do |format|
      format.html # index.rhtml
      format.xml  { render :xml => @activities.to_xml }
    end
  end
  # GET /activities/1
  # GET /activities/1.xml
  def show
   @activity = @workout.activities.find(params[:id])
    respond_to do |format|
      format.html # show.rhtml
      format.xml  { render :xml => @activity.to_xml }
    end
  end
  # GET /activities/new
  def new
    @activity = @workout.activities.build
  end
  # GET /activities/1;edit
  def edit
   @activity = @workout.activities.find(params[:id])
  end
  # POST /activities
  # POST /activities.xml
 
def create
  @activity = @workout.activities.build(params[:activity])
  respond_to do |format|
    if @activity.save
      flash[:notice] = 'Activity was successfully created.'
      format.html { redirect_to workout_path(@workout) }
      format.xml  { head :created, :location => activity_url(@workout,@activity) }
    else
      format.html { render :action => "new" }
      format.xml  { render :xml => @activity.errors.to_xml }
    end
  end
end
  # PUT /activities/1
  # PUT /activities/1.xml
 
def update
  @activity = @workout.activities.find(params[:id])
   respond_to do |format|
     if @activity.update_attributes(params[:activity])
       flash[:notice] = 'Activity was successfully updated.'
       format.html { redirect_to workout_path(@workout) }
       format.xml  { head :ok }
     else
       format.html { render :action => "edit" }
       format.xml  { render :xml => @activity.errors.to_xml }
     end
   end
end
  # DELETE /activities/1
  # DELETE /activities/1.xml
  def destroy
   @activity = @workout.activities.find(params[:id])
    @activity.destroy
    respond_to do |format|
      format.html { redirect_to activities_url }
      format.xml  { head :ok }
    end
  end
  protected
  def find_workout
    @workout= current_user.workouts.find(params[:workout_id])
  end
end
修改activity视图模板:
这里修改activity模板跟以前的类似,有一点就是因为activity资源必须是以workout资源为前提的,所以在参数传递时要加上@workout,下面首先来创建一个_activity.rhtml模板:
<tr>
    <td><%=h activity.exercise.name %></td>
    <td><%=h activity.resistance %></td>
    <td><%=h activity.repetitions %></td>
    <td><%= link_to p_w_picpath_tag("edit_photo.gif", {:title=>"Edit Exercise"}), edit_activity_path(@workout,activity) %></td>
    <td><%= link_to p_w_picpath_tag("delete_photo.gif"), activity_path(@workout,activity), :confirm => 'Are you sure?', :method => :delete %></td>
  </tr>
然后创建一个_form.rhtml模板:
<p>
   <%= f.collection_select :exercise_id, current_user.exercises.find(:all), :id, :name, :prompt=>"Select an Exercise" %>
</p>
<p>One set of <%= f.text_field :repetitions %>with <%= f.text_field :resistance%>pounds of resistance.
</p>
<p>
   <%= submit_tag "Save" %>
</p>
 
修改edit.rhtml模板:
<h1>Editing activity</h1>
<%= error_messages_for :activity %>
<% form_for(:activity, :url => activity_path(@workout,@activity), :html => { :method => :put }) do |f| %>
    <%= render :partial=>'form', :locals=>{:f=>f}%>
<% end %>
<%= link_to 'Back', activities_path(@workout) %>
修改index视图模板:
<h1>Listing activities</h1>
<table>
  <tr>
    <th>Exercise</th>
    <th>Resistance</th>
    <th>Repetitions</th>
  </tr>
  <%= render :partial=>'activity', :collection=>@activities%>
</table>
<br />
<%= link_to 'New activity', new_activity_path(@workout) %>
修改new.rhtml模板:
<h1>New activity</h1>
<%= error_messages_for :activity %>
<% form_for(:activity, :url => activities_path) do |f| %>
  <%= render :partial=>'form', :locals=>{:f=>f}%>
<% end %>
<%= link_to 'Back', activities_path(@workout) %>
最后,修改show.rhtml模板:
<p>
  <b>Exercise:</b>
  <%=h @activity.exercise %>
</p>
<p>
  <b>Resistance:</b>
  <%=h @activity.resistance %>
</p>
<p>
  <b>Repetitions:</b>
  <%=h @activity.repetitions %>
</p>
<%= link_to 'Edit', edit_activity_path(@workout,@activity) %> |
<%= link_to 'Back', activities_path(@workout) %>
修改workout控制器的show方法:
def show
    @workout = current_user.workouts.find(params[:id])
    @activities= @workout.activities.find(:all, :include=>:exercise)
    respond_to do |format|
      format.html # show.rhtml
      format.xml  { render :xml => @workout.to_xml }
    end
  end
修改workout/show.rhtml模板:
<h1><%= h @workout.label%>Workout on <%= h @workout.date.to_s(:long)%></h1>
  <table>
    <tr><th>Exercise</th><th>Reps</th><th>Resistance</th></tr>
<%=render :partial=>'activities/activity', :collection=>@activities%>
    </table>
  <h3>Add Exercise to this workout</h3>
<%form_for(:activity, :url=>activities_path(@workout)) do |f|%>
  <%= render :partial=>'activities/form', :locals=>{:f=>f}%>
  <%end%>
    <%=link_to "Back", workouts_path%>
---改进添加Activity的表单
如果在选择运动时发现运动名称不在select表单中,后面需要加入一个文本框,用来添加运动名称:
先在activity的_form.rhtml模板中增加一个文本框,用来输入要添加的运动名称:
<p>
  <%= f.collection_select :exercise_id, current_user.exercises.find(:all), :id, :name, :prompt=>"Select an Exercise" %>
  or add a new exercise:
<%= f.text_field :new_exercise_name%>

</p>
<p>One set of <%= f.text_field :repetitions %>with <%= f.text_field :resistance%>pounds of resistance.
</p>
<p>
  <%= submit_tag "Save" %>
</p>
这里将文本框内容传递给了:new_exercise_name,在activity模型类中创建一个虚拟属性new_exercise_name
class Activity < ActiveRecord::Base
  belongs_to :exercise
  belongs_to :workout
  validates_presence_of :resistance, :repetitions
  attr_accessor :new_exercise_name

  before_save :create_exercise_if_submitted
  def create_exercise_if_submitted
    create_exercise(:user_id=>workout.user_id,
      :name=>new_exercise_name) unless new_exercise_name.blank?
  end
end
 
--------跟踪锻炼目标:
下面创建一个资源,用来跟踪锻炼目标(体重、血糖等)。该资源属性包括要跟踪的目标名称,要达到的目标,以及该目标上次锻炼后的结果(以便计算当前与目标之间的差距),确定属性后,创建goal资源:
ruby script/generate scaffold_resource goal name:string value:decimal last:decimal user_id:integer
 
我们还需要记录在当前尚未实现目标时的数据。例如,加入用户的目标是跟踪自己的体重,那么可能希望每周都记录一下自己的体重,以便查看是否有变化,我们将这个资源命名为result,包含以下几个属性:该对象关联的目标(goal对象),本次记录的时间,以及记录的数据。
ruby script/generate scaffold_resource result goal_id:integer date:date value:decimal
rake db:migrate
删除生成的layout
---
修改模型类:
class Goal < ActiveRecord::Base
  belongs_to :user
  has_many :results, :dependent => :destroy
  validates_presence_of :value

end
class Result < ActiveRecord::Base
  belongs_to :goal
  validates_presence_of :date, :value
end
class User < ActiveRecord::Base
...
  has_many :goals
...
end
创建嵌套路由:
 
ActionController::Routing::Routes.draw do |map|
  # map.resources :results
  # map.resources :goals
  #map.resources :activities
  map.resources :goals do |goal|
    goal.resources :results
  end
...
配置控制器:
class GoalsController < ApplicationController
  before_filter :login_required
  # GET /goals
  # GET /goals.xml
  def index
    @goals = current_user.goals.find(:all)
    respond_to do |format|
      format.html # index.rhtml
      format.xml  { render :xml => @goals.to_xml }
    end
  end
  # GET /goals/1
  # GET /goals/1.xml
  def show
    @goal =  current_user.goals.find(params[:id])
    @results=@goal.results.find(:all, :order=>'date desc')
    respond_to do |format|
      format.html # show.rhtml
      format.xml  { render :xml => @goal.to_xml }
    end
  end
  # GET /goals/new
  def new
    @goal =  current_user.goals.build
  end
  # GET /goals/1;edit
  def edit
    @goal =  current_user.goals.find(params[:id])
  end
  # POST /goals
  # POST /goals.xml
  def create
    @goal =  current_user.goals.build(params[:goal])
    respond_to do |format|
      if @goal.save
        flash[:notice] = 'Goal was successfully created.'
        format.html { redirect_to goal_url(@goal) }
        format.xml  { head :created, :location => goal_url(@goal) }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @goal.errors.to_xml }
      end
    end
  end
  # PUT /goals/1
  # PUT /goals/1.xml
  def update
    @goal =  current_user.goals.find(params[:id])
    respond_to do |format|
      if @goal.update_attributes(params[:goal])
        flash[:notice] = 'Goal was successfully updated.'
        format.html { redirect_to goal_url(@goal) }
        format.xml  { head :ok }
      else
        format.html { render :action => "edit" }
        format.xml  { render :xml => @goal.errors.to_xml }
      end
    end
  end
  # DELETE /goals/1
  # DELETE /goals/1.xml
  def destroy
    @goal =  current_user.goals.find(params[:id])
    @goal.destroy
    respond_to do |format|
      format.html { redirect_to goals_url }
      format.xml  { head :ok }
    end
  end
end
 
由于result是一个嵌套资源,所以对于Results控制器来说,需要按照前面修改activities控制器的方法来修改:
class ResultsController < ApplicationController
  # GET /results
  # GET /results.xml
  before_filter :login_required
  before_filter :find_goal
  def index
    @results = @goal.results.find(:all)
    respond_to do |format|
      format.html # index.rhtml
      format.xml  { render :xml => @results.to_xml }
    end
  end
  # GET /results/1
  # GET /results/1.xml
  def show
    @result = @goal.results.find(params[:id])
    respond_to do |format|
      format.html # show.rhtml
      format.xml  { render :xml => @result.to_xml }
    end
  end
  # GET /results/new
  def new
    @result = @goal.results.build
  end
  # GET /results/1;edit
  def edit
    @result = @goal.results.find(params[:id])
  end
  # POST /results
  # POST /results.xml
  def create
    @result = @goal.results.build(params[:result])
    respond_to do |format|
      if @result.save
        flash[:notice] = 'Result was successfully created.'
        format.html { redirect_to goal_url(@goal) }
        format.xml  { head :created, :location => result_url(@result) }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @result.errors.to_xml }
      end
    end
  end
  # PUT /results/1
  # PUT /results/1.xml
  def update
    @result = @goal.results.find(params[:id])
    respond_to do |format|
      if @result.update_attributes(params[:result])
        flash[:notice] = 'Result was successfully updated.'
       format.html { redirect_to goal_url(@goal) }
        format.xml  { head :ok }
      else
        format.html { render :action => "edit" }
        format.xml  { render :xml => @result.errors.to_xml }
      end
    end
  end
  # DELETE /results/1
  # DELETE /results/1.xml
  def destroy
    @result = @goal.results.find(params[:id])
    @result.destroy
    respond_to do |format|
      format.html { redirect_to goal_url(@goal) }
      format.xml  { head :ok }
    end
  end
  protected
  def find_goal
    @goal=current_user.goals.find(params[:goal_id])
  end

end
 
配置视图:
首先在layout/appliaction.rhtml中去掉<li><%# link_to 'Goals', goals_path%></li>中的#
1.goal视图:
这里的修改于之前的workout模板的修改基本一致。
新建_form.rhtml
  <p>
    <label for="goal-name">Name of the Goal:</label><br />
    <%= f.text_field :name %>
  </p>
  <p>
    <label for="goal-value">Goal to Reach:</label><br />
    <%= f.text_field :value %>
  </p>
  <p>
    <label for="goal-last">Current Result:</label><br />
    <%= f.text_field :last %>
  </p>
<p> <%= submit_tag "Save" %></p>
 
在edit和new中调用此局部模板:
edit.rhtml
<h1>Editing goal</h1>
<%= error_messages_for :goal %>
<% form_for(:goal, :url => goal_path(@goal), :html => { :method => :put }) do |f| %>
  <%= render :partial=>'form', :locals=>{:f=>f}%>
<% end %>
<%= link_to 'Show', goal_path(@goal) %> |
<%= link_to 'Back', goals_path %>
new.rhtml
<h1>New goal</h1>
<%= error_messages_for :goal %>
<% form_for(:goal, :url => goals_path) do |f| %>
<%= render :partial=>"form", :locals=>{:f=>f}%>
<% end %>
<%= link_to 'Back', goals_path %>
新建_goal.rhtml模板:
<tr>
    <td><%=h goal.name %></td>
    <td></td>
    <td><%= link_to p_w_picpath_tag("display.gif", {:title=>'View Report'}), goal_path(goal) %></td>
    <td><%= link_to p_w_picpath_tag('edit_photo.gif',{:title=>"Edit Goal Details"}), edit_goal_path(goal) %></td>
    <td><%= link_to p_w_picpath_tag("delete_photo.gif"), goal_path(goal), :confirm => 'Are you sure?', :method => :delete %></td>
  </tr>
最后在index页面(goals/index.rhtml)中添加显示两个局部模板的代码:
<h1>Listing goals</h1>
<table>
  <tr>
    <th>Name</th>
  </tr> 

  <%=render :partial=>"goal", :collection=>@goals%>

</table>
<br />
<h1>Add a New Goal</h1>
<div id="add_goal">
  <% form_for(:goal, :url=>goals_path, :html=>{:id=>'new_goal'}) do |f|%>
    <%= render :partial=>'form', :locals=>{:f=>f}%>
    <%end%>
</div>
2.result视图
同样,我们首先创建两个局部模板文件,然后在视图模板中调用这两个局部模板。其次,由于result是一个嵌套资源,所以还需要向所有相关的命名路由传递@goals变量
_form.rhtml:
<p>
  <label for="">Date</label><br />
  <%= f.date_select :date %>
</p>
<p>
  <label for="">Value</label><br />
  <%= f.text_field :value %>
</p>
<p>
  <%= submit_tag "Save" %>
</p>
 
_result.rhtml
<tr>
  <td><%=h result.date.to_s(:long) %></td>
  <td><%=h result.value %></td>
  <td><%= link_to p_w_picpath_tag("edit_photo.gif", {:title=>"Edit Result Details"}), edit_result_path(@goal,result) %></td>
  <td><%= link_to p_w_picpath_tag("delete_photo.gif",{:title=>"Delete Result"}), result_path(@goal,result), :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
 
new.rhtml
<h1>New result</h1>
<%= error_messages_for :result %>
<% form_for(:result, :url => results_path(@goal)) do |f| %>
 <%= render :partial=>'form', :locals=>{:f=>f}%>
<% end %>
<%= link_to 'Back', results_path(@goal) %>
 
edit.rhtml
<h1>Editing result</h1>
<%= error_messages_for :result %>
<% form_for(:result, :url => result_path(@goal,@result), :html => { :method => :put }) do |f| %>
  <%= render :partial=>"form", :locals=>{:f=>f}%>
<% end %>
<%= link_to 'Back to Goal', goal_path(@goal) %>
 
index.rhtml
<h1>Listing results for<%= h @goal.name%></h1>
<table>
  <tr>
    <th>Date</th>
    <th>Value</th>
  </tr>
 <%= render partial=> 'result', :collection=>@results%>
</table>
<br />
<%= link_to "Back to Goal", goal_path(@goal)%>
最后,修改goal资源的show模板:
<h1>Results for <%= h @goal.name%></h1>
<table>
  <tr><th>Date</th><th>Value</th></tr>
<%= render :partial=>'results/result', :collection=>@results%>
</table>
<h3>Record New Result for this Goal</h3>
<% form_for :result, :url=>results_path(@goal) do |f|%>
  <%= render :partial=>'results/form', :locals=>{:f=>f}%>
  <%end%>
  <%= link_to 'Back', workouts_path%>
按照前面的设计,还需要存储最近一次锻炼结果的数据。在这里要借助rails强大的回调函数,可以解决这个问题。打开models/results.rb添加after_create回调函数来设置相关goal资源的last属性。
class Result < ActiveRecord::Base
  belongs_to :goal
  validates_presence_of :date, :value
  after_create :update_last_result
  def update_last_result
    goal.last=value
    goal.save
  end

end
  • 收藏
  • 评论
  • 举报
提问和评论都可以,用心的回复会被更多人看到 评论
发布评论
相关文章