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
Related
I've added a controller collaborators to manage a particular type of join association between Users and Companies. The issue is that whenever I load anything from collaborators, I get the error
uninitialized constant Collaborator
From my understanding, this is because there is no model Collaborator and I am using cancancanfor authorization. From the old cancan (note not cancancan) documentation, I've been able to gather that controllers that don't have a corresponding model need to have a model manually authorized for them something like: load_and_authorize_resource :the_model, :parent => false.
This seems to work if I disable load_and_authorize_resource in my application.rb controller.
SO my quesestion is: what is the best way to authorize controllers that don't have corresponding models with cancancan? Can I continue to load_and_authorize_resource in my application controller?
Many thanks in advance.
This LINK will help.
From the link, I quote,
class ToolsController < ApplicationController
authorize_resource :class => false
def show
# automatically calls authorize!(:show, :tool)
end
end
And in your ability.rb:
class Ability
include CanCan::Ability
def initialize(user)
can :show, :tool
end
end
Here's the scenario. Suppose each user account in my rails application have an unique user handles on top of their assigned ids, how might I modify the routes (or otherwise) such that
/users/1
/users/johnsmith
Would both show the user who has id 1 and user handle 'johnsmith'?
I'm currently implementing it as follows by having a get_user method in my users controller which I call prior to the controller actions:
def get_user
identifier = params[:id]
if identifier.to_i.to_s == identifier # if numeric id
#user = User.find(identifier)
else # else hande
#user = User.find_by(handle: identifier)
end
end
Is there a more elegant solution to go about doing this in Rails?
Just use friendly_id, it's exactly what you need.
#Gemfile
gem 'friendly_id', '~> 5.1'
$ 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 :handle, use: [:finders, :slugged]
end
$ rails c
$ User.find_each(&:save)
This will replace all the references to :id in both your routes and model lookups, so you'll be able to use:
<%= link_to #user.handle, #user %>
... and it will show the user's handle.
Here is an idea that i might work for your situation. in your routes you could create a route like this: get '/user/:id_or_handle', controller: 'users', action: 'action', as: 'user_page' as: creates a path user_page_pathwhere you can pass in user object or current_user if you store user to session, like this user_page_path(user.id or user.username) then you can manipulate :id_or_handle
I'm attempting to use Vanity URLs in my Rails app so that instead of an ID, the username is shown. I've created the slug so that the username is used in the url instead of the id (everything works here ok), the issue I'm facing occurs when I try to load a page where I have load_and_authorize_resource enabled in the controller. I believe this is attempting to find the user via the id instead of the slug which is causing the error "Couldn't find User with id=X". Is there somewhere I can override the load_and_authorize_resource so that it uses the slug and not the ID?
class User < ActiveRecord::Base
def to_param
slug
end
end
class UsersController < ApplicationController
load_and_authorize_resource only: [:dashBoard]
def dashboard
end
def show
#this is the user's profile page, because I don't have a load
#and authorize resource check on it, it loads without error
#when the vanity url is enabled
#user = User.find_by_slug(params[:id])
end
end
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.memberships.count > 0
can :manage, User
else
can :read, :all
end
end
end
I figured it out; adding find_by: :slug to the load_and_authorize_resource did exactly what I was looking for.
load_and_authorize_resource only: [:dashBoard], find_by: :slug
I have some methods now under "profile" like user blocking, banning, moderation.
It feels these should belong under "user" and inside the user controller.
Is there a way to have a user_controller.rb when using devise with a user model?
Reason for this is to scope all user related methods under the user_controller instead of the profile_controller as it is now.
Yes. There is no problem with that. You can simply create users_controller.rb and interact with User model like:
class UsersController < ApplicationController
# do any stuff you need here
def block
#user = User.find(params[:id])
#user.block
end
def ban
#user = User.find(params[:id])
#user.ban
end
end
For sure, you have to create routes for this controller:
resources :users, only: [] do
member do
get :ban
get :block
end
end
Like that.
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.