Daniel Beardsley
Daniel Beardsley

Reputation: 20367

Dynamic URL -> Controller mapping for routes in Rails

I would like to be able to map URLs to Controllers dynamically based on information in my database.

I'm looking to do something functionally equivalent to this (assuming a View model):

map.route '/:view_name',
    :controller => lambda { View.find_by_name(params[:view_name]).controller }

Others have suggested dynamically rebuilding the routes, but this won't work for me as there may be thousands of Views that map to the same Controller

Upvotes: 14

Views: 5880

Answers (4)

Deepak Kumar
Deepak Kumar

Reputation: 972

This question is old, but I found it interesting. A fully working solution can be created in Rails 3 using router's capability to route to a Rack endpoint.

Create the following Rack class:

    class MyRouter
      def call(env)
        # Matched from routes, you can access all matched parameters
        view_name= env['action_dispatch.request.path_parameters'][:view_name]

        # Compute these the way you like, possibly using view_name
        controller= 'post' 
        my_action= 'show'

        controller_class= (controller + '_controller').camelize.constantize
        controller_class.action(my_action.to_sym).call(env)
      end
    end

In Routes

    match '/:view_name', :to => MyRouter.new, :via => :get

Hint picked up from http://guides.rubyonrails.org/routing.html#routing-to-rack-applications which says "For the curious, 'posts#index' actually expands out to PostsController.action(:index), which returns a valid Rack application."

A variant tested in Rails 3.2.13.

Upvotes: 13

barbolo
barbolo

Reputation: 3887

As suggested in the question Rails routing to handle multiple domains on single application, I guess you could use Rails Routing - Advanced Constraints to build what you need.

If you have a limited space of controllers (with unlimited views pointing to them), this should work. Just create a constraint for each controller that verifies if the current view matches them.

Assuming you have a space of 2 controllers (PostController and CommentController), you could add the following to your routes.rb:

match "*path" => "post#show", :constraints => PostConstraint.new
match "*path" => "comment#show", :constraints => CommentConstraint.new

Then, create lib/post_constraint.rb:

class PostConstraint     
  def matches?(request)
    'post' == Rails.cache.fetch("/view_controller_map/#{request.params[:view_name]}") { View.find_by_name(request.params[:view_name]).controller }
  end
end

Finally, create lib/comment_constraint.rb:

class CommentConstraint     
  def matches?(request)
    'comment' == Rails.cache.fetch("/view_controller_map/#{request.params[:view_name]}") { View.find_by_name(request.params[:view_name]).controller }
  end
end

You can do some improvements, like defining a super constraint class that fetches the cache, so you don't have to repeat code and don't risk fetching a wrong cache key name in one of the constraints.

Upvotes: 0

Agustin
Agustin

Reputation: 1254

Here is a nice Rack Routing solution to SEO contributed by zetetic and Steve ross

Testing Rack Routing Using rSpec

It shows you how to write a custom dispatcher (where you can do a db lookup if needed) and with constraints, and testing as well.

Upvotes: 0

Will
Will

Reputation: 8282

So I think that you are asking that if you have a Views table and a View model for it where the table looks like

id | name | model
===================
1  | aaa  | Post
2  | bbb  | Post
3  | ccc  | Comment

You want a url of /aaa to point to Post.controller - is this right?

If not then what you suggest seems fine assuming it works.

You could send it to a catch all action and have the action look at the url, run the find_by_name and then call the correct controller from there.

def catch_all
  View.find_by_name('aaa').controller.action
end

Update

You can use redirect_to and even send the params. In the example below you I am sending the search parameters

def catch_all
  new_controller = View.find_by_name('aaa').controller
  redirect_to :controller => new_controller, :action => :index, 
      :search => params[:search] 
end

Upvotes: 0

Related Questions