I am starting with using omniauth with devise User model. Integration works well but a case is failing.
This is related code from omniauth callback controller:
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
before_action :find_user, only: [:facebook]
def facebook
process_omniauth_response('Facebook', request.env['omniauth.auth'], #user)
end
def failure
redirect_to root_path, flash: { error: 'Authentication \
failed, please try again!' }
end
private
def find_user
#user = User.from_omniauth(request.env['omniauth.auth'])
end
def process_omniauth_response(provider, env_data, user)
if user.persisted?
sign_in_and_redirect user, event: :authentication
set_flash_message(:notice, :success,
kind: provider) if is_navigational_format?
else
session["devise.#{provider.downcase}_data"] = env_data
redirect_to new_user_registration_url
end
end
end
And the User model methods:
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
info = auth.info
user.email = info.email
user.remote_profile_picture_url = info.image
user.name = info.name
user.password = Devise.friendly_token[0, 20]
end
end
def self.new_with_session(params, session)
super.tap do |user|
auth_data =
session['devise.facebook_data'] || session['devise.twitter_data']
fill_with_omniauth_data(user, auth_data) if auth_data
end
end
def self.fill_with_omniauth_data(user, auth_data)
data_info = auth_data['info']
user.email = data_info['email'] if user.email.blank?
user.name = data_info['name'] if user.name.blank?
...
end
So everything works fine i.e if a new user clicks on login by Facebook, after authentication by facebook he will come to signup page with pre-filled data but in case of this below case it is going to sign in page after omniauth callback action:
context 'when facebook email doesn\'t exist in the system' do
before(:each) do
stub_env_for_omniauth
get :facebook
end
it { response.should redirect_to new_user_registration_path }
it 'should create authentication with facebook id' do
authentication = User.where(provider: 'facebook', uid: '1234').first
puts authentication.inspect
authentication.should_not be_nil
end
end
And the stub method:
def stub_env_for_omniauth
request.env['devise.mapping'] = Devise.mappings[:user]
request.env['omniauth.auth'] = OmniAuth::AuthHash.new(
provider: 'facebook', uid: '1234', info: {
email: 'ghost#nobody.com', name: 'mock user'
},
extra: { raw_info: { age: 23 } }
)
end
I got this message:
Expected response to be a redirect to http://test.host/users/sign_up but was a redirect to http://test.host/users/sign_in
From debugging I got that user object is a new , not persisted when I check it in the 'find_user' method of controller but in case of spec it is a persisted one.
I would appreciate any help.
Thanks
A nil user can create a challenge before signing up.
challenges/_form view
<%= text_field :action %>
<% if current_user %>
Send reminder to <%= text_field_tag :email, current_user.email %>
<% else %>
Send reminder to <%= email_field_tag :email, nil, placeholder: 'Enter Email' %>
<% end %>
How can I save to session :email & :action upon challenge create? I got :action to work.
challenges_controller
def create
if current_user == nil
session[:challenge_action] = challenge_params[:action]
session[:user_email] = params[:email] # Email is an attribute of user. A user has_many challenges
redirect_to signup_path
end
end
def update_user_email
email = params[:email]
current_user.update_attribute(:email, email)
end
heroku logs
Processing by ChallengesController#create as HTML
2016-03-21T21:19:48.873239+00:00 app[web.1]: Parameters: {"utf8"=>"✓", "authenticity_token"=>"B5S4YGBOcnb+GYewM5lDUB/nBOedaIrpVpOOlQuoT4E9NFDBsj/x3ltBG2OXNEt+Z8jgRVuhzHz8eDqNjv0GkRg==", "challenge"=>{"action"=>"Run a 5K", "send_email"=>["sun", ""]}, "email"=>"test#gmail.com", "button"=>""}
Once the user is created I want the stored email to be saved as the user's email, but yet the :email says nil:
=> #<User id: 48, name: "Anthony Galli", email: nil,
=> #<Challenge id: 67, action: "Run a 5K"
users_controller
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
action = session.delete(:challenge_action)
email = session.delete(:user_email)
#user.challenges.create(action: action, email: email)
end
end
users/new view
# No email_field is included. The goal is that it is not necessary since email was included with challenge form.
<%= form_for(#user) do |f| %>
<%= f.text_field :name %>
<%= f.password_field :password %>
<%= f.submit %>
<% end %>
BY REQUEST
users_controller full
class UsersController < ApplicationController
before_action :logged_in_user, only: [:index, :edit, :update, :destroy, :following, :followers]
before_action :correct_user, only: [:edit, :update]
before_action :admin_user, only: [:destroy]
def index
#users, #alphaParams = User.alpha_paginate(params[:letter], {:pagination_class => "pagination-centered"}){|user| user.name}
end
def show
#user = User.find(params[:id])
#past_challenges = #user.challenges.publish.order("deadline ASC").select{ |challenge| challenge.deadline < Date.current if challenge.deadline.present? }
#past_challenges_by_years = #past_challenges.group_by { |t| t.deadline.year }
#present_oneshot_challenges = #user.challenges.publish.order("deadline ASC").select{ |challenge| challenge.deadline == Date.current if challenge.deadline.present? }
#present_habit_challenges_today = #user.challenges.publish.unaccomplished.committed_for_today.order(:order).select{ |challenge| challenge.date_started <= Date.tomorrow if challenge.date_started.present? }
#present_habit_challenges_not_today = #user.challenges.publish.unaccomplished.noncommitted_for_today.select{ |challenge| challenge.date_started <= Date.tomorrow if challenge.date_started.present? }
#future_challenges = #user.challenges.unaccomplished.publish.order("deadline ASC").select{ |challenge| challenge.deadline > Date.current if challenge.deadline.present? }
#future_challenges_by_years = #future_challenges.group_by { |t| t.deadline.beginning_of_year }
#inspirations = #user.inspirations.publish
end
def new
#user = User.new
#user.email = session.delete(:user_email)
end
def create
#user = User.new(user_params)
if #user.save
action = session.delete(:challenge_action)
deadline = session.delete(:challenge_deadline)
committed = session.delete(:challenge_committed)
date_started = session.delete(:challenge_date_started)
order = session.delete(:challenge_order)
days_challenged = session.delete(:challenge_days_challenged)
why = session.delete(:challenge_why)
conceal = session.delete(:challenge_conceal)
if deadline.present?
#user.challenges.create(action: action, deadline: deadline, why: why, conceal: conceal, date_started: date_started, committed: committed, days_challenged: days_challenged, email: email)
end
#user.send_welcome_email
log_in #user
redirect_to tutorial_url
flash[:info] = 'Welcome to Live to Challenge!'
else
render 'new'
end
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
redirect_to root_url
flash[:success] = "Settings updated"
else
render 'edit'
end
end
def destroy
User.find(params[:id]).destroy
flash[:success] = "User deleted"
redirect_to root_url
end
def following
#title = "Following"
#user = User.find(params[:id])
#users = #user.following.paginate(page: params[:page])
render 'show_follow'
end
def followers
#title = "Followers"
#user = User.find(params[:id])
#users = #user.followers.paginate(page: params[:page])
render 'show_follow'
end
private
def user_params
if params[:conceal] = true
params.require(:user).permit(:time_zone, :name, :email, :tag_list, :password, :conceal, inspirations_attributes: [:name, :tag_list, :conceal], activities_attributes: [:conceal, :action, :trackable_id, :trackable_type])
else
params[:user][:conceal] = false
params.require(:user).permit(:time_zone, :name, :image, :tag_list, :email, :password, inspirations_attributes: [:name, :tag_list], activities_attributes: [:action, :trackable_id, :trackable_type])
end
end
# Confirms a logged-in user.
def logged_in_user
unless logged_in?
store_location
flash[:danger] = "Please sign in first"
redirect_to root_url
end
end
# Confirms the correct user.
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user)
end
# Confirms an admin user.
def admin_user
redirect_to(root_url) unless current_user.admin?
end
end
challenges_controller full
class ChallengesController < ApplicationController
before_action :set_challenge, only: [:show, :edit, :update, :destroy, :challenging, :mark_accomplished, :mark_completed, :create_freebie, :like]
before_action :update_user_email, if: proc {|c| c.current_user.present? && c.params[:email].present? }
respond_to :html, :json
def new_freebie
#challenge = current_user.challenges.find(params[:id])
#challenge.freebie_date = Date.yesterday
respond_modal_with #challenge
end
def create_freebie
#challenge.update(challenge_params)
#challenge.freebie = #challenge.freebie + 1
#challenge.save
respond_modal_with #challenge, location: root_path
flash[:alert] = 'Freebie added'
end
def sort
params[:order].each do |id, order|
Challenge.where(id: id).update_all(order: order)
end
render nothing: true
end
def mark_accomplished
#challenge.update(accomplished: true, :dont_set_tag_owner => true)
respond_to do |format|
format.html
format.js { render :nothing => true }
end
end
def completed
#challenge = current_user.challenges.find(params[:challenge_id])
#challenge.update(accomplished: true)
#challenge.deadline = Date.current
respond_modal_with #challenge, location: root_path
end
def index
#past_challenges = current_user.challenges.order("deadline ASC").select{ |challenge| challenge.deadline < Date.current if challenge.deadline.present? }
#past_challenges_by_years = #past_challenges.group_by { |t| t.deadline.beginning_of_year }
#present_oneshot_challenges = current_user.challenges.unaccomplished.order("deadline ASC").select{ |challenge| challenge.deadline == Date.current if challenge.deadline.present? }
#present_habit_challenges_today = current_user.challenges.unaccomplished.committed_for_today.order(:order).select{ |challenge| challenge.date_started <= Date.tomorrow if challenge.date_started.present? }
#present_habit_challenges_not_today = current_user.challenges.unaccomplished.noncommitted_for_today.select{ |challenge| challenge.date_started <= Date.tomorrow if challenge.date_started.present? }
#future_challenges = current_user.challenges.unaccomplished.order("deadline ASC").select{ |challenge| challenge.deadline > Date.current if challenge.deadline.present? }
#future_challenges_by_years = #future_challenges.group_by { |t| t.deadline.beginning_of_year }
end
def show
#challenge_to_deadline = current_user.challenges.group_by {|i| i.deadline} if current_user
#notable = #challenge
#notes = #notable.notes
#note = Note.new
#commentable = #challenge
#comments = #commentable.comments
#comment = Comment.new
#correct_user = current_user.challenges.find_by(id: params[:id])
end
def challenging
#challenge = Challenge.new
end
def new
#challenge = Challenge.new
respond_modal_with #challenge, location: root_path
end
def edit
end
def create
#challenge = Challenge.new(challenge_params)
if params[:step] == '2'
if current_user == nil
# If there is no user, store the lifetime values to the session.
session[:challenge_action] = challenge_params[:action]
session[:challenge_committed] = challenge_params[:committed]
session[:challenge_deadline] = [params["challenge"]["deadline(3i)"], params["challenge"]["deadline(2i)"], params["challenge"]["deadline(1i)"]].join('/')
session[:challenge_date_started] = [params["challenge"]["date_started(3i)"], params["challenge"]["date_started(2i)"], params["challenge"]["date_started(1i)"]].join('/')
session[:challenge_order] = challenge_params[:order]
session[:challenge_days_challenged] = challenge_params[:days_challenged]
session[:challenge_why] = challenge_params[:why]
session[:challenge_conceal] = challenge_params[:conceal]
session[:user_email] = params[:email]
redirect_to signup_path
else
#challenge = current_user.challenges.build(challenge_params)
if #challenge.conceal == true
#challenge.save
redirect_to root_path
if #challenge.date_started.present?
flash[:info] = 'Habit Challenge secretly saved! Click "Strike 1" upon incompleting a day.'
else
flash[:info] = 'Goal Challenge secretly saved! Click checkmark upon completing it.'
end
elsif
#challenge.save
track_activity #challenge
redirect_to root_path
if #challenge.date_started.present?
flash[:info] = 'Habit Challenge saved! Click "Strike 1" upon incompleting a day.'
else
flash[:info] = 'Goal Challenge saved! Click checkmark upon completing it.'
end
else
respond_modal_with #challenge
end
end
end
end
def update
#challenge.update(challenge_params)
flash[:info] = 'Challenge updated'
respond_modal_with #challenge, location: root_path
end
def destroy
#challenge.destroy
respond_modal_with #challenge, location: root_path
end
def like
#challenge_like = current_user.challenge_likes.build(challenge: #challenge)
if #challenge_like.save
#challenge.increment!(:likes)
flash[:success] = 'Thanks for liking!'
else
flash[:error] = 'Two many likes'
end
redirect_to(:back)
end
private
def update_user_email
email = params[:email]
current_user.update_attribute(:email, email)
end
def set_challenge
#challenge = Challenge.find(params[:id])
end
def challenge_params
params.require(:challenge).permit(
:default_email,
:action,
:why,
:like,
:deadline,
:accomplished,
:tag_list,
:conceal,
:archive,
:trigger,
:missed_days,
:target,
:reward,
:order,
:date_started,
:date_ended,
:days_challenged,
:completed_at,
:freebie,
:freebie_date,
:send_email => [],
:committed => [])
end
end
user.rb
class User < ActiveRecord::Base
has_many :challenges
has_many :authentications
accepts_nested_attributes_for :challenges, :reject_if => :all_blank, :allow_destroy => true
attr_accessor :remember_token, :activation_token, :reset_token
before_save :downcase_email
before_create :create_activation_digest
validates :name, presence: true, length: { maximum: 50 }, format: { with: /\A[a-z\sA-Z]+\z/,
message: "only allows letters" }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }, unless: -> { from_omniauth? }
has_secure_password
def self.from_omniauth(auth)
# Sets 60 day auth token
oauth = Koala::Facebook::OAuth.new("154dsaf203729762293929", "ee917abf2esdf8f1c98274cdfasdfdsaebb1346f4")
new_access_info = oauth.exchange_access_token_info auth.credentials.token
new_access_token = new_access_info["access_token"]
new_access_expires_at = DateTime.now + new_access_info["expires"].to_i.seconds
where(provider: auth.provider, uid: auth.uid).first_or_initialize.tap do |user|
user.provider = auth.provider
user.image = auth.info.image
user.uid = auth.uid
user.name = auth.info.name
user.oauth_token = new_access_token # auth.credentials.token <- your old token. Not needed anymore.
user.oauth_expires_at = Time.at(auth.credentials.expires_at)
user.password = (0...8).map { (65 + rand(26)).chr }.join
#I commented out below line so it doesn't override email saved with challenge form
#user.email = SecureRandom.hex + "#mailinator.com" unless user.email.present?
user.activated = true
user.save!
end
end
def self.koala(auth)
access_token = auth['token']
facebook = Koala::Facebook::API.new(access_token)
facebook.get_object("me?fields=name,picture")
end
# Returns the hash digest of the given string.
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
# Returns a random token.
def User.new_token
SecureRandom.urlsafe_base64
end
# Remembers a user in the database for use in persistent sessions.
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
# Returns true if the given token matches the digest.
def authenticated?(attribute, token)
digest = send("#{attribute}_digest")
return false if digest.nil?
BCrypt::Password.new(digest).is_password?(token)
end
# Activates an account.
def activate
update_attribute(:activated, true)
update_attribute(:activated_at, Time.zone.now)
end
private
def from_omniauth?
provider && uid
end
# Converts email to all lower-case.
def downcase_email
self.email = email.downcase unless from_omniauth?
end
# Creates and assigns the activation token and digest.
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
self.activated = true
end
end
For Chewy: rails c
Started POST "/users" for 127.0.0.1 at 2016-03-22 14:58:07 -0400
Processing by UsersController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"ZuWflxk9Vljppe9cRUdzzKsfzcekWqNvpo1HnTfVVdiZR9kruqDQnZDXHyohcitGUpVJchGpartqG+hc9450RA==", "user"=>{"name"=>"afdsdfsasafddfsa", "password"=>"[FILTERED]", "time_zone"=>"Eastern Time (US & Canada)"}, "button"=>""}
(0.2ms) BEGIN
SQL (0.4ms) INSERT INTO "users" ("time_zone", "name", "password_digest", "created_at", "updated_at", "activation_digest", "activated") VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING "id" [["time_zone", "Eastern Time (US & Canada)"], ["name", "afdsdfsasafddfsa"], ["password_digest", "$2a$10$jzIDcHvKD1hwZMvfSQx4x.D1qtif.c53bOSPTsaYd9SsG9MvoADBS"], ["created_at", "2016-03-22 18:58:08.038359"], ["updated_at", "2016-03-22 18:58:08.038359"], ["activation_digest", "$2a$10$.WDl3Zqz2VxcbuuWAOgWE.awi7EZ1DalAeFA79B.AUpg0h0Xmb9t6"], ["activated", "t"]]
(1.9ms) COMMIT
cookie#gmail.com
cookie#gmail.com
(0.4ms) BEGIN
SQL (1.1ms) INSERT INTO "challenges" ("committed", "action", "deadline", "why", "user_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING "id" [["committed", nil], ["action", "Meditate"], ["deadline", "2016-03-22"], ["why", ""], ["user_id", 32], ["created_at", "2016-03-22 18:58:08.176075"], ["updated_at", "2016-03-22 18:58:08.176075"]]
(0.8ms) COMMIT
cookie#gmail.com
cookie#gmail.com
(0.2ms) BEGIN
SQL (19.9ms) UPDATE "users" SET "email" = $1, "updated_at" = $2 WHERE "users"."id" = $3 [["email", "cookie#gmail.com"], ["updated_at", "2016-03-22 18:58:08.194324"], ["id", 32]]
SQL (0.3ms) UPDATE "challenges" SET "committed" = $1, "updated_at" = $2 WHERE "challenges"."id" = $3 [["committed", nil], ["updated_at", "2016-03-22 18:58:08.237871"], ["id", 40]]
(1.8ms) COMMIT
Rendered user_mailer/welcome_email.html.erb within layouts/mailer (0.6ms)
Rendered user_mailer/welcome_email.text.erb within layouts/mailer (0.3ms)
UserMailer#welcome_email: processed outbound mail in 79.3ms
Sent mail to cookie#gmail.com (1561.2ms)
Chewy's users_controller
def create
#user = User.new(user_params)
if #user.save
action = session.delete(:challenge_action)
puts cookies[:challenges_email]
if cookies[:challenges_email].present?
#user.email = cookies[:challenges_email]
end
puts cookies[:challenges_email]
if deadline.present?
#user.challenges.create(action: action, deadline: deadline, why: why, conceal: conceal, date_started: date_started, committed: committed, days_challenged: days_challenged)
end
puts cookies[:challenges_email]
if cookies[:challenges_email].present?
#user.email = cookies[:challenges_email]
end
puts cookies[:challenges_email]
#user.email = cookies[:challenges_email]
#user.save
#user.send_welcome_email
log_in #user
redirect_to tutorial_url
flash[:info] = 'Welcome to Live to Challenge!'
else
render 'new'
end
end
One option would be to set a cookie when they submit the challenges form and then check for the existence of that cookie:
unless cookies[:challenges_email].present?
cookies[:challenges_email] = {
value: params[:email],
expires: 1.year.from_now
}
end
Then on the page where the user signs up, check for the existence of the cookie (inside user create action):
if cookies[:challenges_email].present?
#user.email = cookies[:challenges_email]
end
If you want to do this for additional fields you would set an additional cookie as they are meant to be key-value pairs.
Just assign the email that is stored in the session to the #user
# in users_controller
def new
#user = User.new
#user.email = session.delete(:user_email)
end
And in the UsersController#create take email from user
def create
#user = User.new(user_params)
if #user.save
action = session.delete(:challenge_action)
email = #user.email
#user.challenges.create(action: action, email: email)
end
end
If you want to save email to #user, you aren't doing it here unless it is part of user_params. You assign email from session but then you don't update #user.
def create
#user = User.new(user_params)
if #user.save
action = session.delete(:challenge_action)
email = session.delete(:user_email)
#user.challenges.create(action: action, email: email)
end
end
I'm not sure why you are passing email to #user.challenges.create as challenge_params does not allow email.
I would need to see your User model and schema to be sure, but what might work out for you is this:
def create
#user = User.new(user_params)
if #user.save
action = session.delete(:challenge_action)
email = session.delete(:user_email)
#user.update_attributes(email: email) unless #user.email
#user.challenges.create(action: action, email: email)
end
end
This will only assign email to #user if #user doesn't have an email already.
And, again, I'm not sure if you need to pass email to #user.challenges.create, although you do seem to have a default_email in your challenge_params, so if you need that you should change it to #user.challenges.create(action: action, default_email: email).
I am working on rails 3 and using the koala gem to get a connection to the facebook graph api.
And I am using omniauth to autenticate users.
So when a new user logs to the site, the session_controller handles the new user:
class SessionsController < ApplicationController
def create
user = User.from_omniauth(env['omniauth.auth'])
session[:user_id] = user.id
redirect_to root_url, notice: "Signed in!"
end
The create method call the "from_omniauth" class metod in the User.rb model, to create a new user:
class User < ActiveRecord::Base
has_many :friends
def self.from_omniauth(auth)
where(auth.slice(:provider, :uid)).first_or_initialize.tap do |user|
user.provider = auth["provider"]
user.uid = auth["uid"]
user.name = auth["info"]["name"]
user.first_name = auth["info"]["first_name"]
user.last_name = auth["info"]["last_name"]
user.image = auth["info"]["image"]
user.email = auth["info"]["email"]
user.gender = auth["extra"]["raw_info"]["gender"]
user.location = auth["extra"]["raw_info"]["location"]["name"]
user.token = auth["credentials"]["token"]
end
user.save!
end
I would like to save user facebook friends also and store it in a separate tabel called friends, so I made this friend model:
class Friend < ActiveRecord::Base
attr_accessible :name
belongs_to :user
validates :user_id, presence: true
def facebook
#Facebook ||= Koala::Facebook::API.new(token)
end
def add_friends
facebook { |fb| fb.get_connection("me", "friends") }
end
end
But I am lost on how to store users friends, I whould like to know:
How I can create a user, and store it friends
Where should I call the add_friends method?
fb.get_connection("me", "friends") return a array of hashes, like this > [{"name"=>"Johan Gyllenspetz", "id"=>"3624556"}, {"name"=>"Gustaf Josefsson", . And I would like to store the name and uid.
This is how I'd do this:
class User < ActiveRecord::Base
has_many :friends
def self.from_omniauth(auth)
user = where(auth.slice(:provider, :uid)).first_or_initialize.tap do |user|
user.provider = auth["provider"]
user.uid = auth["uid"]
user.name = auth["info"]["name"]
user.first_name = auth["info"]["first_name"]
user.last_name = auth["info"]["last_name"]
user.image = auth["info"]["image"]
user.email = auth["info"]["email"]
user.gender = auth["extra"]["raw_info"]["gender"]
user.location = auth["extra"]["raw_info"]["location"]["name"]
user.token = auth["credentials"]["token"]
end
user.add_friends
user.save
user
end
def add_friends
#facebook.get_connection("me", "friends").each do |hash|
self.friends.where(:name => hash['name'], :uid => hash['id']).first_or_create
end
end
private
def facebook
#facebook ||= Koala::Facebook::API.new(token)
end
end