I would like vanity URLs for the #show action on one of my controllers. I've setup this route:
match "/:username" => "users#show", as: :show_user
I would really like to be able to use this vanity URL with the standard way to link to a user show, like so:
link_to("foo", user)
Thanks!
Jesse Wolgamott has the right idea but a couple notes...
this should go at the bottom of routes.rb so that it doesn't clobber any other routes
resources :users, :path => '/'
UsersController#show
#user = User.where(:username => params[:id])
One (slightly verbose) way to do this with your current route setup is
link_to "foo", show_user_path(user)
Alternatively,
You could name your route 'user instead' of 'show_user'. And then override the provided show_user_url(user) helper to use user.username instead of user.id
Under the covers link_to calls polymorphic_url, which calls build_named_route_call you could monkey-path build_named_route to call your custom route instead of the regular user_url.
Unfortunately, there is no public api/configuration to make this happen cleanly.
routes.rb
resources :users, :path => '/'
user.rb
def to_param
username.parameterize
end
If there is a username of Bob, then <%= link_to user.username, user%> will create
Bob
Related
Hi guys I am new to rails. Sorry if I can't define this question properly.
What I wanted is for:
domain.com/posts/1-sample-post
to be routed like this:
domain.com/1-sample-post
How do I achieve this in rails routes? I've tried searching for this for almost 3 hours. This is very easy in PHP frameworks. I thought this is easy in Rails too.
I forgot to mention I have High_voltage gem installed in my app for my static pages.
Did this:
#routes.rb
resources :posts
get '/:id' => 'posts#show'
Now my High_voltage pages could not be rendered.
Update Solution:
So here is what we did in the routes:
Rails.application.routes.draw do
resources :authors
constraints(lambda { |req| Author.exists?(slug: req.params["id"]) }) do
get '/:id' => 'authors#show'
end
devise_for :users
resources :posts
constraints(lambda { |req| Post.exists?(slug: req.params["id"]) }) do
get '/:id' => 'posts#show'
end
end
Note that it is important to only use an exists? query here as it is very fast than other methods, so it won't eat that much loading time to render a record.
Special thanks to the guys below who helped a lot. Nathanvda, rwold, and Tai.
So the other answer correctly suggested something like
get '/:id', to: 'posts#show'
But this is a catch-all route and if there are no other routes defined this will catch all routes, also your HighVoltage, if it is configured to serve pages on root. You now have two catch-alls: one to find a static page and one to find a post.
Best solution in this case, imho is to make the static pages explicit (since I am assuming there will not be that many?)
get '/about' => 'high_voltage/pages#show', id: 'about'
get '/:id' => 'posts#show'
If you have a lot of pages, it seems easiest to just present the high-voltage on a different route? E.g. something like
get '/pages/:id' => 'high_voltage/pages#show'
get '/:id' => 'posts#show'
In both of these cases, since we use explicit routing, you would have to disable the default routing in the high-voltage initializer:
# config/initializers/high_voltage.rb
HighVoltage.configure do |config|
config.routes = false
end
[UPDATE: add special controller to consider both posts and pages]
Add a HomeController like this:
class HomeController < ApplicationController
# include the HighVoltage behaviour --which we will partly overwrite
include HighVoltage::StaticPage
def show
# try to find a post first
#post = Post.where(id: params[:id).first
if #post.present?
render 'posts/show'
else
# just do the high-voltage thing
render(
template: current_page,
locals: { current_page: current_page },
)
end
end
end
Of course I did not test this code, but I think this should get you started. Instead of doing the rendering of the post, you could also redirect to the posts-controller which is maybe easier (and you will use the PostsController fully) but adds a redirect and will change the url.
In your routing you will then have to write
get '/:id', 'home#show'
In your routes.rb file:
get '/:id-sample-post', to: 'posts#show', as: :sample_post
assuming that posts is your controller and show is the action that calls the view for your article with the given id.
EDIT AFTER OP COMMENT:
The as: :sample_post clause should create a helper sample_post_path that can be invoked as <%= link_to "Show", sample_post %>.
I want the url of a user post to be like this:
example.com/users/username/post-name
How should I set this up?
Currently, the post model contains:
def to_param
name.parameterize
end
(to hyphenate the post name accordingly)
routes.rb contains:
map.resources :users
map.resources :posts
Preferably, I would like to say post_path(post) and this would generate the appropriate path for me by finding the post.user automatically.
What do I need to do to make this happen?
Also added
map.resources :users do |user|
user.resources :posts
end
One way more:
map.resources :users do |user|
user.connect ':name/:action', :controller => 'posts', :defaults => {:action => 'show'}
end
Available routes:
example.com/users/username/post-name
example.com/users/username/post-name/edit (any action)
Hi To make your application recognize routes like
example.com/users/username/post-name
you should add to your routes.rb
map.connect 'users/:username/:post', :controller => "users", :action => "test"
Then you can access params[:username] and params[:post] inside your controllers test action You should define it after map.resources :users but before map ':controller/:action/:id' but you must write your own helper then
That to_param looks okay, but the builtin helpers don't link to nested resources like you want, you'd have to use user_post_path(user, post). Ofcourse, you could write your own post_path helper method which does work the way you want.
def post_path(post)
url_for [post.user, post]
end
I've got a very simple Rails 3 app with a User model run by Devise and a Notes model. Right now the url and routing looks like this:
# url
/users/MEMBERNAME/notes/TITLE-OF-NOTE
# routing
resources :users do
resources :notes
end
But I would like the urls to look like this, how would the routing look like in this case?
# url
/MEMBERNAME/TITLE-OF-NOTE
Update:
Thanks, now I discovered a new problem though. In my forms I have this code:
<%= form_for([#user, #note]) do |f| %>
and in my controller I redirect like this:
format.html { redirect_to([#user, #note], :notice => 'Note was successfully created.') }
In both those cases when I use #user, #note the old urls are still present. Do you know how to translate the form and the redirects to use the member/title structure?
Thanks in advance!
You can use a custom route here:
get "/:user_id/:id", :to => "notes#show", :as => :short_user_note
Hope this helps!
Update:
To use the newly created named route:
# => /USER_NAME/NOTE_NAME
redirect_to short_user_note_path(#user, #note)
# => /user/USER_NAME/note/NOTE_NAME
redirect_to user_note_path(#user, #note)
# OR
redirect_to url_for([#user, #note])
# OR
redirect_to [#user, #note]
So, the general rule is if you pass an array of active_record objects like below to #redirect_to, #url_for or #form_for methods, the #polymorphic_url method is called internally, and generates the standard RESTful route.
You can the routes behaviour you're after with working route helpers in Rails 3 with like the following:
resources :users, :path => ''
resources :users, :path => '', :only => [] do
resources :notes, :path => '', :except => [:index]
end
See my blog post for the details on how I arrived at this solution.
I want to copy the twitter profile page and have a url with a username "http://www.my-app.com/username" and while I can manually type this into the address bar and navigate to the profile page I can't link to the custom URL.
I think the problem is in the routes - here's the code in my routes.rb
map.connect '/:username', :controller => 'users', :action => 'show'
Also, I have Question and Answer models and I want to link to them with the customized URL like so:
http://www.my-app.com/username/question/answer/2210
There's nothing wrong with your route. Just remember to define it at the end, after defining all other routes. I would also recommend using RESTful routes and only if you want to have better looking URLs use named routes. Don't use map.connect. Here's some good reading about Rails routes.
Here's how this could look:
map.resources :questions, :path_prefix => '/:username' do |question|
question.resources :answers
end
map.resources :users
map.user '/:username', :controller => 'users', :action => 'show'
Just a draft you can extend.
To create urls you need to define to_param method for your user model (read here).
class User < ActiveRecord::Base
def to_param
username
end
end
I know this questions is old but it will help someone.
You could try the below. I've used it in a rails 4 project and all seems to be working great. The reason for the as: :admin is I also had a resources posts outside of this scope. It will add a admin to the helper calls e.g. admin_posts_path
scope ":username", module: 'admin', as: :admin do
get '', to: 'profiles#show'
resources :posts
end
I have used like this
In View part
portfolio.user.name,:id =>portfolio) %>
and in rout.rb
map.show_portfolio "portfolios/:username", :action => 'show_portfolio', :controller => 'portfolios'
Right now my user profile URLs are like so:
http://example.com/users/joeschmoe
And that points to the show method in the user controller.
What I'd ideally like to do is offer user profile URLs like this:
http://example.com/joeschmoe
So, what sort of route and controller magic needs to happen to pull that off?
I disagree with what jcm says about this. It's not a terrible idea at all and is used in production by the two biggest social networks Facebook and MySpace.
The route to match http://example.com/username would look like this:
map.connect ':username', :controller => 'users', :action => 'show'
If you want to go the subdomain route and map profiles to a URL like http://username.example.com/, I recommend using the SubdomainFu plugin and the resulting route would look like:
map.root :controller => 'users', :action => 'show' , :conditions => {:subdomain => /.+/}
These broad, catch all routes should be defined last in routes.rb, so that they are of lowest priority, and more specific routes will match first.
I also recommend using a validation in your User model to eliminate the possibility of a user choosing a username that will collide with current and future routes:
class User < ActiveRecord::Base
validates_exclusion_of :username, :in => %w( messages posts blog forum admin profile )
…
end
This does not make sense unless you have no controllers. What happens when you want to name a controller the same as an existing user? What if a user creates a username the same as one of your controllers? This looks like a terrible idea. If you think the /user/ is too long try making a new custom route for /u/
So your custom route would be...
map.connect 'u/:id', :controller => 'my/usercontroller', :action => 'someaction'
In routes.rb this should do the trick:
map.connect ":login", :controller => 'users', :action => 'show'
Where login is the name of the variable passed to the show method. Be sure to put it after all other controller mappings.
Well, one thing you need is to ensure that you don't have name collisions with your users and controllers.
Once you do that you, can add a route like this:
map.connect ':username', :controller => 'users', :action => 'show'
Another thing people have done is to use subdomains and rewrite rules in the web server, so you can have http://joeshmoe.example.com
In Rails 4 to skip controller from url you have to do add path: ''.
resources :users, path: '' do
end