Rails Route based on something different than ID - ruby-on-rails

So currently I have something like /users/1/ when I want to view a user profile. How can I go through routes.rb to change that to /user/chiggins/ where chiggins is a unique username?

You need is to override to_param method in User model:
class User
def to_param
username
end
end
Then rails will use it automagically for routing. See http://api.rubyonrails.org/classes/ActiveRecord/Base.html#method-i-to_param

Another possibility to consider would be the friendly_id gem - https://github.com/norman/friendly_id

Nowadays there is a :param argument on the resource declaration.
http://guides.rubyonrails.org/routing.html#overriding-named-route-parameters

You can get per-resource identifier customization by redefining the member_scope and nested_scope methods on the Resource instance.
resources :users do
#scope[:scope_level_resource].tap do |u|
def u.member_scope
"#{path}/:username"
end
def u.nested_scope
"#{path}/:#{singular}_username"
# member_scope also usable here, assuming username will be nowhere in nested routes.
end
end
end
Regarding the question about #nested_scope below: It gets used when you do something like this in routing:
resources :members do
resources :playlists, only: :index
end
Then, the param would be :member_username instead of just :username. This is useful in the playlists controller when assembling the collection so you can infer the scope of the request.

The best way is to define a route with a custom param:
match "/users/:username" => "users#show"
In your controller, the plain old params[:id] will be params[:username], and you can get the user from de DB using:
User.find_by_username(params[:username])

Related

How to define a route in rails hiding information like ids in urls

I had just started ruby on rails and i am not getting how to hide ids in urls, so that it won't be visible to user
For example for creating a new user, the route will be
users/new
And for editing an existing user, route should be -
users/:id/edit
But id is an unnecessary detail to user. So how we can hide it from users and what will be the new route.
There are at least two perspective to this problem.
The first perspective is that of a user. I'm assuming you're talking about a user editing his own profile. In this case, the ID is indeed redundant. I recommend you handle this use case by adding a resource named profile and the corresponding ProfilesController. In config/routes.rb add:
resource :profile, only: [:show, :update]
Note it reads resource, not resources.
The second perspective is that of an administrator. In this case, it's better to use resources (not resource) so that the administrator is able to edit any user he wishes. On top of that, the administrator may have some extra capabilities that regular users lack (for example: making someone an admin).
You will need to have some unique identifier in the url. However, this unique identifier does not have to be the auto incremented id generated by your database. If you have ensured uniqueness on another field (say username), you could use that as the part of your route.
users/:username/edit
Using This link, we can see that the resources definition in the routes can be done for all the routes in one line
resources :users, param: :username
Your UsersController would look something like this
class UsersController
...
def edit
#user = User.where(username: params[:username]).first
...
end
end
You can also set the to_param method on the model to the new identified username
class User
...
def to_param
username
end
...
end
This will allow you to do
#user = User.where(username: "test123")
edit_user_path(#user) #=> /users/test123/edit
thanks to Simple Lime for pointing that out.
EDIT
The new route would be unchanged by this update. It would still be
/users/new
You can define post route
post 'users/edit', to: user#edit
and in edit method you can get id from params

How to have different routes ids on different routes for the same resources?

I have never found a good solution for this problem. I have the following routes structure:
resources :contents
namespace :admin do
resources :contents
end
When I call content_path(content) I want the id to be the slug of the content, while when I call admin_content_path(content) I want the id to be the id of the content. I just want the id not to be related to the model (actually the id is the returning value of the to_param method of the model), but to the route.
I would like to avoid defining helper methods for every route, it's a weak solution in my opinion.
I know I can write admin_content_path(id: content.id) or content_path(id: content.slug), but this is just an hack actually. Also, this is especially annoying in form_for, since I can't write
form_for #content
but I'm forced to use
form_for #content, url: #content.new_record? ? admin_contents_path : admin_contents_path(id: #content.id)
Usually, you would change the route to:
resources :contents, param: :slug
and then you override to_param method to become:
class Content < ApplicationRecord
def to_param
slug
end
end
And finally in your controller, you replace Content.find(params[:id] with Content.find_by(slug: params[:slug]).
That will give you URLs like /contents/foo-bar when you call content_path(content).
In your case, you can additionally create a subclass that overrides the to_param method:
module Admin
class Content < ::Content
def to_param
id && id.to_s # This is the default for ActiveRecord
end
end
end
Since your admin/contents_controller.rb is namespaced under Admin (e.g Admin::ContentsController), it will by default use the Admin::Content class instead of the normal Content class, and thus the object itself and all routes should be as you like them to be, including forms.
I would say that's two different problems : URL generation for your resources on the user front-end side (using slugs) and URL generation for your admin forms.
Obviously in your admin, you will never be able to just write form_for #resource because your admin is namespaced, so the minimum would at least be form_for [:admin, #resource].
Let's say you have to_param on some of your models to return a slug, you may create your own customised helpers on your admin back-office to always return a path namespaced to /admin/ and using the id of the record.
One generic way to do that is adding this kind of code in your Admin root controller.
class Admin::AdminController < ApplicationController
helper_method :admin_resource_path, :edit_admin_resource_path
def admin_resource_path(resource)
if resource.new_record?
polymorphic_path([:admin, ActiveModel::Naming.route_key(resource)])
else
polymorphic_path([:admin, ActiveModel::Naming.singular_route_key(resource)], id: resource.id)
end
end
def edit_admin_resource_path(resource)
polymorphic_path([:edit, :admin, ActiveModel::Naming.singular_route_key(resource)], id: resource.id)
end
end
Then in your form you can use form_for(#user, url: admin_resource_path(#user). It will work on both user creation and user edition.
You will be able to use those helpers also in your controllers to redirect...
Well, I found a nice solution, but only on Rails >= 5.1 (which is in rc1 at the moment), using the brand new direct method:
namespace :admin do
resources :contents
end
# Maps admin content paths in order to use model.id instead of model.to_param
{ admin_content: :show, edit_admin_content: :edit }.each do |direct_name, action|
direct direct_name do |model, options|
options.merge(controller: 'admin/contents', action: action, id: model.id)
end
end

Using Self joins in Rails views

I have a Users table which also has a manager's id to implement a self-join. when I login as a a manager and click on "My subordinates", I should see my subordinates. The subordinates are also from the User table.
So my question is
What should I say here <%= link_to "My Subordinates", ????_path %>(I mean like user_path.).
How should the model and controller logic be?
I would do something like #ryanfelton said, but instead of overwriting the index method, i would create a new one specifically for the subordinates.
class Manager::UsersController < ApplicationController
before_action :ensure_manager! #this one check the manager_id or any other condition to be manager
def sobordinates
#subordinates = #user.subordinates
end
end
#routes.rb
namespace :manager do
resources :users do
collection do
get :subordinates
end
end
end
This way you can maintain the index of users and you have a method only for the subordinates.
Be aware that you need to create a subordinates.html.erb inside the users folder >
app/views/manager/users/subordinates.html.erb
EDIT:
You where asking for the model and the link also so, here it goes:
The link: after editing the routes.rb, go to the console and use rake routes
and search for the subordinates link. Add the _path or _url depending on the use you are whiling for that path.
The model, I strongly recommend you to read the official documentation about relations: http://guides.rubyonrails.org/association_basics.html. That would help you more than having the answer for copying and pasting :)
I would recommend namspacing a users_controller.rb.
So it would be in the folder app/controllers/manager/users_controller.rb
class UsersController < ApplicationController
before_action :ensure_manager!
def index
#manager.users
end
end
In the routes.rb you would have this route:
namespace :manager do
resources :users
end
So ultimately your path would be manager_users_path

How can I pass parameters to nested resources for single table inheritance in Ruby on Rails?

I am having some difficulties passing in parameters to my controller. I created an Single table inheritance model in my model file.
class Account < ActiveRecord::Base
belongs_to :user
end
class AdvertiserAccount < Account
end
class PublisherAccount < Account
end
I setted up my routes table with nested resources
resources :advertiser_accounts do
resources :campaigns
end
I want to be able to pass the current account_id (an account_id from one of my two subclasses of account) to my campaign controller file.
A URL that I would use is http://127.0.0.1:3000/advertiser_accounts/1/campaigns
Since my resource for the url is advertiser_accounts and not accounts, I am not able to get the parameter :account_id.
class CampaignsController < ApplicationController
def index
#account = current_user.accounts.find_by_id(params[:account_id])
end
end
is there a shortcut to get the current resource or the id? Am I passing in parameters correctly? It seems confusing to call many find_by_id in the controller. Any help is appreciated.
Edit Possible solution:
One of the solutions that I was thinking was setting a type in my routes and then in my controller I would use case statement then get params[:advertiser_account_id] but that seems very tedious and messy. Especially if I will need to copy and paste a list of case statements in each action.
routes.rb
resources :advertiser_accounts, :type => "AdvertiserAccounts" do
resources :campaigns
end
campaigns_controller.rb
def index
case params[:type]
when "AdvertiserAccounts"
#account = current_user.accounts.find_by_id(params[:advertiser_account_id])
when "PublisherAccounts"
#account = current_user.accounts.find_by_id(params[:publisher_account_id])
end
end
Try this out:
resources :advertiser_accounts, :as => "account" do
resources :campaigns
end
that should give you
/advertiser_accounts/:account_id/campaigns/:id(.:format)
You can try with "becomes" method in your controller.
In your private method where you're looking for the account_id you would have:
#account = Account.find(params[:account_id]).becomes Account

How do I create a resource that is the sub-set of an existing resource

In my "routes.rb" file I have the following line:
resource :users
which gives me a bunch of named routes for accessing my User model in a RESTful manner.
Now, I've made some additions to the User model including creating a special class of user. These are still stored in the User model but there is a "special" flag in the database that identifies them as special.
So, is it possible to create special_users resource? For example, I'd like to have a "special_users_path" as a named route to "/special_users" which will return an index of only the special users when you perform a GET on the URL.
Is there a way to do this?
In Rails routing, a 'resource' refers to the standard 7 routes that are created for RESTful resources: index, show, new, create, edit, update and destroy. Normally that is enough, but sometimes you might want to create another action.
In the model, you want to create a scope that only returns special users:
class User < ActiveRecord::Base
scope :special, where(:special => true)
end
On the controller side, there are two ways to go about this. What you are suggesting is the creation of an additional action:
match "/users/special" => "users#special"
resource :users
In the controller, your special action would return the scope you just created:
class UsersController < ApplicationController
def special
#users = User.special
end
end
That will do what you ask, but I would suggest NOT doing it this way. What if you add other flags later that you want to search by? What if you want to search by multiple flags? This solution isn't flexible enough for that. Instead, keep the routes the way they are:
resource :users
and just add an additional line to your controller:
class UsersController < ApplicationController
def index
#users = User.all
#users = #users.special if params[:special]
end
end
and now, when you want to display special users, simply direct the user to /users?special=true
This approach is much more future-proof, IMO.
(This answer is assuming Rails-3. If you're still using 2.3 let me know)
You could set the special_users as a resource:
resource :special_users
If you need to point it to a special controller, you could specify it with:
resource :special_users, :controller => :users
But I would really suggest you to not creating another controller for retrieving a kind of user, but using a param to get them:
class UsersController < ApplicationController
def index
users = case params[:type].to_s
when "special"
User.special_users # Using named scopes
else
User.all
end
end
end
When you use the users_path to call the special users:
users_path(:type => :special)

Resources