find_or_create_by!验证失败时插入记录

时间:2014-06-01 19:03:27

标签: ruby-on-rails validation activerecord devise

在我的rails 4.1 app中使用devise进行用户身份验证。

我尝试从名为transactions_controller的控制器手动创建用户,并且有一个名为CreateAdminService的类,用于根据user中收到的参数创建POST

这是CreateAdminService中执行该操作的代码

  def call (params, confirm = true, role = :user)
    user = User.find_or_create_by!(email: params[:email]) do |user|
        user.name     = params[:name] if params[:name]
        user.password = params[:password]
        user.password_confirmation = params[:password_confirmation]
        user.confirm! if confirm
        user.send("#{role}!") unless role == :user
      end
  end 

一切正常。当我发送的密码不匹配时,将显示验证失败消息,但会创建记录。新记录没有存储任何密码。我不知道这是devisefind_or_create_by!显示此行为的问题。

我尝试在没有find_or_create_by的情况下使用!,这会创建记录并且不会检查任何验证或抛出任何非常糟糕的错误。

有人可以帮助调试这种情况发生的原因,或者我可以在所有验证工作的同时创建用户。

编辑:用户模型如下

class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable

  devise :database_authenticatable, :registerable, :confirmable,
         :recoverable, :rememberable, :trackable, :validatable

  enum role: [:user, :vip, :admin]
  enum status: [:active, :suspended]

  has_many :accounts
  has_many :txns

  after_initialize :set_default_role, :if => :new_record?

  after_create :setup_account

  private

  def set_default_role
    self.role  ||= :user
    self.status ||= :active
  end

  def setup_account
    self.accounts.create!
  end
end

请求和错误的日志是

Started POST "/start" for 127.0.0.1 at 2014-06-01 22:08:38 +0400
Processing by TransactionsController#start as JSON
  Parameters: {"user"=>{"name"=>"test", "email"=>"yavaidya4@test.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "transaction"=>{"user"=>{"name"=>"test", "email"=>"yavaidya4@test.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}}}
  User Load (0.1ms)  SELECT  "users".* FROM "users"  WHERE "users"."email" = 'yavaidya4@test.com' LIMIT 1
   (0.1ms)  begin transaction
Binary data inserted for `string` type on column `encrypted_password`
  SQL (0.4ms)  INSERT INTO "users" ("confirmed_at", "created_at", "email", "encrypted_password", "name", "role", "status", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?)  [["confirmed_at", "2014-06-01 18:08:38.344688"], ["created_at", "2014-06-01 18:08:38.345141"], ["email", "yavaidya4@test.com"], ["encrypted_password", "$2a$10$nUiH/dLDrcNAOWpAPTBBYO.pA/mKKpHoCNPiM/0oX/jBiZy8cogWC"], ["name", "test"], ["role", 0], ["status", 0], ["updated_at", "2014-06-01 18:08:38.345141"]]
  Account Exists (0.2ms)  SELECT  1 AS one FROM "accounts"  WHERE "accounts"."user_id" = 14 LIMIT 1
  SQL (0.2ms)  INSERT INTO "accounts" ("balance", "created_at", "number", "status", "updated_at", "user_id") VALUES (?, ?, ?, ?, ?, ?)  [["balance", 0.0], ["created_at", "2014-06-01 18:08:38.364984"], ["number", "22743176"], ["status", 1], ["updated_at", "2014-06-01 18:08:38.364984"], ["user_id", 14]]
   (0.7ms)  commit transaction
   (0.0ms)  begin transaction
   (0.2ms)  rollback transaction
Completed 422 Unprocessable Entity in 107ms



ActiveRecord::RecordInvalid - Validation failed: Password confirmation doesn't match Password:
   () Users/test/.rvm/gems/ruby-2.1.1@global/gems/activerecord-4.1.0/lib/active_record/validations.rb:57:in `save!'
   () Users/test/.rvm/gems/ruby-2.1.1@global/gems/activerecord-4.1.0/lib/active_record/attribute_methods/dirty.rb:29:in `save!'
   () Users/test/.rvm/gems/ruby-2.1.1@global/gems/activerecord-4.1.0/lib/active_record/transactions.rb:273:in `block in save!'
   () Users/test/.rvm/gems/ruby-2.1.1@global/gems/activerecord-4.1.0/lib/active_record/transactions.rb:329:in `block in with_transaction_returning_status'
   () Users/test/.rvm/gems/ruby-2.1.1@global/gems/activerecord-4.1.0/lib/active_record/connection_adapters/abstract/database_statements.rb:211:in `block in transaction'
   () Users/test/.rvm/gems/ruby-2.1.1@global/gems/activerecord-4.1.0/lib/active_record/connection_adapters/abstract/database_statements.rb:219:in `within_new_transaction'
   () Users/test/.rvm/gems/ruby-2.1.1@global/gems/activerecord-4.1.0/lib/active_record/connection_adapters/abstract/database_statements.rb:211:in `transaction'
   () Users/test/.rvm/gems/ruby-2.1.1@global/gems/activerecord-4.1.0/lib/active_record/transactions.rb:208:in `transaction'
   () Users/test/.rvm/gems/ruby-2.1.1@global/gems/activerecord-4.1.0/lib/active_record/transactions.rb:326:in `with_transaction_returning_status'
   () Users/test/.rvm/gems/ruby-2.1.1@global/gems/activerecord-4.1.0/lib/active_record/transactions.rb:273:in `save!'
   () Users/test/.rvm/gems/ruby-2.1.1@global/gems/activerecord-4.1.0/lib/active_record/validations.rb:41:in `create!'
   () Users/test/.rvm/gems/ruby-2.1.1@global/gems/activerecord-4.1.0/lib/active_record/relation.rb:140:in `block in create!'
   () Users/test/.rvm/gems/ruby-2.1.1@global/gems/activerecord-4.1.0/lib/active_record/relation.rb:286:in `scoping'
   () Users/test/.rvm/gems/ruby-2.1.1@global/gems/activerecord-4.1.0/lib/active_record/relation.rb:140:in `create!'
   () Users/test/.rvm/gems/ruby-2.1.1@global/gems/activerecord-4.1.0/lib/active_record/relation.rb:208:in `find_or_create_by!'
   () Users/test/.rvm/gems/ruby-2.1.1@global/gems/activerecord-4.1.0/lib/active_record/querying.rb:6:in `find_or_create_by!'
  app/services/create_admin_service.rb:11:in `call'
  app/controllers/transactions_controller.rb:8:in `start'

2 个答案:

答案 0 :(得分:2)

好的,你的日志记录看起来很奇怪:它明显地保存了用户,并且只有在那之后验证才会失败。还有一些奇怪的事情:confirmed_at晚于created_at/updated_at。等待。 confirmed_at已经填写了?在您的创建块中,您可以在用户上混合创建和操作。

我只是在猜测,但在我看来,confirm!可能以某种方式迫使用户被保存,然后在find_or_create_by结束时再次保存用户(或{}尝试保存),然后检查验证。

检查devise code我的预感已经确认(双关语!):它会在没有任何验证的情况下保存用户。

所以我会尝试以下方法:

def call (params, confirm = true, role = :user)
  user = User.find_or_create_by!(email: params[:email]) do |user|
    user.name     = params[:name] if params[:name]
    user.password = params[:password]
    user.password_confirmation = params[:password_confirmation]
  end
  user.confirm! if confirm
  user.send("#{role}!") unless role == :user
end 

如果未创建用户,则抛出异常,并且最后两行永远不会执行。

旁注:after_initialize并不理想,因为它被称为很多。

在你的情况下,因为你似乎只是想设置一些默认值,我会做一个

before_create :set_default_role

这只会在保存新用户之前调用。

答案 1 :(得分:0)

也许开始使用带有参数的find_or_initialize_by,然后调用这样的条件:

def call (params, confirm = true, role = :user)
  user = User.find_or_initialize_by(email: params[:email], name: params[:name])
  user.password = params[:password]
  user.password_confirmation = params[:password_confirmation]
  if user.save
    user.confirm! if confirm
    user.send(role) unless roll == :user
  end
end