devise confirmable on rails 5 not working - ruby-on-rails

I am on a rails 5 app. Using devise for user management. I have 'confirmable' enabled in my user model and the migrations are also up for the same. I have created a custom mailer class which is derived from Devise::Mailer, have setup the mailer in devise config to use the custom mailer class however my confirmations email are not being sent properly.
Have tried many so answer threads but couldn't crack it yet. Where could I be going wrong?
User Model
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable, :validatable
after_create :send_confirmation_email
def send_confirmation_email
p 'inside after_create callback func.'
MyDeviseMailer.confirmation_instructions(self).deliver
end
end
Migration for confirmable
def up
add_column :users, :confirmation_token, :string
add_column :users, :unconfirmed_email, :string
add_column :users, :confirmed_at, :datetime
add_column :users, :confirmation_sent_at, :datetime
add_index :users, :confirmation_token, unique: true
end
My custom mailer class derived from Devise::Mailer
class MyDeviseMailer < Devise::Mailer
helper :application
include Devise::Controllers::UrlHelpers
default template_path: 'devise/mailer'
def confirmation_instructions(record, token, opts={})
devise_mail(record, :confirmation_instructions, opts)
end
end

After a couple of hours, figured out that i was following the wrong way of doing things.Followed the well written article over here.Have updated my solution below:
Refactored confirmation_instructions method present in my custom mailer class as shown below:
def confirmation_instructions(record, token, opts={})
if record.email.present?
opts[:subject] = "Welcome #{record.email.split('#').first.capitalize}, Confirm your asana account"
else
opts[:subject] = "Confirm Your Asana Account"
end
super
end
Changed the function invocation to this in my user model:
MyDeviseMailer.confirmation_instructions(self, self.confirmation_token).deliver
This is what worked for me finally.

Related

Devise: Authenticate with Just Username Only Instead of Email

everyone!
So I'm building an application that only needs a username for authentication and not an email.
In my config/initializers/devise.rb, I changed the :email to :username resulting to this
config.authentication_keys = [:username]
config.case_insensitive_keys = [:username]
config.strip_whitespace_keys = [:username]
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :stores
has_many :customers, through: :stores
has_many :visits, through: :stores
has_many :mall_managers
validates :name, presence: true
validates :username, presence: true, uniqueness: true
end
I also edited the views accordingly.
When I try to sign up I get an error:
NoMethodError in Devise::RegistrationsController#create
undefined method `email' for #<User:0x000000011bd61b38>
i guess your user table does not contain email column, but the module validatable of gem devise will validates_presence_of the email, so that error will be raised.
however module validatable allow you ignore email validation by override email_required? method
class User < ApplicationRecord
# ...
def email_required?
false
end
def will_save_change_to_email?
false
end
def email_changed?
false
end
end
update i found more 2 methods need to override (as above), and who know what else (in future versions) ? so i recommend you add email database field (if the root cause i suspect (miss email) correct).

Using a Model method to populate a column during a migration

I'd like to add a new column to my users table, and populate it with a random token. I've got that working, but I'm curious as to why the first method I tried didn't work.
Here's the working version of my migration:
class AddTokenToUser < ActiveRecord::Migration
def up
add_column :users, :secure_token, :string
User.reset_column_information
User.all.each do |user|
user.secure_token = SecureRandom.urlsafe_base64(16)
user.save!
end
end
def down
remove_column :users, :secure_token
end
end
However, because I'm also going to want the code to generate this token on the User model, as I'd like to create a new token along with every new user, I thought I might be able to add the code as a method on the User object:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :snaps
def generate_new_secure_token
#secure_token = SecureRandom.urlsafe_base64(16)
end
end
...and then call it from the migration, to avoid repeating myself:
class AddTokenToUser < ActiveRecord::Migration
def up
add_column :users, :secure_token, :string
User.reset_column_information
User.all.each do |user|
# user.secure_token = SecureRandom.urlsafe_base64(16)
user.generate_new_secure_token
user.save!
end
end
def down
remove_column :users, :secure_token
end
end
However, with this method, I get no errors, but my secure_token column values all end up as NULL in the database, rather than having the a token in them.
I'm new to both Rails and Ruby, so I figure I'm missing something obvious, but I can't see what it is. Why isn't my method working, and is there a good way to move the token generation routine to the User class, so I don't need to have it in two different places?
Change your method to this
def generate_new_secure_token
self.secure_token = SecureRandom.urlsafe_base64(16)
end
#secure_token is an instance variable. Setting that doesn't change the attribute secure_token on the user object

Rails undefined method `find_or_create_from_auth_hash'

My sessions_controller is as follows:
class SessionsController < ApplicationController
require 'omniauth-facebook'
require 'omniauth'
def create
#user = User.find_or_create_from_auth_hash(auth_hash)
self.current_user = #user
redirect_to '/'
end
protected
def auth_hash
request.env['omniauth.auth']
end
end
So... it's THERE isn't it?
Here's my users.rb file:
class User < ActiveRecord::Base
acts_as_voter
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me
# attr_accessible :title, :body
has_many :posts
has_many :tasks
end
And my routes file:
LiquidAdmin::Application.routes.draw do
devise_for :users
get '/auth/:provider/callback', to: 'sessions#create'
resource :sessions, :only => :create
get "home/bandwall"
get "home/index"
root :to => "home#index"
So where's the problem? "auth_hash" is clearly defined... The SessionsController is loading... so why is it complaining about no method for find_or_create_from_auth_hash?
In Rails 3.2, the method you'll want to use is called first_or_create:
User.where(auth_hash: auth_hash).first_or_create
According to the Rails 3.2 release notes:
Add first_or_create, first_or_create!, first_or_initialize methods to
Active Record. This is a better approach over the old
find_or_create_by dynamic methods because it's clearer which arguments
are used to find the record and which are used to create it.
Given this, the following is made possible through the query:
User.where(auth_hash: auth_hash).first_or_create(foo: 'bar') # Assuming no other User entries exist
#=> #<User id: 1, auth_hash: "your_auth_hash", foo: "bar">
User.where(auth_hash: auth_hash).first_or_create(foo: 'baz')
#=> #<User id: 1, auth_hash: "your_auth_hash", foo: "bar">
Given that this question is based on OmniAuth, I think a better answer would be:
#user = User.where(auth_hash).first_or_create
or if in Rails version greater than 4.1.14
#user = User.where(uid: auth_hash[:uid], provider: auth_hash[:provider]).first_or_create
auth_hash is not a field in the User model, but rather a Hash of fields that could be on the User model.

Omniauth callback processing after authorization

In my Rails app, I have user and authorization tables to handle users and auth data. I set up both Devise and Omniauth to use Twitter to sign up, it redirects to Twitter, but after returning to my app, it gives an error like:
NoMethodError at /users/auth/twitter/callback
undefined method `authorizations' for #<Class:0xbdc8100>
In which side, did I go wrong and how can I fix this issue?
Here are related parts: omniauth_callbacks_controller.rb:
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def all
user = User.authorizations.from_auth(auth_hash)
if user.persisted?
flash.notice = "Signed in!"
sign_in_and_redirect user
else
session["devise.user_attributes"] = user.attributes
redirect_to new_user_registration_url
end
end
alias_method :twitter, :all
protected
def auth_hash
request.env['omniauth.auth']
end
end
authorization.rb:
class Authorization < ActiveRecord::Base
attr_accessible :uid, :provider
belongs_to :user
def self.from_auth(auth)
where(auth.slice(:provider, :uid)).first_or_create do |user|
user.provider = auth.provider
user.uid = auth.uid
end
end
user.rb:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable, :omniauth_providers => [:twitter, :facebook]
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me, :name
# attr_accessible :title, :body
has_many :authorizations, dependent: :destroy
end
Your issue is in this line...
user = User.authorizations.from_auth(auth_hash)
You call authorizations on the class User, but as an attribute it needs to be called on an instance of the User class, i.e. a specific user.

Anonymous user in devise - rails

I'm new to rails and I tried to make simple authentication with anonymous user. I followed this tutorial and I have this error:
undefined method `find_or_initialize_by_token'
This is my AnonymousUser model:
class AnonymousUser < User
ACCESSIBLE_ATTRS = [:name, :email]
attr_accessible *ACCESSIBLE_ATTRS, :type, :token, as: :registrant
def register(params)
params = params.merge(type: 'User', token: nil)
self.update_attributes(params, as: :registrant)
end
end
This is my User model:
class User < ActiveRecord::Base
devise :database_authenticatable, :confirmable, :lockable, :recoverable,
:rememberable, :registerable, :trackable, :timeoutable, :validatable,
:token_authenticatable
attr_accessible :email, :password, :password_confirmation, :remember_me
end
And the last one important is my ApplicationController which has this error:
class ApplicationController < ActionController::Base
protect_from_forgery
def authenticate_user!(*args)
current_user.present? || super(*args)
end
def current_user
super || AnonymousUser.find_or_initialize_by_token(anonymous_user_token).tap do |user|
user.save(validate: false) if user.new_record?
end
end
private
def anonymous_user_token
session[:user_token] ||= SecureRandom.hex(8)
end
end
Someone told me that if AnonymousUser user inherits from User then AnonymousUser have method called find_or_initialize_by_token, but i don't know how to fix it.
Provided you have latest rails installed, try to refactor:
# in ApplicationController#current_user
AnonymousUser.find_or_initialize_by_token(anonymous_user_token).tap do |user|
user.save(validate: false) if user.new_record?
end
to something like this:
AnonymousUser.safely_find(anonymous_user_token)
and push the find_or_initialize_by_token and save(validate: false) into the model.
I wrote the blog post you referenced, but today, I would use
AnonymousUser.where(anonymous_user_token: anonymous_user_token).first_or_initialize
Dynamic finders have been deprecated AFAIK.
However, #Saurabh Jain is absolutely correct in his suggestion to refactor that block into a nice little push-button class method on the AnonymousUser.

Resources