I have a User class and map.resources :users in my routes.
If I create a link
link_to #user.name, #user
It will somehow automatically create a link to /users/3 where 3 is an ID of the user.
What if I want to create more userfriendly links and identify users not by IDs but by their usernames. So path would look like /users/some_user_name. How do I reassign the default link for #user so I wouldn't need to change all templates?
You can use FriendlyId gem. This is exactly what you want. For example, if you want links look like /users/username:
class User < ActiveRecord::Base
has_friendly_id :username
end
Found it.
In User.rb:
def to_param
username
end
Related
I have a Rails 6 app and as resource hotel.
My user model has a belongs_to hotel (hotel_id) column.
No I want to create a singular resource where a user can access its hotel directly as mentioned here:
https://guides.rubyonrails.org/routing.html#singular-resources
So I did
resource :hotel which maps to hotels#show
The idea is that I don't need to have a link hotel/ID and instead just have /hotel
But now, how does my hotels controller know which hotel to load? I assumed that it would take it from the user table but instead I get
undefined method `name' for nil:NilClass
which means that there is no #hotel in my view.
What am I doing wrong here?
You need nested routes... so in config/routes.rb you need:
resources :users do
resource :hotel
end
Then examine the route set (e.g. from the rails console do rails routes) and you'll see a set of routes that include something like this:
user_hotel GET /users/:id/hotel hotels/show
do you see the :id parameter buried in the url?
Then in the hotels controller show method, the user's id is found in the params hash. So you can find the hotel like this:
class HotelsController < ApplicationController
def show
user = User.find(user_params(:id))
#hotel = user.hotel
end
private
def user_params
params.require(:user).permit(:id)
end
end
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
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
I have a controller named 'companies' and rather than the urls for each company being denoted with an :id I'd like to have the url use their :name such as: url/company/microsoft instead of url/company/3.
In my controller I assumed I would have
def show
#company = Company.find(params[:name])
end
Since there won't be any other parameter in the url I was hoping rails would understand that :name referenced the :name column in my Company model. I assume the magic here would be in the route but am stuck at this point.
Good answer with Rails 4.0+ :
resources :companies, param: :name
optionally you can use only: or except: list to specify routes
and if you want to construct a URL, you can override ActiveRecord::Base#to_param of a related model:
class Video < ApplicationRecord
def to_param
identifier
end
# or
alias_method :to_param, :identifier
end
video = Video.find_by(identifier: "Roman-Holiday")
edit_videos_path(video) # => "/videos/Roman-Holiday"
params
The bottom line is you're looking at the wrong solution - the params hash keys are rather irrelevant, you need to be able to use the data contained inside them more effectively.
Your routes will be constructed as:
#config/routes.rb
resources :controller #-> domain.com/controller/:id
This means if you request this route: domain.com/controller/your_resource, the params[:id] hash value will be your_resource (doesn't matter if it's called params[:name] or params[:id])
--
friendly_id
The reason you have several answers recommending friendly_id is because this overrides the find method of ActiveRecord, allowing you to use a slug in your query:
#app/models/model.rb
Class Model < ActiveRecord::Base
extend FriendlyId
friendly_id :name, use: [:slugged, :finders]
end
This allows you to do this:
#app/controllers/your_controller.rb
def show
#model = Model.find params[:id] #-> this can be the "name" of your record, or "id"
end
Honestly, I would just overwrite the to_param in the Model. This will allow company_path helpers to work correctly.
Note: I would create a separate slug column for complex name, but that's just me. This is the simple case.
class Company < ActiveRecord::Base
def to_param
name
end
end
Then change my routes param for readability.
# The param option may only be in Rails 4+,
# if so just use params[:id] in the controller
resources :companies, param: :name
Finally in my Controller I need to look it up the right way.
class CompaniesController < ApplicationController
def show
# Rails 4.0+
#company = Company.find_by(name: params[:name])
# Rails < 4.0
#company = Company.find_by_name(params[:name])
end
end
I recommend using the friendly_id for this purpose.
Please be noted that there are differences between friendly_id 4 and 5. In friendly_id 4, you can use like this
#company = Company.find(params[:id])
However, you won't be able to do that in friendly_id 5, you have to use:
#company = Company.friendly.find(params[:id])
In case that you don't want to use the params[:id] but params[:name], you have to override the route in routes.rb. For example
get '/companies/:name', to: "companies#show"
Hope these info would be helpful to you
There's actually no magic to implement this, you have to either build it yourself by correctly implementing to_param at your model (not recommended) or using one of the gems available for this like:
friendly_id
has_permalink
I use friendly_id and it does the job nicely.
Model.find(primary_key)
The default parameter here is primary_key id.
If you want to use other columns, you should use Model.find_by_xxx
so here it could be
def show
#company = Company.find_by_name(params[:name])
end
The :id parameter is whatever comes after the slash when the URL is requested, so a name attribute needs to be extracted from this by checking the :id parameter for non-numerical values with regular expressions and the match? method in the controller. If a non-numerical value is present, the instance can be assigned by the name attribute using the find_by_name() method that rails generated for the model (assuming that the model has an attribute called name)
That's how I figured out how to do it in my app with my Users resource. My users have a username attribute, and all I had to do was modify the UsersController to define my #user variable differently depending on the :id parameter:
private
# allow routing by name in addition to id
def get_user
if params[:id].match?(/\A\d+\Z/)
# if passed a number, use :id
#user = User.find(params[:id])
else
# if passed a name, use :username
#user = User.find_by_username(params[:id])
end
end
This gives me the option to use either id or username when I create a link to a particular user, or type it into the browser's address bar.
Then the only other (optional) thing to do is to change all the links in the views so that they point to the URL with the name instead of the URL with the id.
For example, within the link_to() method call in my navigation bar I changed
... user_path(current_user) ...
to
... user_path(current_user.username) ...
In your example, you might have a company view with the following link:
<%= link_to #company.name, company_path(#company.name) %>
Which, if the current company is Microsoft, would display "Microsoft" and link to "companies/Microsoft", even though the URL "companies/1" would still be valid and display the same thing as "companies/Microsoft"
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)