Rails 4 - 设计Omniauth(多种策略)

时间:2016-01-08 09:05:54

标签: ruby-on-rails devise omniauth

我正在尝试在Rails 4中创建一个应用程序。我已经尝试了最近3年(除了10天),以便设计工作。

我正在尝试按照本教程:http://sourcey.com/rails-4-omniauth-using-devise-with-twitter-facebook-and-linkedin/

请不要推荐其他教程/ gem文档。我已经尝试了至少30个其他教程,并且gem文档中充满了我不理解的错误和组件。

我目前的问题是,当我到达本教程中的完成注册步骤时,表单会要求我提供我的电子邮件地址。

用户控制器的完成注册方法为:

def finish_signup
    # authorize! :update, @user 
    if request.patch? && params[:user] #&& params[:user][:email]
      if @user.update(user_params)
        @user.skip_reconfirmation!
        # sign_in(@user, :bypass => true)
        # redirect_to root_path, notice: 'Your profile was successfully updated.'
        # redirect_to [@user, @user.profile || @user.build_profile]
        sign_in_and_redirect(@user, :bypass => true)
      else
        @show_errors = true
      end
    end
  end

当我尝试这个时,我收到了这个错误:

undefined method `match' for {:host=>"localhost", :port=>3000}:Hash

此行的错误点:

        <div class="intpol3"><%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %></div>

我的开发环境设置为包含电子邮件发件人的所有配置详细信息。

当我在生产模式中尝试相同的步骤时,我收到此错误:

ActionView::Template::Error (No route matches {:action=>"show", :controller=>"profiles", :id=>nil} missing required keys: [:id]):

它正在寻找个人资料ID,因为我在我的用户模型中有一个after_create操作:

after_create :gen_profile

  def gen_profile
    Profile.create(user: self) # Associations must be defined correctly for this syntax, avoids using ID's directly.
    # Profile.save
  end

本教程的另一个问题是身份表中的字段未填充。

我很想找到成功实施本教程的人,或者可以看看如何使其发挥作用。

我的代码是:

的Gemfile

gem 'devise', '3.4.1'
gem 'devise_zxcvbn'
gem 'omniauth'
gem 'omniauth-oauth2', '1.3.1'
gem 'omniauth-google-oauth2'
gem 'omniauth-facebook'
gem 'omniauth-twitter'
gem 'omniauth-linkedin-oauth2'
gem 'google-api-client', require: 'google/api_client'

路由

devise_for :users, #class_name: 'FormUser',
             :controllers => {
                :registrations => "users/registrations",
                # :omniauth_callbacks => "users/authentications"
                :omniauth_callbacks => 'users/omniauth_callbacks'
           }

  # get '/auth/:provider/callback' => 'users/authentications#create'
  # get '/authentications/sign_out', :to => 'users/authentications#destroy' 

  # PER SOURCEY TUTORIAL ----------
  match '/users/:id/finish_signup' => 'users#finish_signup', via: [:get, :patch], :as => :finish_signup

resources :users do
     resources :profiles, only: [:new, :create]
  end

user.rb

class User&lt;的ActiveRecord ::基

  TEMP_EMAIL_PREFIX = 'change@me'
  TEMP_EMAIL_REGEX = /\Achange@me/

  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable,
          :confirmable, :lockable,
         # :zxcvbnable,
         :omniauthable, :omniauth_providers => [:facebook, :linkedin, :twitter, :google_oauth2 ]





  # --------------- associations

  has_many :authentications, :dependent => :delete_all

  has_one :profile

  has_many :identities


  # --------------- scopes

  # --------------- validations

   # validates_presence_of :first_name, :last_name
   validates_uniqueness_of :email

  # per sourcey tutorial - how do i confirm email registrations are unique?
  # this is generating an error about the options in the without function -- cant figure out the solution
  validates_format_of :email, :without => TEMP_EMAIL_REGEX, on: :update 


  # --------------- class methods


# sourcey tutorial

 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

用户控制器

class UsersController < ApplicationController

before_action :set_user, only: [:index, :show, :edit, :update, :finish_signup, :destroy]

# i added finish_signup to the set_user action (not shown in tutorial)

  def index
    # if params[:approved] == "false"
    #   @users = User.find_all_by_approved(false)
    # else
      @users = User.all
    # end

  end

  # GET /users/:id.:format
  def show
    # authorize! :read, @user
  end

  # GET /users/:id/edit
  def edit
    # authorize! :update, @user
  end

  # PATCH/PUT /users/:id.:format
  def update
    # authorize! :update, @user
    respond_to do |format|
      if @user.update(user_params)
        sign_in(@user == current_user ? @user : current_user, :bypass => true)
        format.html { redirect_to @user, notice: 'Your profile was successfully updated.' }
        format.json { head :no_content }
      else
        format.html { render action: 'edit' }
        format.json { render json: @user.errors, status: :unprocessable_entity }
      end
    end
  end

  # GET/PATCH /users/:id/finish_signup
  def finish_signup
    # authorize! :update, @user 
    if request.patch? && params[:user] #&& params[:user][:email]
      if @user.update(user_params)
        @user.skip_reconfirmation!
        # sign_in(@user, :bypass => true)
        # redirect_to root_path, notice: 'Your profile was successfully updated.'
        # redirect_to [@user, @user.profile || @user.build_profile]
        sign_in_and_redirect(@user, :bypass => true)
      else
        @show_errors = true
      end
    end
  end

  # DELETE /users/:id.:format
  def destroy
    # authorize! :delete, @user
    @user.destroy
    respond_to do |format|
      format.html { redirect_to root_url }
      format.json { head :no_content }
    end
  end

  private
    def set_user
      @user = User.find(params[:id])
    end

    def user_params
      # params.require(:user).permit(policy(@user).permitted_attributes)
      accessible = [ :first_name, :last_name, :email, :avatar ] # extend with your own params
      accessible << [ :password, :password_confirmation ] unless params[:user][:password].blank?
      # accessible << [:approved] if user.admin
      params.require(:user).permit(accessible)
    end

end

omniauth回调控制器

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController

  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

          set_flash_message(:notice, :success, kind: "#{provider}".capitalize) if is_navigational_format?
        else
          session["devise.#{provider}_data"] = env["omniauth.auth"]
          redirect_to new_user_registration_url
        end
      end
    }
  end

  # , current_user has been deleted from the end of line 51
  #come back to put current_user into fidn by oauth so i can link other accounts - i have added this back for the purpose of solving the current problem

          # puts current_user.inspect
           # sign_in_and_redirect [@user, @user.profile || @user.build_profile]

          # sign_in_and_redirect_user(:user, event: :authentication)


  [:twitter, :facebook, :linkedin, :google_oauth2].each do |provider|
    provides_callback_for provider
  end

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

end

注册控制器

class Users::RegistrationsController < Devise::RegistrationsController 




      protected

      def after_sign_up_path_for(resource)
      profile_path(resource)
  end


  private
    def user_params
          params.require(:user).permit(:first_name, :last_name, :email, :password )
    end

    end

Identity.rb

class Identity < ActiveRecord::Base


  belongs_to :user
  validates_presence_of :uid, :provider
  validates_uniqueness_of :uid, :scope => :provider



  def self.find_for_oauth(auth)
    find_or_create_by(uid: auth.uid, provider: auth.provider)
  end


end

身份控制器

class IdentitiesController < ApplicationController
  before_action :set_identity, only: [:show, :edit, :update, :destroy]
  before_action :authenticate_user!

  # GET /identities
  # GET /identities.json
  def index
    @identities = Identity.all
  end

  # GET /identities/1
  # GET /identities/1.json
  def show
  end

  # GET /identities/new
  def new
    @identity = Identity.new
  end

  # GET /identities/1/edit
  def edit
  end

  # POST /identities
  # POST /identities.json
  def create
    @identity = Identity.new(identity_params)

    respond_to do |format|
      if @identity.save
        format.html { redirect_to @identity, notice: 'Identity was successfully created.' }
        format.json { render :show, status: :created, location: @identity }
      else
        format.html { render :new }
        format.json { render json: @identity.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /identities/1
  # PATCH/PUT /identities/1.json
  def update
    respond_to do |format|
      if @identity.update(identity_params)
        format.html { redirect_to @identity, notice: 'Identity was successfully updated.' }
        format.json { render :show, status: :ok, location: @identity }
      else
        format.html { render :edit }
        format.json { render json: @identity.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /identities/1
  # DELETE /identities/1.json
  def destroy
    @identity.destroy
    respond_to do |format|
      format.html { redirect_to identities_url, notice: 'Identity was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_identity
      @identity = Identity.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def identity_params
      params[:identity]
    end
end

设计邮件 - 确认

        <div class="intpol3"><%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %></div>

当前问题摘要:

  1. 在开发模式下:确认令牌的链接存在问题。我找不到任何表明可能出现这种情况的材料。错误是:(ActionView :: Template :: Error({:host =&gt;“localhost”,:port =&gt; 3000}的未定义方法`匹配':哈希):

  2. 在生产模式下,用户在查找配置文件ID时出错。错误消息是:ActionView :: Template :: Error(没有路由匹配{:action =&gt;“show”,:controller =&gt;“profiles”,:id =&gt; nil}缺少必需的键:[:id] ):

  3. 我的个人资料路线是:

    resources :profiles, only: [:show, :edit, :update, :destroy]
    resources :users do
         resources :profiles, only: [:new, :create]
      end
    
    1. 身份模型中的所有字段都没有填充。它们都显示为零。
    2. 所做的事情与教程中所显示的不同:

      1. 我也允许电子邮件注册

      2. 我在用户控制器中的操作之前将'finish_sign_up'添加到set_user

      3. 我添加了g +策略(这意味着我的宝石略有不同)

      4. 我的新用户方法不使用原始信息。它使用oauth处理的信息。

      5. 我在完成注册方法中的重定向略有不同,虽然我已经评论过并回到教程中设置的方式以尝试使其工作(尽管上述问题是重复)。

      6. 我疯狂地试图解决这些问题。我会说3年太长时间不能坚持这个问题。如果有人可以提供帮助,我会向前支付10倍,然后支付一些。谢谢。

1 个答案:

答案 0 :(得分:1)

resource

尝试使用@resource代替@resource。 AFAIK它只是一个helper_method,而不是一个实例变量。

我认为这将在生产中完全解决您的问题。由于resourceconfirmation_url(nil, confirmation_token: @token)不同,因此尚未设置,并且您基本上正在调用nil,而config.action_mailer.default_url_options正在通过通过错误信息。

在开发过程中,似乎还有一个问题,很可能与您在config/environments/development.rb中配置config.action_mailer.default_url_options[:host] = { host: 'localhost', port: 9000 } 的方式有关,而且最有可能在{{3}中引发异常}}。我怀疑你有类似的东西:

config.action_mailer.default_url_options[:host] = 'localhost:9000'

将其更改为:

config.action_mailer.default_url_options

看看是否能解决所有问题。如果我对config/environments/development.rb的配置方式有误,请粘贴您的{{1}}以及开发错误中的完整堆栈跟踪,以便我们为您提供进一步的帮助。