Rails / Devise customize the create method - ruby-on-rails

I generate a special code which is unique to each user and generated on registration/create. I want to store it in the DB on create.
class Users::RegistrationsController < Devise::RegistrationsController
before_action :configure_permitted_parameters
def create
# insert special code into instance and ensure that code is unique in database
super # continue to devise registration to CREATE user
end
protected
def special_code
( (0...8).map { char = (65 + rand(26)).chr; }[0..rand(2...4)] << rand(1..9) ).join.downcase
end
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) << [:name, :gender, :birthday]
end
end
As you can see from my code, I added :name, :gender, :birthday columns (this works fine). I just don't know how to get the special_code into the record. Please advise. Thanks!

You will need to modify the create method. First find what the user is assigned to in create, (probably resource) and then assign the special code to it
def create
# insert special code into instance and ensure that code is unique in database
super # continue to devise registration to CREATE user
resource.update special_code: special_code # may want to check model is still valid at this point and handle errors if not
end
also in routes you will need to tell it to use this controller
devise_for :users, :controllers => { :registrations => 'users/registrations' }

I did it the following way (adjusting my own code to your situation):
app/controllers/concerns/user_resource.rb
module UserResource
extend ActiveSupport::Concern
include Resource
included do
def resource_name
:user
end
def resource
if user_signed_in?
current_user
else
#resource || User.new
end
end
def build_resource(hash=nil)
custom_params = hash.present? ? hash : {}
custom_params[:special_code] = special_code
self.resource = resource_class.new_with_session(custom_params || {}, session)
end
def devise_mapping
#devise_mapping ||= Devise.mappings[:user]
end
helper_method :resource_name
helper_method :resource
helper_method :devise_mapping
protected
def special_code
( (0...8).map { char = (65 + rand(26)).chr; }[0..rand(2...4)] << rand(1..9) ).join.downcase
end
end
end
Your special code might belong into the UserResource class.
app/controllers/concerns/resource.rb
module Resource
extend ActiveSupport::Concern
included do
before_action { |c| c.resource}
def resource_name
raise NotImplementedError
end
def resource
raise NotImplementedError
end
def devise_mapping
raise NotImplementedError
end
end
end
app/controllers/users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController
include UserResource
def create
resource = build_resource(sign_up_params)
yield resource if block_given?
if resource.save
if resource.active_for_authentication?
set_flash_message :notice, :signed_up if is_flashing_format?
sign_up(resource_name, resource)
respond_with resource, location: after_sign_up_path_for(resource)
else
set_flash_message :notice, :"signed_up_but_#{resource.inactive_message}" if is_flashing_format?
expire_data_after_sign_in!
respond_with resource, location: after_inactive_sign_up_path_for(resource)
end
else
flash[:error] = "Sorry, we could not save you in our database. Please contact an adminstrator. #{resource.inspect}"
clean_up_passwords resource
respond_with resource
end
end
end
The routes should look like this. It seems you've already done that.
app/config/routes.rb
devise_for :users, :controllers => {
registrations: 'users/registrations'
}
Because the code did not work when I attempted to use a shorter solution, I had to copy some code from the original Devise module. Don't ask me why my previous code didn't work, it was really weird and kind of hard to fix.

Related

Custom devise controller is redirecting to wrong path

My sign up routes are users/sign_up/speed1 etc. However if there is a devise error such as password does not match it redirects the user to '/users' path. Functionality it should redirect back to the same page and let the user re-enter a new password. Any update on this?
class RegistrationsController < Devise::RegistrationsController
def new
super
end
def create
super
end
def update
super
end
def speed1
build_resource
yield resource if block_given?
respond_with resource
end
def speed2
build_resource
yield resource if block_given?
respond_with resource
end
def speed3
build_resource
yield resource if block_given?
respond_with resource
end
def speed4
build_resource
yield resource if block_given?
respond_with resource
end
def speed5
build_resource
yield resource if block_given?
respond_with resource
end
protected
def after_sign_up_path_for(resource)
if current_user.role == 'speed1' || current_user.role == 'speed2' || current_user.role == 'speed3' || current_user.role == 'speed4' || current_user.role == 'speed5'
'/subscription/new' # Or :prefix_to_your_route
else
'/'
end
end
end
Here is my route file that I have
devise_for :users, :controllers => {:registrations => "registrations"}
devise_scope :user do
get 'users/sign_up/speed1', to: 'registrations#speed1'
get 'users/sign_up/speed2', to: 'registrations#speed2'
get 'users/sign_up/speed3', to: 'registrations#speed3'
get 'users/sign_up/speed4', to: 'registrations#speed4'
get 'users/sign_up/speed5', to: 'registrations#speed5'
end
You can add this to your application_controller.rb, it will return to the last page the user visited, or root_path
private
# If your model is called User
def after_sign_in_path_for(resource)
session["user_return_to"] || root_path
end

Customizing devise controller

I am trying to change slightly the behaviour of one devise controller method. Here it says I can do that running rails generate devise:controller users.
However doing that just generated commented code with a bunch of super calls. If I don't know what the super methods do, how am I supposed to edit the lines of code I want to change?
The modification I want to do is simple: if there is no admin user yet (none with role=admin was found), then the user's role will be set to admin, else, it will be a normal user. So I thought in this case an after_filter would be the solution, so I did this:
class UserController < Devise::RegistrationsController
after_filter :set_role, only: [:create]
protected
def set_role
admin_user = User.find_by_role(User::admin_role)
if admin_user.nil?
#user.role = User::admin_role
else
#user.role = User::default_role
end
#user.save
end
end
My routes:
devise_for :users, controllers: { users: "users" }
However, the method is not even being executed. Why? What can I do?
I think you need to be overriding the registrations controller:
devise_for :users, :controllers => {:registrations => "registrations"}
Then the controller:
controllers/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
def create
build_resource(sign_up_params)
admin_user = User.find_by_role(User::admin_role)
if admin_user.nil?
resource.role = User::admin_role
else
resource.role = User::default_role
end
if resource.save
if resource.active_for_authentication?
set_flash_message :notice, :signed_up if is_navigational_format?
sign_up(resource_name, resource)
respond_with resource, :location => after_sign_up_path_for(resource)
else
set_flash_message :notice, :"signed_up_but_#{resource.inactive_message}" if is_navigational_format?
expire_session_data_after_sign_in!
respond_with resource, :location => after_inactive_sign_up_path_for(resource)
end
else
clean_up_passwords resource
respond_with resource
end
end
end

Devise sign up AJAX not working

I am trying to setup a registration form using devise and AJAX.
On a newly generated app it works perfectly but when I try and add it to a project, I do not have success
registration controller
https://gist.github.com/mosinski/8568126
my partial form:
https://gist.github.com/mosinski/8577429
my application.js:
https://gist.github.com/mosinski/8577480
my inicializer:
https://gist.github.com/mosinski/8577533
my application helper:
https://gist.github.com/mosinski/8577564
my form is partial in bootstrap modal.
The whole problem is that it not sending as js but as text/html why ?! ;]
1. Routes
Have you declared the custom controllers in your routes.rb file?
#config/routes.rb
devise_for :users, controllers: { sessions: "sessions", registrations: "registrations" }
2. Forms
Are you sure you're sending the request from your partial form?
Devise generates its own views (which you can typically find at /views/devise). If you're using the standard Devise forms, you'll be sending standard HTML mime-types, and so you'll have to ensure you're using your partial
That's all I can glean from your code currently! Of course we can discuss any fixes in the comments
Update
Here's our working registrations controller:
class RegistrationsController < DeviseController
prepend_before_filter :require_no_authentication, :only => [ :new, :create, :cancel ]
prepend_before_filter :authenticate_scope!, :only => [:edit, :update, :destroy]
before_filter :configure_permitted_parameters
prepend_view_path 'app/views/devise'
# GET /resource/sign_up
def new
build_resource({})
respond_with self.resource
end
# POST /resource
def create
build_resource(sign_up_params)
if resource.save
if resource.active_for_authentication?
set_flash_message :notice, :signed_up if is_navigational_format?
sign_up(resource_name, resource)
respond_with resource, :location => after_sign_up_path_for(resource)
else
set_flash_message :notice, :"signed_up_but_#{resource.inactive_message}" if is_navigational_format?
expire_session_data_after_sign_in!
respond_with resource, :location => after_inactive_sign_up_path_for(resource)
end
else
clean_up_passwords resource
respond_to do |format|
format.json { render :json => resource.errors, :status => :unprocessable_entity }
format.html { respond_with resource }
end
end
end
# GET /resource/edit
def edit
render :edit
end
# PUT /resource
# We need to use a copy of the resource because we don't want to change
# the current user in place.
def update
self.resource = resource_class.to_adapter.get!(send(:"current_#{resource_name}").to_key)
prev_unconfirmed_email = resource.unconfirmed_email if resource.respond_to?(:unconfirmed_email)
if update_resource(resource, account_update_params)
if is_navigational_format?
flash_key = update_needs_confirmation?(resource, prev_unconfirmed_email) ?
:update_needs_confirmation : :updated
set_flash_message :notice, flash_key
end
sign_in resource_name, resource, :bypass => true
respond_with resource, :location => after_update_path_for(resource)
else
clean_up_passwords resource
respond_with resource
end
end
# DELETE /resource
def destroy
resource.destroy
Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name)
set_flash_message :notice, :destroyed if is_navigational_format?
respond_with_navigational(resource){ redirect_to after_sign_out_path_for(resource_name) }
end
# GET /resource/cancel
# Forces the session data which is usually expired after sign
# in to be expired now. This is useful if the user wants to
# cancel oauth signing in/up in the middle of the process,
# removing all OAuth session data.
def cancel
expire_session_data_after_sign_in!
redirect_to new_registration_path(resource_name)
end
protected
# Custom Fields
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) do |u|
u.permit(:first_name, :last_name,
:email, :password, :password_confirmation)
end
end
def update_needs_confirmation?(resource, previous)
resource.respond_to?(:pending_reconfirmation?) &&
resource.pending_reconfirmation? &&
previous != resource.unconfirmed_email
end
# By default we want to require a password checks on update.
# You can overwrite this method in your own RegistrationsController.
def update_resource(resource, params)
resource.update_with_password(params)
end
# Build a devise resource passing in the session. Useful to move
# temporary session data to the newly created user.
def build_resource(hash=nil)
self.resource = resource_class.new_with_session(hash || {}, session)
end
# Signs in a user on sign up. You can overwrite this method in your own
# RegistrationsController.
def sign_up(resource_name, resource)
sign_in(resource_name, resource)
end
# The path used after sign up. You need to overwrite this method
# in your own RegistrationsController.
def after_sign_up_path_for(resource)
after_sign_in_path_for(resource)
end
# The path used after sign up for inactive accounts. You need to overwrite
# this method in your own RegistrationsController.
def after_inactive_sign_up_path_for(resource)
respond_to?(:root_path) ? root_path : "/"
end
# The default url to be used after updating a resource. You need to overwrite
# this method in your own RegistrationsController.
def after_update_path_for(resource)
signed_in_root_path(resource)
end
# Authenticates the current scope and gets the current resource from the session.
def authenticate_scope!
send(:"authenticate_#{resource_name}!", :force => true)
self.resource = send(:"current_#{resource_name}")
end
def sign_up_params
devise_parameter_sanitizer.sanitize(:sign_up)
end
def account_update_params
devise_parameter_sanitizer.sanitize(:account_update)
end
end

Need best way to whitelist the params in this code

I need to convert this code in my ConfirmationsController to work in Rails 4. I'm having difficulty with the requirements for Strong Parameters. The code I'm using is directly from the Devise page
I'm pretty sure that anything I am calling with params needs to be whitelisted but I can't quite figure out how to do that in this case. What is the best way to accomplish this.
class ConfirmationsController < Devise::ConfirmationsController
def show
self.resource = resource_class.find_by_confirmation_token(params[:confirmation_token]) if params[:confirmation_token].present?
super if resource.nil? or resource.confirmed?
end
def confirm
self.resource = resource_class.find_by_confirmation_token(params[resource_name][:confirmation_token]) if params[resource_name][:confirmation_token].present?
if resource.update_attributes(params[resource_name].except(:confirmation_token).permit(:password, :password_confirmation)) && resource.password_match?
self.resource = resource_class.confirm_by_token(params[resource_name][:confirmation_token])
set_flash_message :notice, :confirmed
sign_in_and_redirect(resource_name, resource)
else
render action: "show"
end
end
end
Try this:
class ConfirmationsController < Devise::ConfirmationsController
def show
self.resource = resource_class.find_by_confirmation_token(confirmation_token) if confirmation_token.present?
super if resource.nil? or resource.confirmed?
end
def confirm
self.resource = resource_class.find_by_confirmation_token(confirmation_token) if confirmation_token.present?
if resource.update_attributes(resource_params) && resource.password_match?
self.resource = resource_class.confirm_by_token(confirmation_token)
set_flash_message :notice, :confirmed
sign_in_and_redirect(resource_name, resource)
else
render action: "show"
end
end
private
def resource_params
# pry
# debugger
params.require(resource_name).permit :password, :password_confirmation
end
def confirmation_token
params.require(resource_name).permit :confirmation_token
end
end
More info: https://github.com/rails/strong_parameters
If you continue having trouble I'd recommend stuffing a debugger or pry in the middle and manipulating the params with permit | require until you have what you'd expect.

Uninitialized Constant ConfirmationsController

I'm trying to do a custom override of devise confirmations so that a new user creates a password after they receive a confirmation email. This is in the devise wiki and can be found here.
When I navigate to the confirmation link, I am, however confronted by the following error
uninitialized constant ConfirmationsController
I've seen this before when I flubbed the name of a controller class (left out an s or something similar), however I can't find anything like that here. The two relevant files I can think to present are my controller and my routes, relevant to devise.
Here's my controller:
class Users::ConfirmationsController < Devise::ConfirmationsController
# Remove the first skip_before_filter (:require_no_authentication) if you
# don't want to enable logged users to access the confirmation page.
skip_before_filter :require_no_authentication
skip_before_filter :authenticate_user!
# GET /resource/confirmation/new
def new
super
end
# POST /resource/confirmation
# def create
# super
# end
# GET /resource/confirmation?confirmation_token=abcdef
# PUT /resource/confirmation
def update
with_unconfirmed_confirmable do
if #confirmable.has_no_password?
#confirmable.attempt_set_password(params[:user])
if #confirmable.valid? and #confirmable.password_match?
do_confirm
else
do_show
#confirmable.errors.clear #so that we wont render :new
end
else
#confirmable.errors.add(:email, :password_already_set)
end
end
if !#confirmable.errors.empty?
self.resource = #confirmable
render 'devise/confirmations/new' #Change this if you don't have the views on default path
end
end
# GET /resource/confirmation?confirmation_token=abcdef
def show
with_unconfirmed_confirmable do
if #confirmable.has_no_password?
do_show
else
do_confirm
end
end
unless #confirmable.errors.empty?
self.resource = #confirmable
render 'devise/confirmations/new' #Change this if you don't have the views on default path
end
end
protected
# The path used after resending confirmation instructions.
def after_resending_confirmation_instructions_path_for(resource_name)
super(resource_name)
end
# The path used after confirmation.
def after_confirmation_path_for(resource_name, resource)
super(resource_name, resource)
end
def with_unconfirmed_confirmable
#confirmable = User.find_or_initialize_with_error_by(:confirmation_token, params[:confirmation_token])
if !#confirmable.new_record?
#confirmable.only_if_unconfirmed {yield}
end
end
def do_show
#confirmation_token = params[:confirmation_token]
#requires_password = true
self.resource = #confirmable
render 'devise/confirmations/show' #Change this if you don't have the views on default path
end
def do_confirm
#confirmable.confirm!
set_flash_message :notice, :confirmed
sign_in_and_redirect(resource_name, #confirmable)
end
end
end
And here's the routes relevant to devise:
devise_for :users, controllers: {
sessions: 'users/sessions',
confirmations: "confirmations"
}
as :user do
patch '/user/confirmation' => 'confirmations#update', :via => :patch, :as => :update_user_confirmation
end
Please feel free to ask for any other code that you think might be helpful. Thanks in advance for any ideas.
Shouldn't you be going to 'users/confirmations#update'? not 'confirmations#update' based on your class name of Users::ConfirmationsController
I normally wrap the routes in namespaces, but for simplicity, you should probably update your patch.

Resources