Rails - Devise Invitable - 两个 Devise 用户模型 - 在发送邀请之前,Password can't be blank error with devise

Rails - Devise Invitable - Two Devise User Models - Password can't be blank error with devise prior to invitation being sent

提问人:bweathers 提问时间:4/7/2022 最后编辑:bweathers 更新时间:4/12/2022 访问量:480

问:

我正在尝试使用两个用户模型设置devise_invitable;用户和 Exechost。用户模型一切正常,首先实现了用户代码,然后添加了第二个模型 Exechost。

使用第二种模型,不会创建或发送电子邮件邀请,并且我们会被重定向到设计注册表单,并出现以下错误:

1 个错误禁止保存此 exechost:

  • 密码不能为空

我也在使用 rolify 和 pundit。

以下是终端输出:

Started POST "/exechosts" for ::1 at 2022-04-07 10:17:33 -0400
10:17:33 web.1  | Processing by Exechosts::RegistrationsController#create as HTML
10:17:33 web.1  |   Parameters: {"authenticity_token"=>"[FILTERED]", "exechost"=>{"username"=>"test20", "email"=>"[email protected]"}, "commit"=>"Add Moderator"}
10:17:33 web.1  |   TRANSACTION (0.1ms)  BEGIN
10:17:33 web.1  |   ↳ app/controllers/exechosts/registrations_controller.rb:18:in `create'
10:17:33 web.1  |   Exechost Exists? (0.3ms)  SELECT 1 AS one FROM "exechosts" WHERE "exechosts"."email" = $1 LIMIT $2  [["email", "[email protected]"], ["LIMIT", 1]]
10:17:33 web.1  |   ↳ app/controllers/exechosts/registrations_controller.rb:18:in `create'
10:17:33 web.1  |   TRANSACTION (0.1ms)  ROLLBACK
10:17:33 web.1  |   ↳ app/controllers/exechosts/registrations_controller.rb:18:in `create'
10:17:33 web.1  |   Rendering layout layouts/application.html.erb
10:17:33 web.1  |   Rendering exechosts/registrations/new.html.erb within layouts/application
10:17:33 web.1  |   Rendered exechosts/shared/_error_messages.html.erb (Duration: 0.9ms | Allocations: 432)
10:17:33 web.1  |   Rendered exechosts/shared/_links.html.erb (Duration: 0.1ms | Allocations: 72)
10:17:33 web.1  |   Rendered exechosts/registrations/new.html.erb within layouts/application (Duration: 3.3ms | Allocations: 2073)
10:17:33 web.1  |   Exechost Load (0.3ms)  SELECT "exechosts".* FROM "exechosts" WHERE "exechosts"."id" = $1 ORDER BY "exechosts"."created_at" ASC, "exechosts"."id" ASC LIMIT $2  [["id", "9335e908-f90a-455c-9eb4-f2f8a7ab29ec"], ["LIMIT", 1]]
10:17:33 web.1  |   ↳ app/views/pages/_navBar.html.erb:25
10:17:33 web.1  |   ExecRole Load (0.4ms)  SELECT "exec_roles".* FROM "exec_roles" INNER JOIN "exechosts_exec_roles" ON "exec_roles"."id" = "exechosts_exec_roles"."exec_role_id" WHERE "exechosts_exec_roles"."exechost_id" = $1 AND (((exec_roles.name = 'owner') AND (exec_roles.resource_type IS NULL) AND (exec_roles.resource_id IS NULL)))  [["exechost_id", "9335e908-f90a-455c-9eb4-f2f8a7ab29ec"]]
10:17:33 web.1  |   ↳ app/views/pages/_navBar.html.erb:27
10:17:33 web.1  |   ExecRole Load (0.6ms)  SELECT "exec_roles".* FROM "exec_roles" INNER JOIN "exechosts_exec_roles" ON "exec_roles"."id" = "exechosts_exec_roles"."exec_role_id" WHERE "exechosts_exec_roles"."exechost_id" = $1 AND (((exec_roles.name = 'superowner') AND (exec_roles.resource_type IS NULL) AND (exec_roles.resource_id IS NULL)))  [["exechost_id", "9335e908-f90a-455c-9eb4-f2f8a7ab29ec"]]

以下是使用用户模型邀请时的成功输出:



10:22:01 web.1  | Started POST "/account/users" for ::1 at 2022-04-07 10:22:01 -0400
10:22:01 web.1  | Processing by UsersController#create as HTML
10:22:01 web.1  |   Parameters: {"authenticity_token"=>"[FILTERED]", "user"=>{"first_name"=>"test2", "last_name"=>"test with User", "phone"=>"111-111-1111", "email"=>"[email protected]"}, "commit"=>"Add Team Member"}
10:22:01 web.1  |   User Exists? (0.3ms)  SELECT 1 AS one FROM "users" WHERE "users"."customer_num" = $1 LIMIT $2  [["customer_num", 86384], ["LIMIT", 1]]
10:22:01 web.1  |   ↳ app/models/user.rb:39:in `block in assign_unique_customer_num'
10:22:01 web.1  |   User Load (0.3ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."created_at" ASC, "users"."id" ASC LIMIT $2  [["id", "e22018b6-61a9-401e-9d15-f02924debcfd"], ["LIMIT", 1]]
10:22:01 web.1  |   ↳ app/controllers/application_controller.rb:20:in `current_account'
10:22:01 web.1  |   Account Load (0.2ms)  SELECT "accounts".* FROM "accounts" WHERE "accounts"."id" = $1 LIMIT $2  [["id", "eaa76bc4-078f-432f-be4f-e1730f0c7274"], ["LIMIT", 1]]
10:22:01 web.1  |   ↳ app/controllers/application_controller.rb:21:in `current_account'
10:22:02 web.1  |   User Exists? (0.4ms)  SELECT 1 AS one FROM "users" WHERE "users"."email" = $1 LIMIT $2  [["email", "[email protected]"], ["LIMIT", 1]]
10:22:02 web.1  |   ↳ app/controllers/users_controller.rb:90:in `block in create'
10:22:02 web.1  |   User Load (0.3ms)  SELECT "users".* FROM "users" WHERE "users"."invitation_token" = $1 ORDER BY "users"."created_at" ASC, "users"."id" ASC LIMIT $2  [["invitation_token", "[FILTERED]"], ["LIMIT", 1]]
10:22:02 web.1  |   ↳ app/controllers/users_controller.rb:90:in `block in create'
10:22:02 web.1  |   TRANSACTION (0.3ms)  BEGIN
10:22:02 web.1  |   ↳ app/controllers/users_controller.rb:90:in `block in create'
10:22:02 web.1  |   User Create (42.3ms)  INSERT INTO "users" ("first_name", "last_name", "phone", "status", "customer_num", "expiration_date", "created_at", "updated_at", "email", "encrypted_password", "reset_password_token", "reset_password_sent_at", "remember_created_at", "sign_in_count", "current_sign_in_at", "last_sign_in_at", "current_sign_in_ip", "last_sign_in_ip", "account_id", "invitation_token", "invitation_created_at", "invitation_sent_at", "invitation_accepted_at", "invitation_limit", "invited_by_type", "invited_by_id", "invitations_count") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27) RETURNING "id"  [["first_name", "test2"], ["last_name", "test with User"], ["phone", "111-111-1111"], ["status", "acitve"], ["customer_num", 86384], ["expiration_date", nil], ["created_at", "2022-04-07 14:22:02.102863"], ["updated_at", "2022-04-07 14:22:02.102863"], ["email", "[email protected]"], ["encrypted_password", "[FILTERED]"], ["reset_password_token", "[FILTERED]"], ["reset_password_sent_at", "[FILTERED]"], ["remember_created_at", nil], ["sign_in_count", 0], ["current_sign_in_at", nil], ["last_sign_in_at", nil], ["current_sign_in_ip", nil], ["last_sign_in_ip", nil], ["account_id", "eaa76bc4-078f-432f-be4f-e1730f0c7274"], ["invitation_token", "[FILTERED]"], ["invitation_created_at", "2022-04-07 14:22:02.089182"], ["invitation_sent_at", "2022-04-07 14:22:02.089182"], ["invitation_accepted_at", nil], ["invitation_limit", nil], ["invited_by_type", "User"], ["invited_by_id", 0], ["invitations_count", 0]]
10:22:02 web.1  |   ↳ app/controllers/users_controller.rb:90:in `block in create'
10:22:02 web.1  |   TRANSACTION (3.5ms)  COMMIT
10:22:02 web.1  |   ↳ app/controllers/users_controller.rb:90:in `block in create'
10:22:02 web.1  |   Rendering devise/mailer/invitation_instructions.html.erb
10:22:02 web.1  |   Rendered devise/mailer/invitation_instructions.html.erb (Duration: 2.2ms | Allocations: 1376)
10:22:02 web.1  |   Rendering devise/mailer/invitation_instructions.text.erb
10:22:02 web.1  |   Rendered devise/mailer/invitation_instructions.text.erb (Duration: 1.7ms | Allocations: 698)
10:22:02 web.1  | Devise::Mailer#invitation_instructions: processed outbound mail in 12.1ms
10:22:03 web.1  | Delivered mail [email protected] (852.5ms)
10:22:03 web.1  | Date: Thu, 07 Apr 2022 10:22:02 -0400
10:22:03 web.1  | From: [email protected]
10:22:03 web.1  | Reply-To: [email protected]
10:22:03 web.1  | To: [email protected]
10:22:03 web.1  | Message-ID: <[email protected]>
10:22:03 web.1  | Subject: Invitation instructions
10:22:03 web.1  | Mime-Version: 1.0
10:22:03 web.1  | Content-Type: multipart/alternative;
10:22:03 web.1  |  boundary="--==_mimepart_624ef38a28bcc_9d004ed411214";
10:22:03 web.1  |  charset=UTF-8
10:22:03 web.1  | Content-Transfer-Encoding: 7bit
10:22:03 web.1  | 
10:22:03 web.1  | 
10:22:03 web.1  | ----==_mimepart_624ef38a28bcc_9d004ed411214
10:22:03 web.1  | Content-Type: text/plain;
10:22:03 web.1  |  charset=UTF-8
10:22:03 web.1  | Content-Transfer-Encoding: 7bit
10:22:03 web.1  | 
10:22:03 web.1  | Hello [email protected]
10:22:03 web.1  | 
10:22:03 web.1  | Someone has invited you to http://localhost:3000/, you can accept it through the link below.
10:22:03 web.1  | 
10:22:03 web.1  | http://localhost:3000/users/invitation/accept?invitation_token=4TL5Vpyjfv5CFwCLGx9y
10:22:03 web.1  | 
10:22:03 web.1  |   This invitation will be due in April 21, 2022 02:22 PM.
10:22:03 web.1  | 
10:22:03 web.1  | If you don't want to accept the invitation, please ignore this email. Your account won't be created until you access the link above and set your password.
10:22:03 web.1  | 
10:22:03 web.1  | ----==_mimepart_624ef38a28bcc_9d004ed411214
10:22:03 web.1  | Content-Type: text/html;
10:22:03 web.1  |  charset=UTF-8
10:22:03 web.1  | Content-Transfer-Encoding: 7bit
10:22:03 web.1  | 
10:22:03 web.1  | <p>Hello [email protected]</p>
10:22:03 web.1  | 
10:22:03 web.1  | <p>Someone has invited you to http://localhost:3000/, you can accept it through the link below.</p>
10:22:03 web.1  | 
10:22:03 web.1  | <p>testing here what to say</p>
10:22:03 web.1  | <p><span class="translation_missing" title="translation missing: en.testing here what to say">Testing Here What To Say</span> </p>
10:22:03 web.1  | 
10:22:03 web.1  | 
10:22:03 web.1  | 
10:22:03 web.1  | 
10:22:03 web.1  | <p><a href="http://localhost:3000/users/invitation/accept?invitation_token=4TL5Vpyjfv5CFwCLGx9y">Accept invitation</a></p>
10:22:03 web.1  | 
10:22:03 web.1  |   <p>This invitation will be due in April 21, 2022 02:22 PM.</p>
10:22:03 web.1  | 
10:22:03 web.1  | <p>If you don&#39;t want to accept the invitation, please ignore this email. Your account won&#39;t be created until you access the link above and set your password.</p>
10:22:03 web.1  | 
10:22:03 web.1  | ----==_mimepart_624ef38a28bcc_9d004ed411214--
10:22:03 web.1  | 

以下是路线:


  get 'dashboard/show'
  get 'execdashboard/show'

  devise_for :users, controllers: { registrations: "registrations" }
  devise_for :exechosts, controllers: { registrations: "exechosts/registrations", invitations: "exechosts/invitations" }

Exechost 模型:

class Exechost < ApplicationRecord

  rolify :role_cname => 'ExecRole'

  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  
devise :invitable, :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

end

在 Exechost 控制器中:

class ExechostsController < ApplicationController

  before_action :set_exechost, only: [:show, :edit, :update, :edit_roles ,:update_roles] 
  before_action :set_exechosts, only: [:index]

  def new
    @exechost = Exechost.new
    set_exechost_choices
  end

  def create
    @exechost = Exechost.unscoped.new(exechost_params.except("role"))
    @exechost.password = "password123"

    respond_to do |format|
      begin
        if @exechost.valid? && @exechost.invite!(current_exechost)
          @exechost.add_role :moderator
          format.html {
            redirect_to exechosts_path,
            notice: 'Moderator was successfully invited.'
          }
        else
          set_exechost_choices
          format.html { render :new }
        end
      rescue ActiveRecord::RecordNotUnique
        flash[:alert]= 'Email must be unique'
        format.html { render :new}
      end
    end
  end


  private

   def pundit_user
     user = current_exechost
   end

   def set_exechosts
     @exechosts = Exechost.all
   end

   def set_exechost
     @exechost = Exechost.find(params[:id])
   end

   def exechost_params
     params.require(:exechost).permit(:username, :email, :role)
   end

end

在邀请控制器中:

class Exechosts::InvitationsController < Devise::InvitationsController

  before_action :update_sanitized_params, only: :update

  def create

    @exechost = Exechost.invite!(exechost_params[:exechost], current_exechost) do |u| 
      u.skip_invitation = true
    end

    ExechostInvitationNotificationMailer.invite_message(@exechost).deliver if @exechost.errors.empty?
    @exechost.invitation_sent_at = Time.now.utc # mark invitation as delivered

    if @exechost.errors.empty?
      flash[:notice] = "successfully sent invite to #{@exechost.email}"
      respond_with @exechost, :location => exechosts_path
    else
      render :new
    end
  end


  private

  def exechost_params
    params.require(:exechost).permit(:username, :email, :role)
  end
end


注册控制器:



class Exechosts::RegistrationsController < Devise::RegistrationsController
  protect_from_forgery with: :exception, prepend: true

  prepend_before_action :require_no_authentication, only: [:cancel]

  before_action :configure_sign_up_params

  

  

  protected

  # If you have extra params to permit, append them to the sanitizer.
  def configure_sign_up_params
    devise_parameter_sanitizer.permit(:sign_up, keys: [[:username, :email]])
  end

  # The path used after sign up.
  def after_sign_up_path_for(resource)
    exechosts_path
  end

  
end

设计控制器:


class Exechosts::DeviseController < ApplicationController
  class Responder < ActionController::Responder
    def to_turbo_stream
      controller.render(options.merge(formats: :html))
    rescue ActionView::MissingTemplate => error
      if get?
        raise error
      elsif has_errors? && default_action
        render rendering_options.merge(formats: :html, status: :unprocessable_entity)
      else
        redirect_to navigation_location
      end
    end
  end

  self.responder = Responder
  respond_to :html, :turbo_stream
end

和 config/initializers/devise.rb;只有不可避免的部分,唯一的开启是两周的限制。



  # ==> Configuration for :invitable
  # The period the generated invitation token is valid.
  # After this period, the invited resource won't be able to accept the invitation.
  # When invite_for is 0 (the default), the invitation won't expire.
  config.invite_for = 2.weeks

  # Number of invitations users can send.
  # - If invitation_limit is nil, there is no limit for invitations, users can
  # send unlimited invitations, invitation_limit column is not used.
  # - If invitation_limit is 0, users can't send invitations by default.
  # - If invitation_limit n > 0, users can send n invitations.
  # You can change invitation_limit column for some users so they can send more
  # or less invitations, even with global invitation_limit = 0
  # Default: nil
  # config.invitation_limit = 5

  # The key to be used to check existing users when sending an invitation
  # and the regexp used to test it when validate_on_invite is not set.
  # config.invite_key = { email: /\A[^@]+@[^@]+\z/ }
  # config.invite_key = { email: /\A[^@]+@[^@]+\z/, username: nil }

  # Ensure that invited record is valid.
  # The invitation won't be sent if this check fails.
  # Default: false
  # config.validate_on_invite = true

  # Resend invitation if user with invited status is invited again
  # Default: true
  # config.resend_invitation = false

  # The class name of the inviting model. If this is nil,
  # the #invited_by association is declared to be polymorphic.
  # Default: nil
  #config.invited_by_class_name = 'User'


  # The foreign key to the inviting model (if invited_by_class_name is set)
  # Default: :invited_by_id
  # config.invited_by_foreign_key = :invited_by_id

  # The column name used for counter_cache column. If this is nil,
  # the #invited_by association is declared without counter_cache.
  # Default: nil
  # config.invited_by_counter_cache = :invitations_count

  # Auto-login after the user accepts the invite. If this is false,
  # the user will need to manually log in after accepting the invite.
  # Default: true
  # config.allow_insecure_sign_in_after_accept = false

用户控制器中用于创建的代码;代码与添加引用用户帐户相同。

如上所述,创建操作会创建新用户和电子邮件邀请。

def create
    @user = User.unscoped.new(user_params.except("role"))
    @user.account = current_account
    @user.password = "password123"

    respond_to do |format|
      begin
        if @user.valid? && @user.invite!(current_user)
          @user.add_role :member, current_account
          format.html {
            redirect_to account_users_path,
            notice: 'User was successfully invited.'
          }
        else
          set_choices
          format.html { render :new }
        end
      rescue ActiveRecord::RecordNotUnique
        flash[:alert]= 'Email must be unique'
        format.html { render :new}
      end
    end
  end

Ruby-on-Rails Ruby 设计 Invitable

评论


答:

1赞 Sergio Cambra 4/12/2022 #1

/exechosts 转到 Exechosts::RegistrationsController,它不是 ExechostsController,并且创建操作是 Devise 的正常创建操作,需要密码参数。但是,/account/users 转到 UsersController 请求,其中包含您的自定义代码。

路由代码不包含与 UsersController 或 ExechostsController 相关的任何内容。此外,如果您有一些自定义代码为用户和执行主机调用邀请,您可能不需要到邀请控制器的路由,并且您可能希望使用跳过选项来不为邀请生成路由。