Rails routing and Rest : make resources alias

I have make some custom naming in Rails and I am not sure if I am still RestFull, and the way I am doing this is ugly.

I have two models activity, and task that both have managements: activity_management and task_management. Notice that managements are two differents models, because model behaviour are very different in my case.

In Rails, I start doing the following :

shallow do
   resources :activity do
        resources :activity_management
   end
   resources :task do
        resources :task_management
   end
end

And give me these route structure :

CREATE /activity/:activity_id/activity_management
SHOW /activity_management/:id

CREATE /task/:task_id/task_management
SHOW /task_management/:id

But I find these routes very long and giving repetitive informations (CREATE give twice the information that we are talking about an activity).

So I change to this :

shallow do
   resources :activity do
        resources :management, 
                  only: [:index, :new, :create], 
                  controller: :task_management
   end
   scope :activity do 
        resources :management, 
                  only: [:show, :edit, :update, :destroy], 
                  controller: :task_management
   end
   resources :activity do
        resources :management, 
                  only: [:index, :new, :create], 
                  controller: :activity_management
   end
   scope :activity do 
        resources :management, 
                  only: [:show, :edit, :update, :destroy], 
                  controller: :activity_management
   end
end

That give me this route structure :

CREATE /activity/:activity_id/management
SHOW /activity/management/:id

CREATE /task/:task_id/management
SHOW /task/management/:id

When the code start to be complicated, it's that I am doing something wrong... Do you see a simpler way to solve my problem ?

Answers


You're correct. Your approach is not very RESTful or conventional.

If you need to separate parts of your application that are public facing and a dashboard or admin section backend you would normally set it up like so:

Rails.application.routes.draw do

  # public facing routes
  resources :activities, only: [:show, :index]
  resources :tasks, only: [:show, :index]

  namespace :management do
    resources :activities
    resources :tasks
  end
end

Note that you should be using the plural form for your routes and controllers! This would create "public" routes at:

GET  /activities       ActivitiesController#index
GET  /activities/:id   ActivitiesController#show

And also namespaced routes:

GET|POST          /management/activities            
GET|PATCH|DELETE  /management/activities/:id
GET               /management/activities/new
GET               /management/activities/:id/edit

These routes would go to Management::ActivitiesController. This lets you provide different "representations" of a resource while still sticking to the rails conventions.

Updated.

If you want to do resource scoped roles you would do it like so:

# routes rb
Rails.application.routes.draw do
  resources :roles, except: [:new, :create]

  [:articles, :tasks].each do |resource_name|
    resources resource_name do
      resources :roles, only: [:create, :index], module: resource_name.to_s
    end
  end
end

       Prefix Verb   URI Pattern                           Controller#Action
        roles GET    /roles(.:format)                      roles#index
    edit_role GET    /roles/:id/edit(.:format)             roles#edit
         role GET    /roles/:id(.:format)                  roles#show
              PATCH  /roles/:id(.:format)                  roles#update
              PUT    /roles/:id(.:format)                  roles#update
              DELETE /roles/:id(.:format)                  roles#destroy
article_roles GET    /articles/:article_id/roles(.:format) articles/roles#index
              POST   /articles/:article_id/roles(.:format) articles/roles#create
     articles GET    /articles(.:format)                   articles#index
              POST   /articles(.:format)                   articles#create
  new_article GET    /articles/new(.:format)               articles#new
 edit_article GET    /articles/:id/edit(.:format)          articles#edit
      article GET    /articles/:id(.:format)               articles#show
              PATCH  /articles/:id(.:format)               articles#update
              PUT    /articles/:id(.:format)               articles#update
              DELETE /articles/:id(.:format)               articles#destroy
   task_roles GET    /tasks/:task_id/roles(.:format)       tasks/roles#index
              POST   /tasks/:task_id/roles(.:format)       tasks/roles#create
        tasks GET    /tasks(.:format)                      tasks#index
              POST   /tasks(.:format)                      tasks#create
     new_task GET    /tasks/new(.:format)                  tasks#new
    edit_task GET    /tasks/:id/edit(.:format)             tasks#edit
         task GET    /tasks/:id(.:format)                  tasks#show
              PATCH  /tasks/:id(.:format)                  tasks#update
              PUT    /tasks/:id(.:format)                  tasks#update
              DELETE /tasks/:id(.:format)                  tasks#destroy

This means to add a manager for a task you would do:

POST tasks/1/roles
{
  name: "manager",
  user_id: 666
}

# - resource_id   [integer]
# - resource_type [string]
# - user_id       [integer, foreign key]
# - name          [string]
class Role < ActiveRecord::Base
  belongs_to :resource, polymorpic: true
  belings_to :user
end

class Article < ActiveRecord::Base
  has_many :roles, as: :resource
  # ...
end

class Task < ActiveRecord::Base
  has_many :roles, as: :resource
  # ...
end
class User < ActiveRecord::Base
  has_many :roles
end

 # do the normal index, delete, update here
class RolesController < ApplicationController
  # ...
end

# abstract controller class for reuse
class NestedRolesController < ApplicationController

  before_filter :set_resource

  def create
    @role = @resource.roles.create(role_params)
    respond_with(@role)
  end

  def role_params
    params.require(:role).permit(:user_id, :name)
  end
end

class Articles::RolesController < NestedRolesController
  def set_resource
    @resource = Article.find(params[:article_id])
  end
end

class Tasks::RolesController < NestedRolesController
  def set_resource
    @resource = Task.find(params[:task_id])
  end
end

How about something like:

['task','activity'].each do |manager|
  resources :activity do
    resources :management, 
      only: [:index, :new, :create], 
      controller: "#{manager}_management".to_sym
  end
  scope :activity do 
    resources :management, 
      only: [:show, :edit, :update, :destroy], 
      controller: "#{manager}_management".to_sym
  end
end

This should get you the routes you're looking for (I think) with fairly little code, but the controller actions will all be namespaced as 'task_management/management#action' so you'd need to account for that:

resources :task do
  member do
    resources :management, module: 'task_management', shallow: true
  end
end

resources :activity do
  member do
    resources :management, module: 'activity_management', shallow: true
  end
end

You can alternatively list the matches explicitly, which is more verbose but will namespace the controller actions a little more cleanly:

resources :task do
  member do
    match '/management' => 'task_management#index', :via => :get
    match '/management/:id' => 'task_management#show', :via => :get
    match '/management' => 'task_management#create', :via => :post
    match '/management' => 'task_management#update', :via => :put
    match '/management' => 'task_management#delete', :via => :delete
  end
end

In order to have two shallow resources with the same name. You can provide a shallow_path to differentiate them. You can also shorten your resource URL with path. Don't forget to add shallow: true on the nested resources.

scope shallow_path: "activity"
  resources :activity do
    resources :activity_management, path: 'management', shallow: true
  end
end

scope shallow_path: "task"
  resources :task do
    resources :task_management, path: 'management', shallow: true
  end
end

Need Your Help

Awk if else issues

if-statement awk syntax

Bash points an arrow to "else" and says "syntax error" in a provocative whining tone.

Need help sorting the array alphabetically

c++ arrays sorting

I have to sort the last names in alphabetical order and I have been looking for hours on how to do this but my book which is garbage offers no examples other than integers so any help will be aweso...