get 'users/:id/edit/settings' => 'users#account'
What is the dry way to reference this path in link_to?
As a side note, I use 'users/:id/edit' to edit name/location/age etc and I am using the route above to edit password and email, because I wish to force the user to authenticate their :current_password before editing these more sensitive attributes. I mention this just to make sure my routing logic is correct.
Just run rake routes and you will see all the routes that you have in you app. It should be to the far right
You can use the as: option to setup a named route.
However I would set it up with conventional rails routes:
Rails.application.routes.draw do
resources :users do
resource :settings, only: [:edit, :update], module: :users
end
end
This would create an idiomatically correct RESTful route.
Using the singular resource creates routes without an id parameter. Also you should only use the name :id for the rightmost dynamic segment in a route to avoid violating the principle of least surprise.
rake routes will show you the following routes:
Prefix Verb URI Pattern Controller#Action
edit_user_settings GET /users/:user_id/settings/edit(.:format) users/settings#edit
user_settings PATCH /users/:user_id/settings(.:format) users/settings#update
PUT /users/:user_id/settings(.:format) users/settings#update
...
As a side note, I use 'users/:id/edit' to edit name/location/age etc
and I am using the route above to edit password and email, because I
wish to force the user to authenticate their :current_password before
editing these more sensitive attributes. I mention this just to make
sure my routing logic is correct.
Your route will in no way enforce this authorization concern.
Instead you should do a check in your controller:
# app/models/users/settings_controller.rb
class Users::SettingsController
before_action :set_user
before_action :check_password, except: [:edit]
def edit
# ...
end
def update
# ...
end
private
def set_user
#user = User.find(params[:user_id])
end
def check_password
# this is an example using ActiveModel::SecurePassword
unless #user.authorize(params[:current_password])
#user.errors.add(:current_password, 'must be correct.')
end
end
end
change it to:
get 'users/:id/edit/settings' => 'users#account', as: :edit_user_settings
and then you can just reference it as:
link_to edit_user_settings_path(#user)
rake routes will probably give you a path something like users_path which you can link to using something like
<%= link_to 'Users', users_path(#id) %>
Related
I have some more complex routes setup in my app, that I'm wondering could be turned into resourceful routes.
What are the ideal Rails conventions for turning these into resourceful routes?
Route 1: /grandparent-place/parent-place/place/
These routes are at the bottom of my routes.rb file, as they pull from the root path and are scoped by parents and children.
Routes.rb
get ':grandparent_id/:parent_id/:id', to: 'places#show', as: :grandparent_place
get ':parent_id/:id', to: 'places#show', as: :parent_place
get ':id', to: 'places#show', as: :place
Places_Controller.rb
def set_place
if params[:parent_id].nil? && params[:grandparent_id].nil?
#place = Place.find(params[:id])
elsif params[:grandparent_id].nil?
#parent = Place.find(params[:parent_id])
#place = #parent.children.find(params[:id])
else
#grandparent = Place.find(params[:grandparent_id])
#parent = #grandparent.children.find(params[:parent_id])
#place = #parent.children.find(params[:id])
end
end
Application_Helper.rb
def place_path(place)
'/' + place.ancestors.map{|x| x.id.to_s}.join('/') + '/' + place.id.to_s
end
Route 2: /thread#post-123
These routes are meant to allow for only specific actions, using the parent module to specify the controller directory - and using a # anchor to scroll to the specified post.
Routes.rb
resources :threads, only: [:show] do
resources :posts, module: :threads, only: [:show, :create, :update, :destroy]
end
Application_Helper.rb
def thread_post_path(thread, post)
thread_path(thread) + '#post-' + post.id.to_s
end
Is it convention to override the route paths in the application helper, or is there a better way to generate the correct URLs without overriding the helpers?
Path variables are used to specify resources and usually one variable specify one resource. For example:
get '/publishers/:publisher_id/articels/:article_id/comments/:id'
In your setup you have places as a resource.
So, in this endpoint get '/places/:id' :id specifies which place should be retrieved.
Regarding your first route it would be most appropriate to leave just one get endpoint:
resource :places, only: [:show] # => get '/places/:id'
and pass the id of the parent or grandparent as :id whenever you need to retrieve the parent or grandparent place. That way you won't need any conditions in set_place method and so have:
def set_place
#place = Place.find(params[:id])
end
In case you need to access parents or grandparents of a place object you can build:
get '/places/:place_id/parents/:parent_id/grandparents/:id'
or just leave get '/places/:place_id/parents/:id' and whenever you need to access a grandparent just make the call starting from your parent place instead of the child. Route setups can vary depending on your needs. Rails provides various examples on that matter: Rails Routing from the Outside In
Regarding the helpers, there isn't a general rule for overriding or not your path methods and again it mostly depends on the application's needs. I think it is a good practice to keep them intact as much as possible. In your case instead of overriding the path method you can place:
thread_posts_path(thread) + '#post-' + post.id # => /threads/7/posts#post-15
directly in your view, for example:
link_to 'MyThredPost', thread_posts_path(thread) + '#post-' + post.id
I find the resource route method quite convenient, but I totally hate that it does not create create and destroy path helpers.
I understand that writing
<% form_for(#object) %>
is supposed to automatically get the route name, and that we can play with arrays or symbols to automatically get the namespace/prefixes when they exist, but I have many routes with complicated scope definitions, and not being able to get create_xxx helpers totally annoys me
Is there no simpler solution than to write ? (I am trying to keep the default RESTful URLs while generating the helpers)
complicated_scope do
resources :my_resources, except: [:create, :destroy] do
post '', on: :collection, action: :create, as: 'create' # plus this generates a pluralized version, not very intuitive `create_complicated_scope_my_resourceS_path`
delete '', on: :member, action: :destroy, as: 'destroy'
end
end
EDIT. My example of 'somewhat complicated scope'
# Company access routes under /company/
namespace :company do
# I need a company id for all nested controllers (this is NOT a resource strictly speaking, and using resources :companies, only: [] with 'on: :collection' doesn't generate appropriate urls)
scope ':company_id' do
# Company administrators
namespace :admin do
# There is a lot of stuff they can do, not just administration
namespace :administration do
# There are several parameters grouped in different controllers
resources :some_administrations do
... # finally RESTful actions and others here
end
end
end
end
end
Resourceful routing does create create and destroy helpers, but they're implied by the type of HTTP request being made (POST and DELETE respectively) so the routing helper methods should work fine with the code you've provided.
Suppose you have the following route definition:
complicated_scope do
resources :my_resources
end
end
As a simple example, in the case of delete, you could use a named route like so:
link_to "Delete [resource]", complicated_scope_resource_path(id: #my_resource.id), method: :delete
Since the HTTP verb disambiguates the controller action this helper method routes to the destroy method of the controller.
Alternatively, you should be able to use the array syntax as well.
link_to "Delete [resource]", [:complicated_scope, #my_resource], method: :delete
The same goes for forms:
<%= form_for [:complicated_scope, #my_resource] do |f| %>
If #my_resource is a new object (not persisted), as in the case of a new action this would be equivalent to sending a post request to /complicated_scope/my_resource with the form params going in the body of the request.
Alternatively if #my_resource exists, as in the case of an edit action, the above would be equivalent to sending a PUT/PATCH which will route to the update action of your controller with /complicated_scope/my_resource/:id/update.
<%= link_to "Whatever", current_user %>
is linking to /user.id
My routes are setup like this
resource :user, except: [:index, :destroy]
So it should be linking to /user, right?
When I visit /user it says "Couldn't find User without an ID".
My user show action looks like this
def show
#user = User.find(params[:id])
end
The reason you are getting /user.id is because you have defined your routes as
resource :users, except: [:index, :destroy]
Note the singular resource which will create all the routes without any :id. As you don't have an accepting param within the route, the current_user that you are passing is matched to the format i.e., like .html, .js, etc. which in your case becomes .id
I would recommend using resources (note the plural)
resources :users, except: [:index, :destroy]
This will resolve the error Couldn't find User without an ID as you would be passing an params id within your route.
NOTE:
As per Rails convention, controller name should be plural. For UsersController, resources should be resources :users
I have had this happen many times. My fix is to use the path helpers.
<% link_to "Whatever", user_path current_user %>
This will drop the .id and make it /user/id
Is it a bad practice to leave the user's database id in the url like this:
localhost:3000/users/16/edit
If it is bad, how can I hide the id in the url? What do I have to watch out when calling the path in my view, routes.rb, etc?
If this is relevant to the discussion, the user resource looks like this in my routes.rb:
resources :users, only: [:new, :edit, :create, :update]
Simply override to_param in ActiveRecord::Base subclass
class User < ActiveRecord::Base
validates_uniqueness_of :name
def to_param #overriden
name
end
end
Then query it like this
user = User.find_by_name('Phusion')
user_path(user) # => "/users/Phusion"
Alternatively you can use gem friendly_id
While you can use friendly ids as described by hawk and RailsCast #314: Pretty URLs with FriendlyId, using the primary key in your routes is standard practice, maybe even best practice. Your primary key ensures the right record is being fetched whenever '/posts/1/edit' is being called. If you use a slug, you have to ensure uniqueness of this very slug yourself!
In your specific case it seems that you are building some kind of "Edit Profile" functionality. If each user is to edit only his or her own profile, you can route a Singular Resource.
Possible routes:
/profile # show profile
/profile/edit # edit profile
Then your user's primary key would not be visible from the URL. In all other models, I'd prefer to go with the id in the URL.
Based on your route, it looks like your users will not have a publicly visible profile. If this is the case then you can simply use example.com/settings for Users#edit and example.com/sign_up for Users#new.
get 'settings', to: 'users#edit'
get 'sign_up', to: 'users#new'
resource :users, path: '', only: [:create, :update]
If your users will indeed have a publicly visible profile in the future, then you can either use the friendly_id gem as suggested by hawk or perhaps a randomized 7 digit ID by overwriting it before creating the record. After a bit of research, this is how I ended up doing it:
before_create :randomize_id
private
def randomize_id
self.id = loop do
random_id = SecureRandom.random_number(10_000_000)
break random_id unless random_id < 1_000_000 or User.where(id: random_id).exists?
end
end
I'm making a page with activeadmin to update password of current user. I have a non-persisted model to check validation of password, etc. My problem is that when I try
ActiveAdmin.register UpdatePassword do
actions :edit, :update
end
It creates the routes /update_passwords/:id and /update_passwords/:id/edit.
I want to change those routes to /update_passwords via get and put.
Is there any way to change that?
I couldn't find a way to do it with activeadmin but defining the routes manually worked:
#config/routes.rb
match "/admin/update_passwords" => 'admin/update_passwords#edit', via: :get, as: "admin_update_passwords"
match "/admin/update_passwords" => 'admin/update_passwords#update', via: :post
Though the question is about 2 years old, but you can achieve routing as well as the customized method using collection_action or member_action. Refer this.
It seems to me that the controller name UpdatePassword is confusing.
The paths end up being something like:
edit_admin_update_passwords_path
update_admin_update_passwords_path
I think that this would be better:
ActiveAdmin.register Password do
actions :edit, :update
end
or
ActiveAdmin.register User do
actions :edit, :update
end