How to exclude guest users from getting emailed Rails 4 Devise - ruby-on-rails

Just set up my first mailer on Rails 4. I have a welcome email sent to new users as soon as they sign up (create) for a new account using devise.
I also have devise set up so that if a current_user is not found, a guest user will be created. Unfortunately, this is interfering with the mailer. Every time a guest account is created, the mailer will send an email to a non-existing email.
I am having trouble figuring out how to exclude the guests from the mailer.
user.rb:
after_create :send_welcome_mail
def send_welcome_mail
UserMailer.welcome_email(self).deliver
end
mailers/user_mailer.rb:
class UserMailer < ActionMailer::Base
default from: "example#gmail.com"
def welcome_email(user)
#user = user
mail(:to => user.email, :subject => "Welcome!")
end
end
application_controller.rb (guest creation):
def current_or_guest_user
if current_user
if session[:guest_user_id] && session[:guest_user_id] != current_user.id
logging_in
guest_user(with_retry = false).try(:destroy)
session[:guest_user_id] = nil
end
current_user
else
guest_user
end
end
# find guest_user object associated with the current session,
# creating one as needed
def guest_user(with_retry = true)
# Cache the value the first time it's gotten.
#cached_guest_user ||= User.find(session[:guest_user_id] ||= create_guest_user.id)
rescue ActiveRecord::RecordNotFound # if session[:guest_user_id] invalid
session[:guest_user_id] = nil
guest_user if with_retry
end
private
# called (once) when the user logs in, insert any code your application needs
# to hand off from guest_user to current_user.
def logging_in
# For example:
# guest_comments = guest_user.comments.all
# guest_comments.each do |comment|
# comment.user_id = current_user.id
# comment.save!
# end
end
def create_guest_user
u = User.create(:name => "guest", :email => "guest_#{Time.now.to_i}#{rand(100)}#example.com")
u.save!(:validate => false)
session[:guest_user_id] = u.id
u
end
I'm sure this is easy, but I am still new to rails and am a bit confused on the best way to go about this. Let me know if you need any other code.

def welcome_email(user)
# The following line is unnecessary. Normally, you do
# something like this when you want to make a variable
# available to a view
##user = user
# You can make the if more explicit by writing
# if user.id == nil, but if will return false in
# Ruby if a value doesn't exist (i.e. is nil)
if user.id
mail(:to => user.email, :subject => "Welcome!")
end
end

Related

Rails 4, Devise - sign in redirect paths

I am trying to make an app in rails 4.
I have tried to make an after sign in path in my application controller to redirect users.
When I sign into devise with a new session (via my omniauth callbacks controller), I expect to go to my profile page. Profile#show is the page I'm wanting to go to in those circumstances. Each user has one profile (and profile belongs to user).
If I authenticate from a page that requires authentication, the request referer works to redirect to that page.
In circumstances when my email is verified, I am still getting the finish sign up path (although all of the form fields in that form other than the submit button) are not rendered). If the email is verified, then the finish sign up path should not be the redirect.
Instead, I am sent to the root path.
Can anyone see what I've done wrong?
def after_sign_in_path_for(resource)
if !resource.email_verified?
finish_signup_path(resource)
elsif params[:redirect_to].present?
store_location_for(resource, params[:redirect_to])
elsif request.referer == new_session_path(:user)
profile_path(resource.profile) # or whatever the route is for the destination you want
else
stored_location_for(resource) || request.referer || root_path
end
end
def set_redirect_path
#redirect_path = request.path
end
Omniauth callbacks controller has:
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
#sourcey tutorial ------------------
def self.provides_callback_for(provider)
class_eval %Q{
def #{provider}
#user = User.find_for_oauth(env["omniauth.auth"], current_user)
if #user.persisted?
sign_in_and_redirect #user, event: :authentication
else
session["devise.#{provider}_data"] = env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
}
end
[:twitter, :facebook, :linkedin, :google_oauth2].each do |provider|
provides_callback_for provider
end
end
User.rb callback method is:
def self.find_for_oauth(auth, signed_in_resource = nil)
# Get the identity and user if they exist
identity = Identity.find_for_oauth(auth)
# If a signed_in_resource is provided it always overrides the existing user
# to prevent the identity being locked with accidentally created accounts.
# Note that this may leave zombie accounts (with no associated identity) which
# can be cleaned up at a later date.
user = signed_in_resource ? signed_in_resource : identity.user
# p '11111'
# Create the user if needed
if user.nil?
# p 22222
# Get the existing user by email if the provider gives us a verified email.
# If no verified email was provided we assign a temporary email and ask the
# user to verify it on the next step via UsersController.finish_signup
email_is_verified = auth.info.email && (auth.info.verified || auth.info.verified_email)
email = auth.info.email if email_is_verified # take out this if stmt for chin yi's solution
user = User.where(:email => email).first if email
# Create the user if it's a new registration
if user.nil?
# p 33333
user = User.new(
# at least one problem with this is that each provider uses different terms to desribe first name/last name/email. See notes on linkedin above
first_name: auth.info.first_name,
last_name: auth.info.last_name,
email: email ? email : "#{TEMP_EMAIL_PREFIX}-#{auth.uid}-#{auth.provider}.com",
#username: auth.info.nickname || auth.uid,
password: Devise.friendly_token[0,20])
# fallback for name fields - add nickname to user table
# debugger
# if email_is_verified
user.skip_confirmation!
# end
# user.skip_confirmation!
user.save!
end
end
# Associate the identity with the user if needed
if identity.user != user
identity.user = user
identity.save!
end
user
end
def email_verified?
self.email && TEMP_EMAIL_REGEX !~ self.email
end
This worked for me. I put the profile path redirect into the final string of options before root path, instead of having it as a separate else statement (which I now think conflicts with the sign in redirect path where there is a request referer.
def after_sign_in_path_for(resource)
if !resource.email_verified? or !resource.first_name.present? or !resource.last_name.present?
finish_signup_path(resource)
elsif params[:redirect_to].present?
store_location_for(resource, params[:redirect_to])
else
stored_location_for(resource) || request.referer || profile_path(resource.profile) ||root_path
end
end
I found a simple yet effective solution on Github here.
def after_sign_in_path_for(resource)
stored_location_for(resource) || profile_path
end
It is also stated that in order for the above to work, your root path must be publicly visible. For example, adding this to you "pages_controller"...
before_action :authenticate_user!, except: [:landing]

Rails devise invitable undefined method this

I am using devise invitable in my app: a user fills a form with email, that creates a user in my database and sends an invitation to the email address. The other person can then confirm its account by setting up his password.
Everything works fine until the invited user tries to log in by setting up his password. When he submits the form, I get this error:
undefined local variable or method `this' for #
here's my invitation controller:
class Users::InvitationsController < Devise::InvitationsController
def new
#user = User.new
end
def create
#user = User.new
#user.save
end
def update
if this
redirect_to root_path
else
super
end
end
private
# this is called when creating invitation
# should return an instance of resource class
def invite_resource
## skip sending emails on invite
resource_class.invite!(invite_params, current_inviter) do |u|
u.skip_invitation = true
end
end
# this is called when accepting invitation
# should return an instance of resource class
def accept_resource
resource = resource_class.accept_invitation!(update_resource_params)
## Report accepting invitation to analytics
Analytics.report('invite.accept', resource.id)
resource
end
end
What is this "this" thing ?

NameError when bypassing mass assignment while updating a user?

I followed the Railscast tutorial for bypassing mass assignment to edit my role attribute of my User model as the "admin". This is how I defined my roles:
class User < ActiveRecord::Base
attr_accessible :email, :password, :remember_me
attr_accessor :accessible
devise :database_authenticatable, ....etc
before_create :setup_default_role_for_new_users
ROLES = %w[admin default banned]
private
def setup_default_role_for_new_users
if self.role.blank?
self.role = "default"
end
end
def mass_assignment_authorizer
super + (accessible || [])
end
end
And then I created a new UsersController only to have issues with my update method:
def update
#user = User.find(params[:id])
#user.accessible = [:role] if user.role == "admin"
if #user.update_attributes(params[:user])
redirect_to #user, :notice => "Successfully updated user."
else
render :action => 'edit'
end
end
I can't do this though becuase this line: if user.role == "admin", is causing issue, giving me the error:
NameError (undefined local variable or method `user' for UsersController
What am I missing here?
Thanks in advance.
With the user part in user.role == "admin" you're trying to use a local variable, which hasn't been defined in your update method. If user isn't declared as a helper method that's accessible in your controllers then ruby won't find it.
From your code I'm assuming that only an admin user can update the role of another user? Thus you're not using #user.role == "admin" but user.role == "admin"?
If so you have to provide a user object whether it's through a helper method (i.e. in your ApplicationHelper class) or fetch it before you try to use it in your update method, or with a before_* callback in your controller.
I hope it's clear what I meant.

Gradual engagement, persistent guest user with Devise

I'm trying to set up gradual engagement in my utility app which people can use without registering e.g. notepad.cc and jsfiddle.net and I plan to create a guest user (with Devise) for the user when he 'writes' to the app.
I found this guide on the Devise wiki https://github.com/plataformatec/devise/wiki/How-To:-Create-a-guest-user which shows how to create a guest user for the duration of the browser session. What I want is for the user to continue using the same guest account in subsequent visits, until he signs up, maybe when I introduce subscription plans for more features.
How can I modify what's in the guide to make this possible?
Code in the guide linked above:
# file: app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
# if user is logged in, return current_user, else return guest_user
def current_or_guest_user
if current_user
if session[:guest_user_id]
logging_in
guest_user.destroy
session[:guest_user_id] = nil
end
current_user
else
guest_user
end
end
# find guest_user object associated with the current session,
# creating one as needed
def guest_user
User.find(session[:guest_user_id].nil? ? session[:guest_user_id] = create_guest_user.id : session[:guest_user_id])
end
# called (once) when the user logs in, insert any code your application needs
# to hand off from guest_user to current_user.
def logging_in
end
private
def create_guest_user
u = User.create(:name => "guest", :email => "guest_#{Time.now.to_i}#{rand(99)}#email_address.com")
u.save(false)
u
end
end
And using it in the controller:
#thing.user = current_or_guest_user
#thing.save
After some yak-shaving I've managed to get it to work. Here's the working code:
class ApplicationController < ActionController::Base
protect_from_forgery
# if user is logged in, return current_user, else return guest_user
def current_or_guest_user
if current_user
if cookies[:uuid]
logging_in # Look at this method to see how handing over works
guest_user.destroy # Stuff have been handed over. Guest isn't needed anymore.
cookies.delete :uuid # The cookie is also irrelevant now
end
current_user
else
guest_user
end
end
# find guest_user object associated with the current session,
# creating one as needed
def guest_user
User.find_by_lazy_id(cookies[:uuid].nil? ? create_guest_user.lazy_id : cookies[:uuid])
end
# called (once) when the user logs in, insert any code your application needs
# to hand off from guest_user to current_user.
def logging_in
# What should be done here is take all that belongs to user with lazy_id matching current_user's uuid cookie... then associate them with current_user
end
private
def create_guest_user
uuid = rand(36**64).to_s(36)
temp_email = "guest_#{uuid}#email_address.com"
u = User.create(:email => temp_email, :lazy_id => uuid)
u.save(:validate => false)
cookies[:uuid] = { :value => uuid, :path => '/', :expires => 5.years.from_now }
u
end
end
I will accept another answer if you can show me a better way to do this.
The above solution works great.
Don't forget to setuphelper_method :current_or_guest_user to make the method accessible in views. Took me some time to figure out.

Access current_user in model

I have 3 tables
items (columns are: name , type)
history(columns are: date, username, item_id)
user(username, password)
When a user say "ABC" logs in and creates a new item, a history record gets created with the following after_create filter.
How to assign this username ‘ABC’ to the username field in history table through this filter.
class Item < ActiveRecord::Base
has_many :histories
after_create :update_history
def update_history
histories.create(:date=>Time.now, username=> ?)
end
end
My login method in session_controller
def login
if request.post?
user=User.authenticate(params[:username])
if user
session[:user_id] =user.id
redirect_to( :action=>'home')
flash[:message] = "Successfully logged in "
else
flash[:notice] = "Incorrect user/password combination"
redirect_to(:action=>"login")
end
end
end
I am not using any authentication plugin. I would appreciate if someone could tell me how to achieve this without using plugin(like userstamp etc.) if possible.
Rails 5
Declare a module
module Current
thread_mattr_accessor :user
end
Assign the current user
class ApplicationController < ActionController::Base
around_action :set_current_user
def set_current_user
Current.user = current_user
yield
ensure
# to address the thread variable leak issues in Puma/Thin webserver
Current.user = nil
end
end
Now you can refer to the current user as Current.user
Documentation about thread_mattr_accessor
Rails 3,4
It is not a common practice to access the current_user within a model. That being said, here is a solution:
class User < ActiveRecord::Base
def self.current
Thread.current[:current_user]
end
def self.current=(usr)
Thread.current[:current_user] = usr
end
end
Set the current_user attribute in a around_filter of ApplicationController.
class ApplicationController < ActionController::Base
around_filter :set_current_user
def set_current_user
User.current = User.find_by_id(session[:user_id])
yield
ensure
# to address the thread variable leak issues in Puma/Thin webserver
User.current = nil
end
end
Set the current_user after successful authentication:
def login
if User.current=User.authenticate(params[:username], params[:password])
session[:user_id] = User.current.id
flash[:message] = "Successfully logged in "
redirect_to( :action=>'home')
else
flash[:notice] = "Incorrect user/password combination"
redirect_to(:action=>"login")
end
end
Finally, refer to the current_user in update_history of Item.
class Item < ActiveRecord::Base
has_many :histories
after_create :update_history
def update_history
histories.create(:date=>Time.now, :username=> User.current.username)
end
end
The Controller should tell the model instance
Working with the database is the model's job. Handling web requests, including knowing the user for the current request, is the controller's job.
Therefore, if a model instance needs to know the current user, a controller should tell it.
def create
#item = Item.new
#item.current_user = current_user # or whatever your controller method is
...
end
This assumes that Item has an attr_accessor for current_user.
The Rails 5.2 approach for having global access to the user and other attributes is CurrentAttributes.
If the user creates an item, shouldn't the item have a belongs_to :user clause? This would allow you in your after_update to do
History.create :username => self.user.username
You could write an around_filter in ApplicationController
around_filter :apply_scope
def apply_scope
Document.where(:user_id => current_user.id).scoping do
yield
end
This can be done easily in few steps by implementing Thread.
Step 1:
class User < ApplicationRecord
def self.current
Thread.current[:user]
end
def self.current=(user)
Thread.current[:user] = user
end
end
Step 2:
class ApplicationController < ActionController::Base
before_filter :set_current_user
def set_current_user
User.current = current_user
end
end
Now you can easily get current user as User.current
The Thread trick isn't threadsafe, ironically.
My solution was to walk the stack backwards looking for a frame that responds to current_user. If none is found it returns nil. Example:
def find_current_user
(1..Kernel.caller.length).each do |n|
RubyVM::DebugInspector.open do |i|
current_user = eval "current_user rescue nil", i.frame_binding(n)
return current_user unless current_user.nil?
end
end
return nil
end
It could be made more robust by confirming the expected return type, and possibly by confirming owner of the frame is a type of controller...

Resources