Customizing User Show Route in Rails - ruby-on-rails

I want, instead of /user/:id I want the default user route to be /user:created_at I was able to get /user:id to work (without the second /) however when I try to do :created_at I get an error.
Does anyone know how to fix this? Also, even though I have match 'users:id', :to => 'users#show', :as => :user, :via => :get /user/1 still is a valid link since I have resources :users in my config/routes.rb. Is there a way to remove the default /user/:id when rails compiles the resources :users?

If I understand it correctly you can either use to_param: http://apidock.com/rails/ActiveRecord/Base/to_param
so if you used in your user model
def to_param
"#{first_name}_#{last_name}"
end
then user_path(#user) would generate /users/planet_pluto for example
to prevent a route to be generated by map.resources simply use :except
map.resources :user, :except => [:show]
hope that helps

Related

Abstracting rails route

I want to replace the normal /users/:id route that is created by the resources command, with a more abstract /profile route. It won't be possible to view other users profiles in my app, and therefor the current route is unnecessary specific.
I have tried to overwrite the route created by 'resources :users' with:
get '/profile', to: 'users#show'
and other variances and combinations, but can't seem to get it right. Either the controller can't find the user because of a missing id or it simply can't find the route.
Thanks for the help!
You can use this code in routes.rb file:
resources :users, :except => :show
collection do
get 'profile', :action => 'show'
end
end
It will generate url "/users/profile".
But, if u want to use only '/profile', then don't create route as collection inside users resources block.
resources :users, :except => :show
get 'profile' => "users#show", :as => :user_profile
It will redirect '/profile' to show action in users controller.
I suggest simply adding a users/me route pointing to the show action of your UsersController like so:
resources :users, only: [] do
collection do
get 'me', action: :show
end
end
You can also use the match keyword in routes.rb file.
match 'users/:id' => 'users#show', as: :user_profile, via: :get

ruby custom routes

I am new to ruby on rails.
Here is my routes.rb
RpxNowExample::Application.routes.draw do
root :to => "users#index"
resources :users
end
Normally my functionality is working fine, but I want to make a tweak. I want it to redirect to another view "promptemail" using the same controller calling another action if a condition is true i.e
if(#provider == "Twitter")
redirect_to :action => :promptemail
end
It should take me to that promptemail view.
You can pass a block to your resources definition to add extra actions outside of the standard:
resources :users do
match :promptemail, :via => [:get], :on => :member
end
The :via option allows you to restrict on get, post, put etc, the :on parameter will take either :member, or :collection.
:collection will operate on a collection, so similar to the index action, :member will operate on an individual record. As such if you specify your route as :on => :member, you will need to provide an object or id when you generate the route.
More info about adding routes to resources can be found here: http://guides.rubyonrails.org/routing.html#adding-more-restful-actions
Using the same controller calling another action-
Try:
routes.rb:
RpxNowExample::Application.routes.draw do
root :to => "users#index"
resources :users do
collection do
get 'promptemail'
end
Your controller:
if(#provider == "Twitter")
redirect_to :promptemail
end
Note: - No need to call action in redirect_to as you are calling the action within the same controller.

How to declare a rails resource with a parameter for new action?

I have a model named Entree for which the new action needs a parameter, the id of another model named Cave. I don't want to nest Entree in Cave since Cave is already nested.
What I did was declaring the resource Entree as follow in routes.rb:
resources :entrees, :except => [:new]
match "/entrees/new/:id", :to => "Entrees#new", :as => 'new_entree'
That works, but the problem is when there's an error in the create action, I want to display the page again with the invalid input. But since there's no new action, I must do a redirect_to new_entree_path, which does not keep the user input.
I have tried the following (simplest) route:
resources :entrees
But then the path http://localhost:3000/entrees/new/32 returns an error:
No route matches [GET] "/entrees/new/32"
The question is, how can I declare the Entree resource in the routes file with a parameter for the new action ?
I'm not sure if that's a hack or not, but the following works and seems cleaner than 2-levels nesting.
resources :entrees, :except => [:new] do
collection do
get 'new/:id', :to => "entrees#new", :as => 'new'
end
end
Now I can do a render "new" instead of a redirect_to.
I must say that I must have asked my question wrongly, my bad.
Rails has a route helper called path_names that does this:
resources :entrees, path_names: { new: 'new/:id' }
To improve gwik 's solution (which in fact didn't work for me):
resources :things, except: [:new] do
new do
get ':param', to: 'things#new', as: ''
end
end
It gets you new_thing_* helpers (instead of new_things_*) for free.
If you want to use Rails resource routes, you will have to nested them according to how they work
resources :caves do
resources :entrees
end
to get the route /caves/70/entrees/new
Otherwise, you are in a world of creating manual match routes.
match "/entrees/new/:id", :to => "entrees#new", :as => 'new_entrees'
I do not understand why you are forced to use a redirect? The new_entrees route is valid. You will not be able to use the form_for helper, since it is not a resource, but the form_tag helper will work.
UPDATE: Render and Route
The Route does not directly change what view is rendered in the Controller. That is determined by the controller itself. Render examples:
render :new will render the new action's view
render 'entrees/new' will render the entrees/new template
I found this generates the correct new_thing_path method not new_things_path as Antoine's solution.
resources :things, :except => [:new] do
with_scope_level(:new) do
get 'new/:param', :to => "things#new", :as => ''
end
end

Devise routing: is there a way to remove a route from Rails.application.routes?

devise_for creates routes including a DELETE route, which we want to remove, and devise_for doesn't support an :except or :only option.
How can I remove a route from Rails.application.routes? Either in the draw block, or afterward?
Here are details of a bug, which was the reason we needed to remove the route.
we were issuing a DELETE request to a custom UJS controller action
in the controller action we were removing what we wanted to, then doing a 302 redirect. This was a bad idea, and we have since corrected it by returning some JSON instead.
some clients, upon receiving the 302 would issue a new DELETE request to the redirect, which routes to a Devise delete route! Thereby inadvertantly deleting the person! Yikes. We were assuming this would be a GET. Bad assumption.
This bug has been fixed, but i would like to remove the route nonetheless.
Here is what I did in the end, which was suggested by the bounty-winner in his quote from JoseĀ“ Valim:
In config/routes.rb, I added this above the devise_for call, which sets up the rest of my 'people' routes:
delete '/person', :to => 'people#destroy'
Then in my existing people_controller.rb, I added a no-op method:
def destroy
render :nothing => true
end
I'm still a little irked that there isn't a simple way to just remove the route from the RouteSet. Also, the delete route still exists for the devise controller, but it won't get called because rails looks for the first match in config/routes.rb and returns it.
Here is what Jose Valim (the author of devise) has to say on the subject:
There is no way to remove routes individually. Or you use :skip to
remove all and draw the ones you need manually or you overwrite this
routes by defining a route to the same path first in your config/
routes.rb
So the short answer to your question is no, you can't delete that one route. You can of course try doing things like patching the devise_for method, but that would be a somewhat involved undertaking (a day or several worth of effort). I'd just use the :skip option, then implement the routes you do want for that controller and leave off the one that you don't.
Yes, kinda. You can completely overwrite devise controllers used and write your own (inheriting Devise's if needed). This wiki page can serve as guideline.
Edit
Why I have said kinda :)
Overriding sessions using:
devise_for :users, :controllers => { :sessions => 'custom_devise/sessions'}, :skip => [:sessions] do
get 'sign_in' => 'custom_devise/sessions#new', :as => :new_user_session
post 'sign_in' => 'custom_devise/sessions#create', :as => :user_session
end
will give you only two routes [:get, :post], but not :destroy
new_user_session GET /sign_in(.:format) {:controller=>"custom_devise/sessions", :action=>"new"}
user_session POST /sign_in(.:format) {:controller=>"custom_devise/sessions", :action=>"create"}
So, effectively, you skip destroy/delete route. Now in controller you can go:
class SessionsController < Devise::SessionsController
def new
super
end
def create
super
end
end
You can now repeat the process for registrations, passwords and unlocks.
Second Edit
Ah, yes there is another, simpler way. You can manually create routes (documentation) using devise_scope also known as "as" without overriding:
as :user do
get "sign_in", :to => "devise/sessions#new"
post "sign_in", :to => "devise/sessions#create"
...
end
Gives:
sign_in GET /sign_in(.:format) {:controller=>"devise/sessions", :action=>"new"}
POST /sign_in(.:format) {:controller=>"devise/sessions", :action=>"create"}
Third Edit
Also, you could overwrite part of Devise in charge of creating these routes, (only to be used in applications that will have no devise "destroy" route whatsoever), by creating an initializer:
module ActionDispatch::Routing
extend ActionDispatch::Routing
class Mapper
protected
def devise_session(mapping, controllers) #:nodoc:
resource :session, :only => [], :controller => controllers[:sessions], :path => "" do
get :new, :path => mapping.path_names[:sign_in], :as => "new"
post :create, :path => mapping.path_names[:sign_in]
end
end
def devise_registration(mapping, controllers) #:nodoc:
path_names = {
:new => mapping.path_names[:sign_up],
:cancel => mapping.path_names[:cancel]
}
resource :registration, :only => [:new, :create, :edit, :update], :path => mapping.path_names[:registration],
:path_names => path_names, :controller => controllers[:registrations] do
get :cancel
end
end
end
end
Note that this fix removes all destroy routes used in Devise (there are only two in "sessions" and "registrations") and is a fix only for this specific case.
In addition
You could also add :except option to routes. In order to do it, you must add devise_for method (copy it from original and modify to suit your wishes) to Mapper class so it sends [:except] member of options to above-mentioned (in code) private methods.. Then you should modify those to add routes based on conditions.
Fastest, dirty way, would be to add #scope[:except] = options[:except] and then to modify private methods so that except hash (if you decide to have fine grained route control like: :except => {:sessions => [:destroy]}, thus making :skip obsolete) or array (if you want to remove this specific action from all routes, like: :except => [:destroy]) is checked before adding route.
Anyway, there are plenty ways to achieve what you need. It's up to you to pick the one you think is best suited.
Actually devise_for does support :skip and :only, for example (docs):
devise_for :user, :skip => :registration
This will skip all the registration controller's routes, rather than one specifically. You could then implement the routes you need. This seems cleaner than removing the route after the fact.
UPDATE:
Another possible solution is to use Rails' advanced constraints feature to block the unwanted route completely:
# config/routes.rb
constraints lambda {|req| req.url =~ /users/ && req.delete? ? false : true} do
devise_for :users
end
Here is a post on using lambdas for route constraints. The request object is explained here. This might be the simplest solution.
I found a simple solution with Devise 4.2.0 and Rails 5.0.1. I think this will work with Rails 4, and I'm uncertain about older versions of Devise.
Create an initializer overriding the devise_* route helpers. Examples methods are devise_session, devise_password, devise_confirmation, devise_unlock, and devise_registration. Check out the source.
Ensure the initializer is loaded after the Devise initializer by giving the filename a larger alphanumeric value.
For example, Devise creates a :confirmation route with the :new, :create, and :show actions. I only want the :create action.
# config/initializers/devise_harden.rb
module ActionDispatch::Routing
class Mapper
# Override devise's confirmation route setup, as we want to limit it to :create
def devise_confirmation(mapping, controllers)
resource :confirmation, only: [:create],
path: mapping.path_names[:confirmation], controller: controllers[:confirmations]
end
end
end
Now POST /auth/confirmation is the only route setup for confirmation.

Difference between :as option in Rails 2 and Rails3 routing?

In Rails 2.X we have:
map.resources :posts, :controller => 'posts', :as => 'articles'
This essentially creates an alias for our posts routes. For example, this sends "domain.com/articles/" to the posts controller index action.
In Rails3, however, the :as option behaves differently. For example:
resources :posts, :controller => 'posts', :as => 'articles'
sets a named route rather than an alias, and going to "domain.com/articles/" gives an error:
No route matches {:controller=>"posts"}
How do I get the old (Rails 2) :as behavior using the new (Rails 3) api?
PS: Please don't tell me to simply rename my controller. That's not an option for me.
From some cursory reading of the RoR guide on routing, I think you might need to try:
resources :articles, :controller => "posts"
(http://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use)
You might also need to add :as => "articles", but that named helper might already be set up since you are adding :articles resources.
You can accomplish this same behavior using the path option:
resources :posts, :path => '/articles/'
Now for example /posts/new becomes /articles/new.

Resources