Accessing custom actions from another route and general routing confusion

Ok, I’m really new to rails, so go easy on me :sweat_smile: I’ve been looking around for a few days for a nice solution this or some best practice and I haven’t found a concise solution

I have a task app where tasks belong to a project and a route for all tasks. Tasks have statuses for completion, archived etc and some scopes with associated actions in TasksController:

def complete
    @task.update(status_id: 4)
    redirect_back(fallback_location: root_url)
  end

  def incomplete
    @task.update(status_id: 1)
    redirect_back(fallback_location: root_url)
  end

  def completed
    @tasks = Task.completed.not_archived.newest
  end

  def archive
    @task.update(archived: true)
    redirect_to root_path
  end

  def archived
    @tasks = Task.archived.newest
  end
routes.rb

resources :priorities
  resources :statuses

  resources :tasks do
    member do
      put 'complete'
      put 'incomplete'
      put 'archive'
    end
  end
  resources :projects
  get 'completed', to: 'tasks#completed', as: 'completed_tasks'
  get 'archived', to: 'tasks#archived', as: 'archived_tasks'

  root 'tasks#index'

I have two scenarios that are causing me quite a bit of confusion.

Originally, I had the list of tasks from tasks#index on the projects/show view (It felt icky), which worked and showed what it need to show, but trying to filter out completed/archived tasks would not work.

TasksController.rb

def index
    @tasks = if params[:completed].present?
               Task.completed.newest
             elsif params[:archived].present?
               Task.archived.newest
             else
               Task.not_completed.not_archived.newest
             end
  end

So I changed approach and looked at nesting the route.

 resources :projects do
    resources :tasks do
      member do
        put 'complete'
        put 'incomplete'
        put 'archive'
      end
    end
  end

I hadn’t really expected the member routes to work TBH, and yeah, they didn’t. But trying projects/2/tasks?completed=true also would not work and since (As my probably incorrect) understand is that nested routes hit the controller of the resource that is nested? So I tried using the new routes (Bot with and without the members from tasks in my routes.rb. and they would just error vs and failing 302. So I got very confused.

Then I removed the nested resource and tried mirroring the param setup from tasks#index in projects#show:

ProjectsController.rb

def show
    @tasks = if params[:completed].present?
               @project.tasks.completed.newest
             elsif params[:archived].present?
               @project.archived.newest
             else
               @project.tasks.not_completed.not_archived.newest
             end
  end

And projects/2/tasks?completed=true will now work but I’m back to square one.

As much as I think I could probably solve this by futzing something awful together, I’m really aware that theres a gap and I’m missing something. I would really like to know the Rails™️ way of doing this as I know convention is important, I’ve just clearly missed something along the way.

Apologies if I’m missing any useful context, I’m really early into Rails and probably moving a bit too fast. Any help would be sincerely appreciated.

Thanks in advance!

glancing at this, judging by the indentation, you want this 2nd and 3rd line nested under 1st line, in which case the word “do” is missing.

So first off - I’m sorry that I don’t quite understand what you’re asking (and so I don’t have an exact answer for you). But this sort of situation sounds familiar to me, so here’s what I normally do to fix it. First off, run bundle exec rails routes (or bundle exec rails routes > routes.txt can be handy too). This will give you the full list of the URLs that are defined, and exactly which method on which controller they are going to call. That can be really helpful when iterating on routes.rb and figuring out the nesting, whether to use resource vs resources, etc. That normally straightens things out for me, or at least saves time figuring out why it’s not working.

The second thing I do is to try to keep to “resourceful routing” - see How DHH organizes his Rails controllers - Jerome's Adventures in Software . It might require a bit of rethinking of the controller methods, but it will align your controller methods with your routes.rb routing declarations, and I find it helps make things run more smoothly. So for example, if you are updating something (e.g. a task), always make that a method called update, if you are showing a list of something (tasks or projects) then always make that an index method. You can then decide if your other lists of things (e.g. list of completed tasks) are implemented as a query parameter (/projects/2/tasks?state=complete) or as a separate index action (/projects/2/tasks/completed) on a different controller. It sounds like a lot of work at first (and I find picking controller names is harder than just coming up with yet another method on a mega-controller) but it pays off over time.

Like I say, sorry I don’t have a specific answer, but I hope this might help a little bit.