私は、次のことを実現するルートとコントローラーを実装することになりました。
- それは完全にRESTfulです。
- これは完全に対称的です。/persons/:person_id/clubs/:club_id/membershipsは/clubs/:club_id / peoples /:person_id/membershipsと同じです。
- とてもドライです。
- 通常のユーザーには、Membership:idは表示されません。代わりに、:person_idと:club_idは、特定のメンバーシップを参照する複合キーとして機能します。
- :idによるメンバーシップオブジェクトへの直接アクセスは許可されますが、これは管理者用に予約されています。
認識されるルートの一部を次に示します。
/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