1

Right now I have two API namespaces, Api::V1 and Api::V2

I plan on implementing V2 slowly over time rather than all in one shot. However, since part of it exists, I'd like it if the client could send all HTTP requests to the V2 URL and let the server deal with it if that particular endpoint is not yet implemented.

Is there a way to route V2 requests to the V1 controller if the V2 controller does not have an action?

Simple Example:

routes:

namespace :api do
    namespace :v1 do
        resources :items, only: [:index]
    end
    namespace :v2 do
        resources :items, only: :create
    end
end

will produce the following endpoints:

  1. GET /api/v1/items
  2. POST /api/v2/items

The goal is to send a GET request to /api/v2/items and have it invoke Api::V1:ItemsController#index since the V2 controller does not have this method yet.

nick
  • 241
  • 2
  • 13

2 Answers2

1
namespace :api do

    namespace :v1, path: "v2" do
        # All endpoints that are on v1 of API
    end

    namespace :v2 do
        # All endpoints that are on v2 of API
    end

end

If you run rake routes here you'll see that they all match the route "api/v2/____#_____" but the ones in the top block invoke Api::V1 actions and the bottom block invoke Api::V2 actions, so youll have to move them from the top to the bottom as you implement those endpoints

nick
  • 241
  • 2
  • 13
0

I also have a versioned API. I haven't bumped to the next version yet. But I thought I'd share what I plan to do. This might be a little hairballed. And I have a feeling that this will be more useful to me (to think through my plan) than it is to you. So, sorry about that. But, here goes...

Before I start, I should say that I take a different sort of approach to my controller actions. In my apps, I like to delegate my controller actions to plain old ruby objects that I call 'managers'. Every controller has a 'manager_base'. So, in my controllers, I have something like this:

class ApplicationController < ActionController::Base

  private

  def do_action(action=nil)
    action ||= caller[0][/`.*'/][1..-2]
    manager.send("manage_#{action}", self, cookies, request)
  end

  def manager
    base = self.class.name.split('::')
    base.pop
    base << "#{controller_name.camelize}Managers::ManagerBase"
    base.join('::').constantize
  end

end 

class Api::V1::FooController < ApplicationController

  def index
    do_action
    render_result
  end

end

And then I also have:

class ManagerBase
  class << self

    def manage_index(controller, cookies, request)
      sub_out("Index", controller, cookies, request)             
    end

    def manage(controller, cookies, request)
      new(controller, cookies, request).manage
    end

  private

    def sub_out(method, controller, cookies, request)
      sub_manager(method).manage(controller, cookies, request)   
    end

  end # Class Methods

  def initialize(controller, cookies, request)
    @controller = controller
    @cookies = cookies
    @request = request
  end

end

class Api::V1::FooManagers::ManagerBase < ManagerBase
  class << self

    private

    def sub_manager(method)
      "Api::V1::FooManagers::#{method}Manager".constantize
    end  

  end # Class Methods
end

class Api::V1::FooManagers::IndexManager < Api::V1::FooManagers::ManagerBase
  def manage
    ... do stuff
  end
end

If you follow the bouncing ball, here's how my application flow goes:

  • index gets called on Api::V1::FooController
  • index calls do_action (inherited from ApplicationController) which, in turn calls manager (also inherited from ApplicationController)
  • manager returns the Api::V1::FooManagers::ManagerBase class
  • do_action then calls manage_index on Api::V1::FooManagers::ManagerBase
  • manage_index calls sub_out which in turn calls sub_manager
  • sub_manager returns the Api::V1::FooManagers::IndexManager
  • sub_out then calls manage on Api::V1::FooManagers::IndexManager
  • manage (the class method - inherited from ManagerBase) creates a new instance of Api::V1::FooManagers::IndexManager and then calls manage (the instance method) on the new instance.

As may or may not be apparent, when I move to Api::V2, I have two opportunities to 'hook' back into the Api::V1 versions of my managers (which is equivalent to using my V1 controller methods - your original question).

First, if I haven't implemented Api::V2::FooManagers::ManagerBase yet, I can cause ApplicationController.manager to fall back to the last implemented version of ManagerBase (i.e., Api::V1::FooManagers::ManagerBase). In which case I'll be using all of the Api::V1::FooManager sub managers (like IndexManager).

Second, if I've implemented Api::V2::FooManagers::ManagerBase but have not yet implemented Api::V2::FooManagers::IndexManager, then I can cause Api::V2::FooManagers#sub_manager to fall back to Api::V1::FooManagers::IndexManager.

Okay, I'm going to stop now. Thanks for the opportunity to think this through out loud. Apologies if it's a totally useless, hot mess.

jvillian
  • 19,953
  • 5
  • 31
  • 44
  • 1
    Wow! that is one hell of a scheme! it turns out I was able to solve my problem by using the :path option when namespacing an api module, and now i just need to move the controller actions from v1 to v2 as they are completed – nick Aug 10 '16 at 15:57
  • Well, thanks. I guess. I hope it doesn't get me into trouble down the line. How and where do you use the :path option? – jvillian Aug 10 '16 at 16:11
  • too much for a comment, i posted it as an answer check that out – nick Aug 10 '16 at 19:37