Is there a way to have example.com/username while preserving example.com/users/1 route?
To put simply, I just need to link to users's profile via username.
You can add an arbitrary route like this using:
get "/:username" => "users#show", as: :username
This will pass the username as a parameter to the show action.
Then in your views:
<%= link_to user.name, username_path(username: user.username) %>
You'll also need a controller action that knows how to handle the username parameter. If you want to use your existing users controller and preserve your /users/:id URLs too, you could do something like:
def show
#user = if params[:username].present?
User.find_by_username(params[:username])
else
User.find(params[:id])
end
end
Related
So I want to display posts for an user in his/her profile page as at top user details, below all the posts.
I know I can get param from a url like http://localhost:3000/posts?category=article
with
if params[:category]
#category_id = Category.find_by(title: params[:category]).id
#posts = Post.where(category_id: #category_id)
end
but param doesn't work when I have an url like http://localhost:3000/user/adem-balka
So, how can I get user name to find its id and pull posts with that user id?
Thank you all.
The name of a parameter in a url is set in your routes file.
If you look at your routes in config/routes.rb, you should be able to find the line(s) that corresponds to the user model. It should look something like this:
get '/users/:name', to: 'users#show'
This means that if you go to /users/adem-balka, params[:name] will be set to 'adem-balka'. You can then access the parameter in the corresponding controller function.
What you are looking for is a path parameter, where adem-balka is say params[:username].
Assuming you have no forward slashes or dots in your parameter, this is as simple as adding /:username as part of your route, e.g.
get '/users/:username', to: 'users#show'
# in the controller
#user = User.find_by(username: params[:username])
This is all covered in the Rails Routing from the Outside In guide.
Note that the routes generated resources already contain the :id path parameter for you (for show, edit, etc.). But even if you change the controller, the generated helpers (e.g. users_path(#user)) will use the id.
To make it work with resources using say username instead of id however (e.g. users_path(#user) giving /users/ben instead of /users/5), you need to also override the to_param method, e.g.
class User < ApplicationRecord
def to_param
username #rather than id
end
end
# routes.rb
get '/users/:username' => 'users#posts'
# users_controller.rb
def posts
username = params[:username]
# etc..
end
This is described in the Rails Docs as Routing Parameters.
thank you for the answers. I learned from them 🙏
and this solved my problem
#user_id = User.friendly.find(params[:id])
#posts = Post.where(user_id: #user_id)
Say for instance I have a posts controller that currently has a method user_posts which shows all of the posts that are associated with the user with the associated id as so:
def user_posts
#user = User.find(params[:id])
#posts = #user.posts.all
end
I want the url to be: foo.com/my_posts when the posts have the same ID as my current_user; How would I do this? currently my routes are set up as so:
get 'user/posts/:id', to: 'posts#user_posts', as: 'user/posts'
I know that I could create an entirely new controller action for my_posts but I want to know if there is a way to do it in the config/routes.
If for example I am browsing throughout the site and tap on a link that says "user posts" I would expect to go the the users posts and if that user happens to be me I would like the url to show website.com/my_posts
If I understand well, you have a list of users (including the currently connected user) and each has a link 'user posts' to see the user's posts.
You can simply do:
views
In your views, change the user post link according to the user id. As you loop through your users, check if the user's id is the same as the currently logged user. If yes, change the link to the /my_posts route as follow:
<% if user.id == current_user.id %>
<%= link_to "My posts", my_posts_path %>
<% else %>
<%= link_to "User posts", user_posts_path(user) %>
<% end %>
routes.rb
Add a my_posts route that points to the same controller method as user/posts.
get 'user/posts/:id', to: 'posts#user_posts', as: 'user/posts'
get 'my_posts', to: 'posts#user_posts', as: 'my_posts'
controller
In your controller method, we need to instantiate the #user to get its posts. If there is no :id in the params (like the /my_posts route), then set the #user to the current_user. If an :id is passed, set the #user by fetching it from the db.
def user_posts
#user = params[:id].present? ? User.find(params[:id]) : current_user
#posts = #user.posts.all
end
No need to do checking in the routes.rb file. This is simple and more "Rails" like.
Is this what you are looking for?
As I know - no. It's possible to create in routes redirect route and check some conditions (example from documantation):
get 'jokes/:number', to: redirect { |params, request|
path = (params[:number].to_i.even? ? "wheres-the-beef" : "i-love-lamp")
"http://#{request.host_with_port}/#{path}"
}
But it's impossible to check current user in routes. Redirect can be implemented in the controller with two separate actions as mentioned.
Also available a little trick - generate from the beginning 'right' routes if you use html.erb (slim/haml). For current user posts link can be generated not as usual user/posts/:id but /my_posts (it's possible to check current user id without any problems) and define two routes:
get 'user/posts/:id', to: 'posts#user_posts', as: 'user/posts'
get 'my_posts', to: 'posts#user_posts', as: 'my_posts'
In controller check request.path to find user:
user = request.path == '/my_posts' ? current_user : User.find(params[:id])
Hope it helps.
I'm guessing you didn't want to use the index method of the posts controller because you were using it to show all posts from all users, but you can still use it. Here's how:
class PostsContoller < ApplicationController
def index
#posts = if params[:user_id].present?
User.find(params[:user_id]).posts
else
Post.all
end
end
end
Then in your routes file do this:
resources :posts
resources :users do
resources :posts
end
This allows posts to be a first class resource as well as a nested resource. Now when you go to /posts/ you get all posts, but when going to /users/:user_id/posts you get only posts from the given user.
In your app, when you need to link to all posts from all users, you can do
posts_path
and when you need to link to just a user's posts you can do
user_posts_path(user)
I have a simple controller "Users" which has a show action (for each user). Now, I want to have a textbox on the home page which allows to search for a specific user by username.
So on show action, I accept a username param and I respond with the user object. I've seen form_for() but it seems I need an instance of #user which I don't quite understand, since I am only just retrieving on the next request. I've seen tutorials but they seem to have it under index controller action but I really want it to point to users#show so it will use the assets, template, url path, etc.
So I want a form that would just perform a get request with /users/. Am I just missing something simple?
You don't need instance if you use form_tag. You can read more about form_tag here
Example:
<%= form_tag("/search", method: :get) do %>
<%= text_field_tag :user %>
<%= submit_tag "Search" %>
<% end %>
The html snippet from #Mihail well help you but the example given will require you to create a custom route for the search action.
Add in your routes:
GET '/search', to: 'users#show'
Then in your users show action attempt to look up the user by calling:
#user = User.find_by(id: params[:user])
Be sure to not use User.find, because you will get an error when the user doesn't exist. The last thing you'll need to do is make sure your users/show template is able to handle the case when #user is nil, or instead render some sort of custom user_not_found page instead of the users/show if #user is nil.
You can use form_tag instead of form_for. Also, all the form helpers have their _tag equivalents. I.e. select_tag instead of f.select and so on. And don't forget the case when user could not be found, You have to deal with it somehow.
I'll just add in what I did.
As advised, I've added a route for /search on config/routes.rb.
get '/search', to: 'users#search'
Then on UsersController, I've added a redirect on search action.
def search
#username = params[:search]
redirect_to "#{users_path}/#{#username}"
end
This would now allow me to reuse whatever I have under users#show (templates, url and everything). From what I understand, this will return a 301/302 that would cause another HTTP request so this is not at all optimal. It would probably be cleaner to just share functionality between show and search.
Right now, my urls are being rendered as
/users/2/products
I know i can use friendly_id to have it render as
/users/username/products
But I dont want that. In my database User, there is a field role, which has entries like - worker, janitor and so on. So, I want to have
/users/2/products
as
/worker/username/products or /janitor/username/products and so on..
How do i do it ? Can i use friendly_id to do it ?
I don't know friendly_id. I can't even be sure of what you are asking, but something tells me you want to define a custom route.
Adding a custom route
in your route file, you can add
match "/worker/:username/products" => "users#show"
and in the action, check for :username parameter variable.
if params[:username]
#user = User.find_by_name(params[:username])
else
#user = User.find(params[:id])
end
Routes for your specific case
In your case, it seems that you want this in your route.rb:
match "/:role/:username/products" => "users#show"
and this in your action to use the params:
User.where("role = '?' and username = '?', pararms[:role], params[:username])
The route will make accessing a URL like /worker/fotanus/products/ end up in the action User#show with params[:role] = 'worker' and params[:username] = 'fotanus'.
currently, I have this on my routes.rb
match '/:username' => 'profiles#show', :as => :public_profile
now I want to use this format, and add another parameter to make another match for another route.
I don't know how to explain this but let me give you an example.
Suppose with that match above:
http://example.com/foo_username
this connects to the profile of foo_username.
Now I want to have a url like this:
http://example.com/foo_username/5-my-story
This link should go to the stories#show action with 5-my-story as its id (slug), and this story is owned by the username provided in the path. How do I configure that in routes?
I tried this one:
match '/:username/:id' => 'stories#show', :as => public_story
It works, but when I change the parameter of the username, it still goes to the story. It should return a route not found because the username does not own the story.
How do I best implement this?
In your controller, you'll have something like this:
#story = Story.find(params[:id])
You need to restrict this to the user's stories maybe like this
#user = User.find_by_username( params[:username] )
#story = #user.stories.find(params[:id])
You then have to check if #story is nil, and return a 404 if not.
Adapt this to suit your needs:
# stories_controller.rb
def show
#story = Story.find(params[:id])
raise ActiveRecord::RecordNotFound unless #story.user.name == params[:username]
# ...
end