0
votes

About routing, If I do something like this:

resources :students
resources :teachers

I will get something like:

students GET /students(.:format) students#index
...
teachers GET /teachers(.:format) teachers#index
...

Changing to:

resources :students, controller: :users
resources :teachers, controller: :users

will give me:

students GET /students(.:format) users#index
teachers GET /teachers(.:format) users#index

Note that now, both resources are using the same controller Users and the same action index. But what I need, instead of using the same index action, is the students resource to use actions prefixed by students like students_index and teachers resources prefixed by teachers like teacher_index.

In other words, I want bin/rails routes to give me the following output:

students GET /students(.:format) users#students_index
teachers GET /teachers(.:format) users#teachers_index

I know that I can do the same with:

get 'students', to: 'users#students_index'

But there is a way to do the same with resources?

2
Probably looking for either collection or member - Mingsheng
why don't use use subclasses for your controller? so controller user, and subclasses student and teacher? if you want really this, you need to map each of the CURL actions. - Roger
@Rogier both student and teacher are users. This routes is for an API. I dont want to have a single endpoint like: POST http://localhost:3000/users to create the student and teacher. For this I have to everytime specify what type of user I am creating. - WeezHard
@psantos, ok. Whats the diff in students_index vs teachers_index ? - Roger
@psantos go with the subclass approach (its the cleanest imo). You could wrap both resources in a namespace (namespace :users do .... end) for clean(er) urls (i.e. /users/students/new) - Roger

2 Answers

0
votes

I don't think there's a way to do that with resources helper. What you could do (if it's only the index action you wanna override) is add an except, like this:

resources :students, controller: :users, except: [:index]
resources :teachers, controller: :users, except: [:index]

then, as you already suggested, do the individuals index actions like that:

get 'students', to: 'users#students_index', as: :student
get 'teachers', to: 'users#teachers_index', as: :teacher

Or you could reconsider the structure of your controllers... Good luck!

0
votes

There is a far better way to do this as you might have surmised - inheritance.

# app/controllers/users_controller.rb
class UsersController < ApplicationController

  delegate :singular, :plural, :param_key, to: :model_name

  before_action :set_resource, only: [:show, :edit, :update, :destroy]
  before_action :set_resources, only: [:index]

  def initialize
    @model_name = resource_class.model_name
    super
  end

  def show
  end

  def index
  end

  def new
    @resource = resource_class.new
    set_resource
  end

  def create
    @resource = resource_class.new(permitted_attributes)

    if @resource.save
      redirect_to @resource
    else
      set_resource
      render :new
    end
  end

  def edit
  end

  def update
    if @resource.update(permitted_attributes)
      redirect_to @resource
    else
      set_resource
      render :edit
    end
  end

  def destroy
    @resource.destroy
    redirect_to action: "index"
  end

  # ...

  private

  # Deduces the class of the model based on the controller name
  # TeachersController would try to resolve Teacher for example.
  def resource_class
    @resource_class ||= controller_name.classify.constantize
  end 

  # will set @resource as well as @teacher or @student
  def set_resource
    @resource ||= resource_class.find(params[:id])
    instance_variable_set("@#{singular}", @resource)
  end

  # will set @resources as well as @teachers or @students
  def set_resources
    @resources ||= resource_class.all
    instance_variable_set("@#{plural}", @resources)
  end

  def permitted_attributes
    params.require(param_key).permit(:a, :b, :c)
  end
end

# app/controllers/teachers_controller.rb
class TeachersController < UsersController
end

# app/controllers/students_controller.rb
class StudentsController < UsersController
end

# routes.rb
resources :students
resources :teachers

This lets you follow the regular Rails convention over configuration approach when it comes to naming actions and views.

The UsersController base class uses quite a bit of magic through ActiveModel::Naming both to figure out the model class and stuff like what to name the instance variables and the params keys.