简体   繁体   中英

Omniauth Twitter - Redirect to form for extra Information

I'm following this tutorial but fail on how to redirect the User to a page to fill out more information when he connects with omniauth.

In my User Model I have:

user = User.new(
  name: auth.extra.raw_info.name,
  user_name: auth.info.nickname,
  about_me: auth.info.description,
  email: email ? email : "#{TEMP_EMAIL_PREFIX}-#{auth.uid}-#{auth.provider}.com",
  password: Devise.friendly_token[0,20]
)
user.skip_confirmation!
user.save!

In my Omniauth callbacks controller I have:

def after_sign_in_path_for(resource)
  if resource.profile_valid?
    super resource
  else
    finish_signup_path(resource        
  end
end

profile_valid checks for:

def profile_valid?
  self.email && self.email !~ TEMP_EMAIL_REGEX
end

Twitter auth doesn't give you an email, which I require for a Registration, so i'm passing a dummy email with a regex (TEMP_EMAIL_REGEX).

So when a valid email isn't present it should redirect to the finish_signup_page which contains:

<div id="add-email" class="container">
  <h1>Add Email</h1>
  <%= form_for(current_user, :as => 'user', :url => finish_signup_path(current_user), :html => { role: 'form'}) do |f| %>


    <% if @show_errors && current_user.errors.any? %>
      <div id="error_explanation">
        <% current_user.errors.full_messages.each do |msg| %>
          <%= msg %><br>
        <% end %>
      </div>
    <% end %>

    <!-- User Name -->
    <div class="form-group">
      <%= f.label :user_name %>
      <div class="controls">
        <%= f.text_field :user_name, :autofocus => true, :value => '', class: 'form-control', placeholder: 'Username' %>
        <p class="help-block">Please enter your username</p>
      </div>
    </div>

    <div class="form-group">
      <%= f.label :email %>
      <div class="controls">
        <%= f.text_field :email, :autofocus => true, :value => '', class: 'form-control', placeholder: 'Example: email@me.com' %>
        <p class="help-block">Please confirm your email address. No spam.</p>
      </div>
    </div>
    <div class="actions">
      <%= f.submit 'Continue', :class => 'btn btn-primary' %>
    </div>
  <% end %>
</div>

Here is my problem, when I enter something it doesn't save the User and it would be better if in the actual fields the current values would be present?

But I'm lost on how to do that, or maybe I'm trying this now for too long and have a tunnel-vision.

What am I Missing?

First go through the following classes. You need to store the correct data for the user, a generated email is not a good solution. So we basically store the provider's data in a separate model Social Provider .

Registration Flow:

  • sp = Social Provider is created
  • The user is initialized by the data received from the provider (not saved).
  • We store the sp id in the session, to isolate it from the user reach.
  • we render device registration/new view (adding all fields we need).
  • On successful registeration, we link the sp to the created user.

1.The User Class, has update_from_oauth method, which updates the main attributes in the user without saving it.

##########################
# User Class
##########################

class User < ActiveRecord::Base
  has_many :social_providers, dependent: :destroy

  # update from OAuth
  def update_from_oauth(auth, provider_type)
    self.email = auth[:info][:email] if self.email.blank?
    case provider_type
    when :twitter
      name = auth[:info][:name].split(' ')
      self.first_name ||= name[0]
      self.last_name ||= name[1]
      self.remote_avatar_url = auth[:extra][:raw_info][:profile_image_url]
    when :facebook
      ...
    when :google
      ...
    end
  end
end

2. SocialProvider class or (Identity)

class SocialProvider < ActiveRecord::Base

  #Relations
  belongs_to :user

  def self.find_for_oauth(auth, provider_type)
    unless social_provider = self.find_by(pid: auth[:uid].to_s, provider_type: provider_type)
      user = User.find_by_email(auth[:info][:email])
      social_provider = user.social_providers.where(provider_type: provider_type).first if user.present?
      social_provider ||= SocialProvider.new
    end
    social_provider.update_from_oauth(auth, provider_type)
    social_provider
  end

  def update_from_oauth(auth, provider_type)
    self.email= auth[:info][:email]
    self.pid= auth[:uid]
    self.provider_type= provider_type
    credentials = auth[:credentials]
    case provider_type
    when :twitter
      self.token = credentials[:token]
      self.secret = credentials[:secret]
      self.url = auth[:info][:urls][:Twitter]
    when :facebook
      ...
    when :google
      ...
    end
  end
end

##########################
# SocialProvider Migration
##########################

class CreateSocialProviders < ActiveRecord::Migration
  def change
    create_table "social_providers" do |t|
      t.string   "pid" # user provider id
      t.string   "token"
      t.string   "refresh_token"
      t.string   "secret"
      t.datetime "expires_at"
      t.string   "provider_type"
      t.integer  "user_id"
      t.string   "url"
      t.string   "email"
      t.timestamps
    end
  end
end

3. Omniauth Callbacks Controller

###############################
# OmniAuth Callbacks Controller
###############################

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController

  before_filter :prepare_auth

  def facebook
    connect(:facebook)
  end

  def twitter
    connect(:twitter)
  end

  def google_oauth2
    connect(:google)
  end

  private

    def prepare_auth
      @auth = request.env["omniauth.auth"]
    end

    def connect(provider_type)
      social_provider = SocialProvider.find_for_oauth(@auth, provider_type)
      if user_signed_in?
        if social_provider and social_provider.user_id == current_user.id
          flash[:notice] = "Your #{provider_type} account is already attached"
          redirect_to current_user and return
        elsif social_provider.user_id.nil?
          current_user.update_from_oauth(@auth, provider_type)
          current_user.social_providers << social_provider if current_user.save
          flash[:notice] = "Successfully attached #{provider_type} account"
          redirect_to current_user and return
        else
          flash[:notice] = "#{provider_type} is already connected to another account"
          redirect_to current_user and return
        end
      else
        @user = social_provider.user || User.find_by_email(@auth[:info][:email]) || User.new
        @user.update_from_oauth(@auth, provider_type)
        social_provider.save
        if @user.persisted? # If user already has account and not logged in
          @user.social_providers << social_provider if @user.save
          flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => provider_type.capitalize
          sign_in_and_redirect @user, :event => :authentication
        else # If user has no account
          session[:sp_id] = social_provider.id 
          render 'registrations/new'
        end
      end
    end
end

4. Overriding Devise registration controller

#################################
# Devise::RegistrationsController
#################################

class RegistrationsController < Devise::RegistrationsController

  def create
    super
    if user_signed_in? and session[:sp_id].present?
      SocialProvider.find_by(id: session[:sp_id],user_id: nil).update(user_id: current_user.id)
    end
  end

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM