Reputation: 639
I have many resources with advanced relations (habtm/hm/hmt etc..), everything you can imagine, but now it's time to write a beautiful routing for this API. The problem is, I can't fin the best practices about nested resource + advanced relations ro do my routing, here is what I am trying to do:
Here are my models with the concerned relations
# app/models/candidate.rb
class Candidate < ApplicationRecord
include Sociable, Locatable
belongs_to :user
has_many :sourcing_accounts
has_many :accounts, through: :sourcing_accounts
has_many :users, through: :sourcing_accounts
end
# app/models/sourcing_account.rb
class SourcingAccount < ApplicationRecord
belongs_to :account
belongs_to :candidate
belongs_to :user
end
# app/models/user.rb
class User < ApplicationRecord
include Sociable
has_many :candidates
has_many :campaigns
has_many :sourcing_account
end
For this example, I am willing to permit to create a relation between a Candidate
and a User
by creating a SourcingAccount
.
resources :candidates do
resources :accounts
resources :users, only: [:index] do
post :remove
post :add
end
end
It generates:
v1_candidate_user_remove POST /v1/candidates/:candidate_id/users/:user_id/remove(.:format) api/v1/users#remove {:subdomain=>"api", :format=>:json}
v1_candidate_user_add POST /v1/candidates/:candidate_id/users/:user_id/add(.:format) api/v1/users#add {:subdomain=>"api", :format=>:json}
I did not found anything about this. Is there best practices ??? If not, what do you think would be the best for this case ?
Without precisions, Rails wants to route this to users#remove and users#add, which I think is totally wrong. These actions must not belong to the users controller.
Bonus:
What should look like a polymorphic route to create an Account
belonging to 2 other models (with presence validation) the 2 models are Source
and the other one is polymorphic [Candidate,User] # for example
, (they are Sociable
models)
Upvotes: 3
Views: 1094
Reputation: 102134
The best practice is to never* nest resources more than one level and only nest where the nesting is necessary or provides context.
Remember than any record with a unique id or uid can be fetched directly without context. So nesting member routes needlessly will make your API overcomplicated and really wordy.
DELETE /as/:id
is a lot better than
DELETE /as/:a_id/bs/:b_id/c/:id # Are you kidding me!
Lets say take a classical microblogging app as an example:
class User
has_many :posts, foreign_key: 'author_id'
has_many :comments
end
class Post
belongs_to :author, class_name: 'User'
end
class Comment
belongs_to :user
belongs_to :post
end
You could declare the routes as:
resources :users do
scope module: :users do
resources :posts, only: [:index]
resources :comments, only: [:index]
end
end
resources :posts do
resources :comments, module: :posts, only: [:index, :create]
end
resources :comments, only: [:index, :destroy, :update]
Using the module option lets us destinguish between the controllers for a "base resource" and its nested representation:
class API::V1::PostsController < ApplicationController
# GET /api/v1/posts
def index
@posts = Post.all
end
def show
# ...
end
def destroy
# ...
end
def update
# ...
end
end
# Represents posts that belong to a user
class API::V1::Users::PostsController < ApplicationController
# GET /api/v1/users/:user_id/posts
def index
@user = User.eager_load(:posts).find(params[:user_id])
respond_with(@user.posts)
end
end
In some cases you will want to nest to nest the create action as well if the resources should be created in the context of another:
class API::V1::Posts::CommentsController < ApplicationController
# PATCH /api/v1/posts/:post_id/comments
def create
@post = Post.find(params[:post_id])
@comment = @post.comments.create(comment_params)
respond_with(@comment)
end
# GET /api/v1/posts/:post_id/comments
def index
@post = Post.eager_load(:comments).find(params[:post_id])
respond_with(@post.comments)
end
end
Upvotes: 5