Reputation: 20367
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
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
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
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
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
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