I have a basic app in Rails 3.2 - two main controllers: users and articles.
The basic routing is like web.com/users/john and web.com/articles/articles-name.
With the match rule I can make the URLs above shorter:
match "u/:permalink" => "users#my_action_name"
match "a/:permalink" => "articles#my_action_name"
allow me to do
web.com/u/john and web.com/a/articles-name
But in the best way, I would like to make the URL yet shorter and more readable, like this (this way use for instance Twitter - twitter.com/username)
web.com/john and web.com/articles-name
But I can't imagine, how to realize this way of routing. In the app are used also another controllers, like for example HomeController with actions index, about, contact.
Take a look at the "Route Globbing" section of the official Rails guides. You need to add something like
match '*users_or_articles' => 'agnostic#show'
at the end of your config/routes.rb file. This will catch all request that found no matchers in your routes file so far. Then your controller has to identify what to do with the request.
I was wondering the same thing some time ago and found an answer in the Routing Guide.
Your question can be answered in 2 steps:
1) First step would be to override the to_param method in your models in order to have prettier URLs. Take a look at this question for an example. You could also use a gem like FriendlyId (take a look at this screencast).
2) Then you would have to use the :path option in your routes.rb file. So for example if you wanted to have URLs like "example.com/3214-a-blog-post" you should declare your resource like this:
resources :posts, :path => "/"
Related
When discussing Routing using Dynamic Segments, the Ruby on Rails Guides (v4.0.1) say "You can't use :namespace or :module with a :controller path segment." They go on to suggest: "if you need to do this then use a constraint on :controller that matches the namespace you require."
I've got a lot of controllers under quite a few namespaces. If I could get dynamic segments to work in my situation then I could handle all of that routing with one line in routes.rb. Perhaps something like this:
get '/:namespace/:controller/:action' #note: this isn't valid
But that won't work (see above). I'd rather keep the namespaces, so I could just write out a fresh line in my routes.rb file for each namespace and then add a constraint to check that the URL is asking for that namespace. That wouldn't be the end of the world. It just seems so un-DRY and a shame because all the routing information is neatly presented right there in the URL.
I'm just wondering if there are any sneaky ways round what seems (to me) like a bit of a gap in the capabilities of Dynamic Segment Routing. Can one get the Routing DSL to run a block that (unlike Constraints) does something more complex than a Boolean yes/no on whether the route matches? Or perhaps I'm missing something in the way the standard Rails routing capabilities work. Any suggestions appreciated.
Update: Just to spell it out a little bit more. My understanding of the Rails Guides is that I'll need to have lines like these:
post ':controller/:action' , controller: /user_details\/[^\/]+/
post ':controller/:action' , controller: /preferences\/[^\/]+/
...and so on. If I want 20 namespaces then I'll need 20 of these lines. I'm just wondering if there isn't a more concise way that approaches my 1-line ideal given above.
If what you want is a dynamic route that matches all routes like
post 'user_details/name/edit' => 'user_details/name#edit'
post 'user_details/address/edit' => 'user_details/address#edit'
post 'user_details/password/edit' => 'user_details/password#edit'
post 'preferences/privacy/edit' => 'preferences/privacy#edit'
post 'preferences/colors/edit' => 'preferences/colors#edit'
then you could just use
post ':controller/:action'
since the :controller part will match namespaced controllers, too.
Verified for Rails 4.0.1 and 4.2.0.
Since the beginning I always hat this one problem with rails, short urls without the controller name.
For example, I have a blog and I don't want any dates or controller names in the url, I already have a Page model with a unique field url in my database. Rails works great with such urls:
jeena.net/pages/1
And when I modify the model I even can get it to use
jeena.net/pages/foo
But it seems not to matter what I do I can not get it to work with just:
jeena.net/foo
Of course I want the index page still to work with
jeena.net/pages
And I want creating new pages and updating old pages to work too in some was as well as the link_to()-method. All suggestions are appreciated.
To define that route, try adding the following to your routes.rb:
match '/:id' => 'your_controller#your_action'
This will pretty much match everything to the id of your model. And that's not very nice... You don't want to route youe_host/pages to the pages controller, with an id equal to 'pages'... To prevent that from happening, make sure to put that line on the end of the routes.rb file. The router uses the first route that matches the path received, so putting that line on the end of it will make sure that it will only match your route after it ran out of other meaningful options.
A better practice would be to pass regexp constraints to the router, so that it will only match ids with a specific format, like that:
match '/:id' => 'your_controller#your_action', :constraints => { :id => /your_regexp/ }
Refer to the guides if you have doubts about the rails rounting system. It is pretty well written and covers lots of important things.
Rails rounting - Official Guides
edit: to create a named route, one that you can call in your controllers and override the normal routes that you are probably creating with resource, you have to provide the :as => parameter in your routes.rb
match '/:id' => 'your_controller#your_action', :as => some_name
Then you'll be able to call it in your controller/views like this:
link_to some_name_path(#my_string_id)
Hope this helps. And take a time to read the guides, it has really lots of useful info, including more details about creating named routes.
I've got some Documents (and a DocumentsController), which are sorted using limited, fixed set of categories. I'd want my routes to take into account these categories, so my urls would look like :
/documents/:category/:id
/documents/:category/new
/documents/:category/:id/edit
...and so on, which should allow me to access params[:category] in order to filter the results. Is there a simple way to achieve this, that would still generate path helpers ? Or im i wrong to do this that way ?
You can provide a path to a resource (as you mentioned):
# config/routes.rb
resources :documents, :path => 'documents/:category'
This would give you the following routes:
/documents/:category
/documents/:category/new
/documents/:category/:id/edit
/documents/:category/:id
I am not sure in this case what purpose the category capturing will serve, since you can reference the document by its primary key. This key most likely does not repeat across categories.
It's not hard to customize paths in Rails 3.
match '/documents/:id', to: 'documents#show', as: :document would give you the path helper document_path(:id). This will work even for an ID that's a string rather than a number, so extending this pattern to /documents/:category/:id/edit should be no problem.
I'd like to have a URL like this:
/payroll/region/1
and I'd like it to map to the Tasks Controller's payroll_list function. I'd also like to use REST. What's the best way to do this?
Many thanks!
Well I'd suggest you better go with the convention how Rails handles this. If you still insist on using such "strange" URLs and want to ignore the problems/headaches this can create during further development, then try to use Refraction.
I don't want to be rude but currently it seems to me that you did not understand why restful URLs are the way they are. Please do understand the design behind this first, then rethink your application/controller and routing design. I bet you will be enlighted.
In this example, your URL should probably be /regions/1/payrolls with map.resources :regions, :has_many => :payrolls. Then your payroll list would be rendered by the PayrollsController having a params[:region_id] - and that actually makes sense (and probably what you tried to achieve with your URL layout). Code snippet:
def index
if params[:region_id]
#region = Region.find(params[:region_id])
#payrolls = #region.payrolls
else
#payrolls = Payroll.all
end
end
If you still want to have a resource under a different named URL, use the following:
map.resources :regions do |regions|
regions.resources :tasks, :as => :payrolls
end
This will map the nested resources to the tasks controller using the named URL part "payrolls." But this probably does not work as you might expect because restful logic means you should handle the payroll model in the PayrollsController. Otherwise you might run into strange looking code. Maybe your design of the TasksController is just wrong? Rails will probably expect tasks to be handled over to your tasks controller although you name it payrolls. This can be confusing at least (however, it does not actually expect these being task models, so it will probably work).
BTW - Keep in mind: "restful" also means your application should answer to standard verbs on a resource, not just using "resourceful" routes. It's also about the GET, PUT, DELETE and POST http verbs, and of course the "edit", "new" etc default actions. Do not try to make your controllers big and complicated. Follow the motto "skinny controllers - fat models".
OK, so a better question, then might be this:
How can I get it so that I use your suggestion:
/regions/1/payroll
and have that map RESTfully to:
Tasks controller with index, new, etc that are prefixed by "payroll_"?
Like this: TasksController#payroll_index or TasksController#payroll_new
I'm using Rails 2.
I have resources nested like this:
- university_categories
- universities
- studies
- professors
- comments
I wish to use RESTful routes, but I don't want all that clutter in my URL. For example instead of:
/universities/:university_id/studies/:study_id/professors/:professor_id
I want:
/professors/:university_id/:study_id/:professor_id
(I don't map professors seperately so there shouldn't be a confusion between this and /professors/:professor_id since that route shouldn't exist).
Again, I want to use RESTful resources/routes...
Also note, I am using slugs instead of IDs. Slugs for studies are NOT unique, while other are. Also, there are no many-to-many relationships (so if I know the slug of a professor, which is unique, I also know which study and university and category it belongs to, however I still wish this information to be in the URI if possible for SEO, and also it is necessary when adding a new professor). I do however want to use shallow nesting for "administrator" URIs like edit, destroy (note the problem here with Study since it's slug is not unique, though)...
I would also like some tips on how to use the url helpers so that I don't have too much to fix if I change the routes in the future...
Thank you.
It doesn't seem like map.resources will provide you with this functionality, but you could use something like (untested)
map.show_professor "/professors/:university_id/:study_id/:professor_id", :controller => "professors", :action => "show"
and then similar routes for the other actions.
There might be a better solution, but this is the only way I can find that would work, since it seems map.resources assumes it is in the form of /resources/(:resource_id)
You can use REST this way, you just have to do all the actions yourself instead of using the shortcut.
As an example of an edit, you can just use
map.edit_professor "/professors/:id/edit", :controller => "professors", :action => "edit"