我最终实现了完成以下任务的路由和控制器:
- 它是完全 RESTful 的。
- 它是完全对称的: /persons/:person_id/clubs/:club_id/memberships 与 /clubs/:club_id/persons/:person_id/memberships 相同
- 它非常干燥。
- 普通用户永远不会看到会员身份:id。相反, :person_id 和 :club_id 用作引用特定成员资格的复合键。
- 它确实允许通过 :id 直接访问 Membership 对象,但这是为管理员保留的。
以下是它识别的一些路线:
/persons/:person_id/clubs/:club_id/memberships - a specific membership association
/clubs/:club_id/persons/:person_id/memberships - equivalent
/persons/:person_id/clubs - all the clubs that a specific person belongs to
/clubs/:club_id/persons - all the persons that belong to a specific club
/memberships/:id - access to a specific membership (accessible only to admin)
请注意,在以下示例中,我没有包含用于身份验证和授权的 Devise 和 CanCan 构造。它们很容易添加。
这是路由文件:
# file: /config/routes.rb
Clubbing::Application.routes.draw do
resources :persons, :except => [:new, :edit] do
resources :clubs, :only => :index
end
resources :clubs, :except => [:new, :edit] do
resources :persons, :only => :index
end
resources :memberships, :except => [:new, :edit]
# there may be clever ways to specify these routes using #resources and
# #collections and #member, but this ultimately is more straightforward
match "/persons/:person_id/clubs/:club_id/memberships" => "memberships#create", :via => :post
match "/persons/:person_id/clubs/:club_id/memberships" => "memberships#show", :via => :get
match "/persons/:person_id/clubs/:club_id/memberships" => "memberships#update", :via => :put
match "/persons/:person_id/clubs/:club_id/memberships" => "memberships#destroy", :via => :delete
match "/clubs/:club_id/persons/:person_id/memberships" => "memberships#create", :via => :post
match "/clubs/:club_id/persons/:person_id/memberships" => "memberships#show", :via => :get
match "/clubs/:club_id/persons/:person_id/memberships" => "memberships#update", :via => :put
match "/clubs/:club_id/persons/:person_id/memberships" => "memberships#destroy", :via => :delete
控制器出奇地简单。对于 PersonsController 和 ClubsController,唯一不标准的事情是在 :index 方法中,我们在参数和范围中相应地查找 :club_id 或 :person_id 的存在:
# file: /app/controllers/persons_controller.rb
class PersonsController < ApplicationController
respond_to :json
before_filter :locate_collection, :only => :index
before_filter :locate_resource, :except => [:index, :create]
def index
respond_with @persons
end
def create
@person = Person.create(params[:person])
respond_with @person
end
def show
respond_with @person
end
def update
if @person.update_attributes(params[:person])
end
respond_with @person
end
def destroy
@person.destroy
respond_with @person
end
private
def locate_collection
if (params.has_key?("club_id"))
@persons = Club.find(params[:club_id]).persons
else
@persons = Person.all
end
end
def locate_resource
@person = Person.find(params[:id])
end
end
# file: /app/controllers/clubs_controller.rb
class ClubsController < ApplicationController
respond_to :json
before_filter :locate_collection, :only => :index
before_filter :locate_resource, :except => [:index, :create]
def index
respond_with @clubs
end
def create
@club = Club.create(params[:club])
respond_with @club
end
def show
respond_with @club
end
def update
if @club.update_attributes(params[:club])
end
respond_with @club
end
def destroy
@club.destroy
respond_with @club
end
private
def locate_collection
if (params.has_key?("person_id"))
@clubs = Person.find(params[:person_id]).clubs
else
@clubs = Club.all
end
end
def locate_resource
@club = Club.find(params[:id])
end
end
MembershipsController 只是稍微复杂一点:它在参数哈希中检测 :person_id 和/或 :club_id 并相应地应用范围。如果 :person_id 和 :club_id 都存在,我们可以假设它指的是一个唯一的会员对象:
# file: /app/controllers/memberships_controller.rb
class MembershipsController < ApplicationController
respond_to :json
before_filter :scope_collection, :only => [:index]
before_filter :scope_resource, :except => [:index, :create]
def index
respond_with @memberships
end
def create
@membership = scope_collection.create(params[:membership])
respond_with @membership
end
def show
respond_with @membership
end
def update
if @membership.update_attributes(params[:membership])
end
respond_with @membership
end
def destroy
@membership.destroy
respond_with @membership
end
private
# apply :person_id and/or :club_id scoping if present in params hash
def scope_collection
@memberships = scope_by_parameters
end
def scope_resource
@membership = scope_by_parameters.first
end
def scope_by_parameters
scope_by_param_id(scope_by_param_id(Membership.scoped, :person_id), :club_id)
end
def scope_by_param_id(relation, scope_name)
(id = params[scope_name]) ? relation.where(scope_name => id) : relation
end
end