Assume a standard has_many :through relationship among three models
class Person < ActiveRecord::Base
has_many :memberships, :dependent => :destroy
has_many :clubs, :through => :memberships
end
class Club < ActiveRecord::Base
has_many :memberships, :dependent => :destroy
has_many :persons, :through => :memberships
end
class Membership < ActiveRecord::Base
belongs_to :person
belongs_to :club
end
In an API driven application, you would expect to expose URIs that:
- list the clubs that person x belongs to
- list the persons that are members of club y
- (…and the usual collection of CRUD methods…)
My first thought is to implement a pair of nested routes that map to the MembersController, something like:
GET /clubs/:club_id/memberships => members_controller#index
GET /persons/:person_id/memberships => members_controller#index
… but here it gets a bit weird.
Both routes map to the same members_controller method (index). That’s no problem — I can look in the params hash to see if a :club_id or a :person_id is given, and apply appropriate scoping on the members_controller table.
But I’m not certain we want to expose Member objects to the end user at all. A more intuitive pair of routes (at least from the user’s perspective) might be:
GET /clubs/:club_id/persons
GET /persons/:person_id/clubs
… which would return a list of persons and a list of clubs (respectively).
But if you do it this way, what controller and action would you map these routes to? Is there any convention in Rails that offers guidance? Or is this strayed far enough off the track that I should just implement it any way I see fit?
I’ve ended up implementing routes and controllers that accomplish the following:
Here are some of the routes it recognizes:
Note that in the following examples, I have NOT included the Devise and CanCan constructs for authentication and authorization. They’re easy to add.
Here is the routing file:
The controllers were surprisingly simple. For PersonsController and ClubsController, the only non-standard thing was in the :index methods, where we look for the presence of :club_id or :person_id in the parameters and scope accordingly:
The MembershipsController is only slightly more complicated: it detects :person_id and/or :club_id in the parameters hash and applies scoping accordingly. If both :person_id and :club_id are present, we can assume it refers to a unique membership object: