Rails - Devise - User Profile url with username on - ruby-on-rails

I would like to get user profiles accessible from the URL: root/user/(username)
As of now I have it working with root/user/(id) but i want it to be more user friendly and shareable with the username in the URL.
This is how I currently have it set up
#user_controller.rb
class UserController < ApplicationController
def profile
#user = User.find(params[:id])
end
end
#routes.rb
match 'user/:id' => 'user#profile'
#profile.html.erb
<h1><%= #user.firstname %>'s Profile</h1>
Basically what I'm trying to do is to change out :id for :username. I've created the usernames in the user models from devise so I know that is working. But right now when I try to get usernames in the URL I get Couldn't find User with id=username.

Change your controller
class UserController < ApplicationController
def profile
#user = User.find_by_username(params[:username])
end
end
Then the route
match 'user/:username' => 'user#profile'

Try friendly_id. No need for any hacks in controller or model level.

Related

Accessing current_user in Rails route

Please forgive me... I know there are other posts with a similar title but I have not seen my question so...
I am trying to create a url mysite.com/myusername/profile and I was wondering how to create the route for that. At the moment, the url for user#profile is just that, mysite.com/user/profile, but I want to make it something more specific like say each user has a username like JohnnySmith the URL would be mysite.com/JohnnySmith/profile. I was thinking something like
get "/#{current_user.username}", to: "user#profile", as: user_profile
but I know this isn't correct.
I should mention that, too, that it is not possible for just anyone to access mysite.com/JohnnySmith/profile.... the current user would have to be JohnnySmith.
Can someone help? Thanks.
If you want to pass a parameter in a route, it should be
get "/:username/profile", to: "user#profile", as: user_profile
Please take a look at http://guides.rubyonrails.org/routing.html#naming-routes
Then you can use params[:username] in your controller to validate the user like
if current_user.username != params[:username]
# redirect to error page
Or you can use cancancan gem to do this.
You need to use friendly_id with CanCanCan for authorization.
Essentially, what you're trying to do is allow Rails to process usernames through the params. This can be done without friendly_id, but is somewhat hacky.
Using the friendly_id gem will allow you to use the following:
#Gemfile
gem "friendly_id"
$ rails generate friendly_id
$ rails generate scaffold user name:string slug:string:uniq
$ rake db:migrate
#app/models/user.rb
class User < ActiveRecord::Base
extend FriendlyID
friendly_id :username, use: [:finders, :slugged]
end
You'd then be able to use:
#config/routes.rb
resources :users, path: "", only: [] do
get :profile, action: :show, on: :member #-> url.com/:id/profile
end
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def show
#user = User.find params[:id]
end
end
This will automatically translate params[:id] into the slug attribute for the User model:
<%= link_to "Profile", user_profile_path(current_user) %>
# -> url.com/:current_user_name/profile
--
The next stage to this is authorization.
Using CanCanCan should make it so that only the current_user can view their profile:
#Gemfile
gem "cancancan"
#app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
can :read, User, id: user.id
end
end
You can then use load_and_authorize_resource in your users controller:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
load_and_authorize_resource
def show
end
end

Routing issue in ruby on rails

For user profile I make an action in home controller and I want that when user click on my profile link only shows www.abc.com/profile
For it I do
get '/profile' => 'home#profile'
But now issue is that there is a need to send user id along with that.If I send it in format then url become
www.abc.com/profile.4
I want that url remains '/profile' and Id of user also send
This is because you're setting it as a member route (it expects to pass the id for the controller). You need to replace it with a collection route, if you're going to nest it:
#config/routes.rb
devise_for :users ... do
get :profile, to: "home#profile", on: :collection, as: :profile #-> url.com/users/profile
end
You'd then be able to access the profile as follows:
<%= link_to "Profile", users_profiles_path, if user_signed_in? %>
If you wanted to make it so that you could access profile without nesting, your route would work:
#config/routes.rb
get "profile", to: "home#profile", as: :profile
<%= link_to "Profile", profile_path if user_signed_in? %>
Controller
We have a setup similar to this, let me explain the controller setup...
#app/controllers/users_controller.rb
class UsersController < ApplicationController
before_action :authenticate_user!
def edit
#user = current_user
end
def update
#user = current_user
...
end
end
This gives us the ability to use the following (we also use devise):
#config/routes.rb
resource :profile, path: "profile", controller: :users, path_names: { edit: "" }, only: [:edit, :update] #-> domain.com/profile
If you want the url to stay /profile then you'll have to rely on your login implementation. I've not used devise myself, but according to the documentation it provides you with a current_user method to use in your controllers. So your HomeController#profile would look something like this:
def profile
#user = current_user
render :profile
end
That way you don't need the client to send the id to you, because (as I understand it) this should be the profile page of the logged in user, which you should already have information about through devise.
Otherwise you need to allow the id to come through the path by defining the route like so:
get '/profile/:id' => "home#profile"
in devise gem they had mentioned current_user model so use
current_user.id
to find the User id so that in your profile action do like this, and pass the user id of current user so that you can easily fetch his details

Restrict devise users to their own view/association

I am using devise for authentication and have an association between users (has_many :products) and products model (belongs_to :user).
My routes file is
resources :users do
resources :products
end
Now what happens is, user with id 3 at /users/3/products can also see whats at /users/4/products. I want to restrict that. I dont want /users/3/products to able to see whats at /users/4/products and so on (not specific to these two users but for all). How do I do it ? Should I have a Users Controller? I dont have it right now. If i have the controller, how do I do it? I was thinking maybe redirect it?
thanks
You could add a before_filter in your products controller:
class ProductsController < ApplicationController
before_filter :user_is_current_user
...
private
def user_is_current_user
unless current_user.id == params[:user_id]
flash[:notice] = "You may only view your own products."
redirect_to root_path
end
end
end
Also, in the products controller you could retrieve only products belonging to the current_user:
def index
#products = current_user.products # will fetch all products with a user_id matching the current user's
end
If you used the above you wouldn't really need a user's ID in the URL, you could use a path like /users/products or just /products.

"Cannot find User without an ID" error after logging in using Devise

I have started building a simple rails app and have used Devise for authentication. I have created a Dashboard page for the users after they login, it's using a users_contoller added below:
class UserController < ApplicationController
before_filter :authenticate_user!
def show
#user = User.find(params[:id])
end
end
I have renamed this user#show to dashboard in routes:
match '/dashboard', to: 'user#show'
I have created a if else statement in the User show html page to see if this works, shown below:
<% if #user.lists.any? %>
Cool you have lists
<% else %>
OK No Lists
<%= #user[:id] %>
<% end %>
Unfortunately this just renders an error when trying to load the page in a browser
Couldn't find User without an ID
Im not sure if this is simply a Devise setting or something more fundamental and apologies in advance if this is avery noob question.
When a user is autenticated using devise the current autenticated user is "stored" in a helper called "current_user".
A quick fix for your problem should be changing the code in your "UserController" to something like this (at the same time mantaning the rest funcionality):
class UserController < ApplicationController
before_filter :authenticate_user!
def show
#user = params[:id].blank? ? current_user : User.find(params[:id])
end
end
Your route:
match '/dashboard', to: 'user#show' doesn't include the id parameter, you need to do as #Immendes suggested and use the current_user helper otherwise change your route to:
match '/dashboard/:id', to: 'user#show' and send the id

devise with multiple layout

I really like to authenticate my devise user through 2 different interfaces with a view to have 2 different layout.
For example I would be able to use /users/sign_in and /admin/sign_in based on the same User model.
I had set 2 routes :
devise_for :users
and
devise_for :users, :module => "admin/users", :path => ''
But I'm not sur it's the right way to do that because I need overwrite current_user on my application controller, like this:
def current_user
super || current_admin_user
end
Moreover I have 2 methods : authenticate_user! and authenticate_admin_user!
I'm really confused with this specification, can anybody help ?
I'm not sure if I got your problem, if not, please comment on it :)
There is no need to overwrite current_user. You can create a filter that filters admins like this:
def require_admin_user
unless current_user.admin
flash[:error] = "You need admin privileges to enter this area"
redirect_to root_path
end
end
current_user will return the current_user logged in, whether it is an admin or it isn't an admin. If you want for an user to be able to login in as an admin only if they as an ordinary user, I would suggest another approach: creating another model for admins and filtering for require_user! for the admin sign_in.
Your best bet is to use STA (Single Table Inheritance)… Then you can use 2 devise_for declarations, one for each model.
I have a different controller admin in that i have added a login action.
class AdminController < ApplicationController
def login
#user = User.new
end
end
In view of login.html.erb
<%= form_for(#user, :as => :user, :url => session_path(:user)) do |f| %>
<% end %>
U can now call admin/login path
and successfully got sign up, but if you want to redirect to some other page after sign up instead of root url then
In application controller write inside this method of devise
def after_sign_in_path_for(resource)
end

Resources