In my Rails 4.1 app I'm using devise for user authentication.
I'm trying to create a user manually from a controller called transactions_controller
and there is a class called CreateAdminService
which I use for creating user
from the parameters received in POST
.
This is the code in CreateAdminService
which does that
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
Everything works fine. When the passwords I sent don't match, the validations fail messages is show but the record is created. The new record doesn't have any passwords stored. I don't know if this a problem with devise
or find_or_create_by!
which shows this behaviour.
I tried to use find_or_create_by
without the !
and this creates the record and doesn't check any validations or throws any errors which is quite bad.
How can I debug why this is happening or how I can create user whilst all validations can work.
Edit: The user model is as follows
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
The log for the request and the error is this
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'
Ok, your logging seems pretty weird: it clearly saves the user, and only after that the validations fail. And something else that is weird: the confirmed_at
is later then the created_at/updated_at
. Wait. confirmed_at
is already filled in? In your create block, you mix creation and action on the user.
I am just guessing, but it seems to me the confirm!
might somehow force the user to be saved already, and then later, at the end of the find_or_create_by
the user is saved again (or attempted to be saved), and then the validations are checked.
Checking the devise code my hunch is confirmed (pun! awwwww): it saves the user without any validations.
So I would try the following:
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
If the user was not created, an exception is thrown, and the last two lines are never executed anyway.
On a side-note: after_initialize
is not ideal, since it is called a lot.
In your case, since you seem to just want to set some default values, I would do a
before_create :set_default_role
And this will only be called before saving a new user.