Devise Registration URL could not be found - ruby-on-rails

I have a problem with devise after customizing the signup - route.
The devise doc mentions, that routes can easily be customized, so I tried to add a token to the URL to set up a easy invitation system. Ist really straight forward and all I did was adding
devise_for :users, :path_names => { :sign_up => "signup/:invitation_token" }
to my routes. A mailer sends an email with the token and inside I pass
new_user_registration_path(#invitation.token)
rake routes says
new_user_registration GET /users/signup/:invitation_token(.:format) devise/registrations#new
But I'm still getting
No route matches {:action=>"new", :controller=>"devise/registrations", :locale=>:de, :invitation_token=>nil}
I get it wether I pass the token or not...
I'm not shure what I'm missing.
Thanks in advance, hope someones sees what I'm doing wrong.
Greets, Rob

Check #invitation.token to make sure that it's not nil.
The error you're witnessing will occur on any view in which you pass nil into your new_user_registration_path link tag.
Bear in mind that you'll need to override the default behavior of Devise's users/registration controller in order to get your invitation system working correctly. Something like this would work:
# routes.rb
devise_for :users, :path_names => { :sign_up => "signup/:invitation_token" }, :controllers => {:registrations => "users/registrations"}
# app/controllers/users/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
def create
# add custom create logic here
end
end

Related

Customize part of devise custom routes, basically I don't know Rails routing =(

I feel like my brain left the building when I was learning Rails routing... I can't figure this out.
So I have customized some devise controller, and therefore I have updated the route file like so:
devise_for :users, controllers: {
registrations: "users/registrations",
sessions: "users/sessions",
passwords: "users/passwords"
}
That works GREAT. It gives me paths like this:
new_user_registration GET /users/sign_up(.:format) users/registrations#new
The challenge now is I want to run an A/B test using Google Analytics where I need 2 more pages for the sign up.
So in my controller this is how I would modify:
class Users::RegistrationsController < Devise::RegistrationsController
def new
end
# ADD BELOW
def new_control
end
def new_test
end
end
But I can't figure out how to modify my routing so that I have these 2 new routes in additional to the old new_user_registration_path (note the named path helper for these new ones don't matter so much to me because I never actually use it)
GET /users/sign_up/control(.:format) users/registrations#new_control
GET /users/sign_up/test(.:format) users/registrations#new_test
Note that I want to keep all the other lovely routes the devise_for code has created, e.g., the create and edit actions
You can access specify the route normally as you would do in a rails app. Only thing you need to do is wrap the route inside a device_scope. This also shows up as warning when you try to access the route without adding the device_scope.
So in your case routes should be like:
devise_scope :user do
get 'users/sign_up/control' => 'users/registrations#new_control'
get 'users/sign_up/test' => 'users/registrations#new_test'
end

Devise authentication as API only

I'm trying to use Devise authentication with Angular.js. Everything is already working except I want to hide the server-side login form, i.e. the result of /users/sign_in GET request leaving only the possibility of /users/sign_in POST request. Is this possible?
I think you'll need to skip sessions in your devise_for call in routes.rb (or not even call it) and just stick to devise_scope for setting up your sign_in path. So, your routes.rb would look like this:
devise_for :users, :skip => :sessions
devise_scope do
post "/users/sign_in" => "devise/sessions#create"
delete "/users/sign_out" => "devise/sessions#destroy"
end

Rails Devise user controller in subfolder

I am using omniauth and found devise using a subfolder for this(in official example) controllers/users/omniauth_callbacks_controller.rb. I need to create a User show page as well as other actions for User so I decide to create a new UsersController inside a controllers/users folder. Now it looks like
class Users::UsersController < ApplicationController
def show
#user = User.find(params[:id])
end
end
routes.rb
My::Application.routes.draw do
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
match 'users/:id' => 'users/users#show'
root :to => 'home#index'
end
it works but the route created is unnamed
rake routes gives
/users/:id(.:format) users/users#show
without GET and route_name
so I'm unable to use it for example after login redirect. Is there a better way to realize the subfolder routes structure and is it good idea to group controllers like this?
You just need name your route in your route.rb
match 'users/:id' => 'users/users#show', :as => 'user'
After that you can call this route by user_url(user.id)
See example on guides : http://guides.rubyonrails.org/routing.html#naming-routes

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.

Devise change the user_omniauth_callback route

I am working with OmniAuth to use Facebook Connect in my Devise based rails app. One of the routes it creates is:
user_omniauth_callback /users/auth/:action/callback(.:format) {:action=>/facebook/, :controller=>"devise/omniauth_callbacks"}
I'd like to modify this route to a custom URL. Where would be the right place to do that?
the problem is by default, the route it creates is http://foo/users/auth/:action/callback.format. I want to have something more custom like http://foo/prefix_path/users/auth/:action/callback.format. I tried making my routes file look like the following:
scope "/mypath" do
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
end
but it still generates the wrong route:
user_omniauth_callback /users/auth/:action/callback(.:format) {:action=>/facebook/, :controller=>"users/omniauth_callbacks"}
I'm not exactly sure what you are asking, I assume you want to have your own custom code for the callback.
You can extend the devise controller such as:
class MyOmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
#Custom Code here
end
end
Then you can register this new controller in your routes.rb
devise_for :users, :controllers => {:omniauth_callbacks => "my_omniauth_callbacks"}
EDIT:
devise can also take a 'path' option in the devise_for so changing the route:
devise_for :users, :controllers => {:omniauth_callbacks => "my_omniauth_callbacks"}, :path => "path_prefix/users"
If you are unsatisfied with omniauthable in devise itself, then you may consider implementing omniauth as separate gem and then just tie it with device.
To modify routes, you may use :match as well and map those routes to omniauth_callbacks url. Didn't get why you want to
I'd like to modify this route to a custom URL.
Decribe what you want to make different that what is available.

Resources