Data migration problems when you explicitly set ID's (Rails + Postgres) - ruby-on-rails

I'm writing a script for a big migration and have come across a major issue.
# Import users
user_data.each do |data|
u = User.new
u.id = data.id
u.email = data.email
# more user attributes set...
u.save!
end
# required to prevent Postgres from trying to use now taken user ids
ActiveRecord::Base.connection.execute "ALTER SEQUENCE users_id_seq RESTART WITH #{User.last.id+1};"
So first we read user data from a data source, and set it's id manually. We need to preserve ids since we are migrating associated data as well.
Then later on, we need to create more users conditionally from the data of an associated object.
# Create a user for this email if no user with this email exists.
if data.email
user = User.find_by_email(data.email)
if user
o.user = user
else
o.user = User.create!(
first_name: 'Unknown',
last_name: 'Unknown',
email: data.email,
password: generate_temp_password
)
end
end
This fails at User.create! with:
Validation failed: Email has already been taken (ActiveRecord::RecordInvalid)
I've debugged this a bit and can see that User.where(email: data.email).first is nil right before this error is thrown. I suspect this has something to with setting ids beyond the current auto increment value, somehow causing the new records to be invisible in my queries, but visible to Postgres own validations.
So how can a user with a specific email not be present, but still trigger DB validation errors?

Apparently, Devise downcases email addresses. And the offending email had some caps in it. So it missed a case sensitive check, and then failed as a dupe when case insensitive.
Devise outsmarted me it seems.

Related

Why doesn't FactoryBot save passwords in the test database?

I have just run into a situation where I had to tack down why my test user could not login in a system test.
It turns out that the password word for the user was nil.
I ran binding.pry after a user is created:
it 'some tests do
user = create(:user)
binding.pry
end
user.password = '12345' # correct
User.last.password = nil # wtf
user.email = 'joe#example.com' #correct
User.last.email = 'joe#example.com' #correct
Does anyone know why passwords are not persisted into the database with FactoryBot?
The reason User.last.password is nil is because the plain text password is encrypted and not accessible. Check your schema.rb file...you should only see an encrypted_password column (I'm assuming you are using Devise).
To check if the User is persisted just check user.persisted?, user.errors, or something of the sort to figure out whats going on.

Hartl's rails tutorial 6.3.4 exercise 2, why doesn't the new assigned name save?

In this section, I have created a user in our data and the exercise says to change the name of the user by assigning a new name, and then, save the change with the save method.
I did this, and it returned false. Hartl asks why it didn't work, and I am not sure why. I tried to authenticate the user in the console with:
user.authenticate("foobar")
And then tried changing the name, but user.save still returned false.
Edit:
I changed the name by assigning a new name
user.name = "Harry Caray"
but I beleive I should use user.update_attribute(:name, "Big Lebowski")
user.save still returns false, but upon a reload the name is saved.
How to update the user's name
Starting with a new rails console, assuming you already have a user in the database, we can assign him to a local variable like this:
> user = User.find_by(email: "mhartl#example.com")
Now we have a User object in memory that is a copy of the user in the database. We can confirm this with:
> user
=> #<User id: 1, name: "Michael Hartl", email: "mhartl#example.com", created_at: "2018-08-16 05:57:13", updated_at: "2018-08-16 06:15:04", password_digest: "$2a$10$bS2s/24d3ePLscZ8iT/NheDAvOZmODKjbCV5groq4v5...">
Now we change our user object's name:
> user.name = "Ace Ventura"
=> "Ace Venutura"
However, if we try to save this user, it fails:
> user.save
=> false
We can then examine the error message:
> user.errors.messages
=> {:password=>["can't be blank", "is too short (minimum is 6 characters)"]}
In exercise 2 of section 6.2.4 in Hartl's book, he asks us why calling .save didn't work. The reason it didn't work is because the validations are failing. In the user model at app/models/user.rb, we specifically state that in order for a user to be added to the database, a validation check should be performed to ensure that the user object has a password string of at least 6 characters in length. If we check our user object for a password string, we get this result:
> user.password
=> nil
All we need to do then is enter in the password we supplied earlier in the chapter, and we will be able to call .save:
> user.password = "foobar"
=> "foobar"
> user.save
=> true
Now we can check to make sure that the database was updated correctly:
> User.find_by(email: "mhartl#example.com").name
=> "Ace Ventura"
What was the "technique" Hartl was talking about?
The method I gave above for updating a database entry is not the "technique" Hartl was referring to in question 3. I provided that section to explain what is going on with the validations. When Hartl mentioned a "technique", he was implying that you should do this trick:
> user.update_attribute(:name, "El Duderino")
=> true
(Note we are using update_attribute here, not the plural version update_attributes.) As Hartl explains in section 6.1.5, when we need to update only a single attribute and skip validations at the same time we should use update_attribute.
So what does the authenticate method do?
In a simple sense, all the .authenticate method does is answer the question: "If I provided this password to the user object, will I be able to save the object to the database?" So when we do:
> user.authenticate("not_the_right_password")
=> false
All authenticate is doing here is telling us that if we set user.password = "not_the_right_password", when we call .save it will fail because we have the wrong password. It doesn't actually change anything on the object itself.
What about save! ?
To cite the docs:
There are many ways to change the state of an object in the database. Some methods will trigger validations, but some will not. This means that it's possible to save an object in the database in an invalid state if you aren't careful.
The following methods trigger validations, and will save the object to the database only if the object is valid:
create
create!
save
save!
update
update!
The bang versions (e.g. save!) raise an exception if the record is invalid. The non-bang versions don't: save and update return false, and create just returns the object.
Note that save also has the ability to skip validations if passed validate:
false as an argument. This technique should be used with caution.
save(validate: false)
In summary:
save - If the model is new a record gets created in the database, otherwise the existing record gets updated. Validations run and if any of them fail the action is cancelled and save returns false.
save! - If the model is new a record gets created in the database, otherwise the existing record gets updated. Validations run and if any of them fail ActiveRecord::RecordInvalid gets raised.
save(validate: false) - Same as save but validations are bypassed altogether.
You will get to know the reason by inspecting model error for example-
user.name = "new name"
#error_object = user.save
#error_object.errors.full_messages

How to insert an already encrypted password on devise model?

I am a newbie in rails. I would like to ask if it's possible to insert an encrypted password(encrypted using devise default encryption) to devise model?
e.g:
User.csv
id,name,encrypted_password
1,dude,$2a$10$0.xEu5LvDbnmGVIqgIab8ej5o2b3eKa8KLevsg5bxIX0SHSOl5gye
I want to read this csv file and then insert the data to User model.
But I realized that you can't insert the already encrypted password normally on encrypted_password of User model. I'm thinking to decrypt the password but they say that it is bad for the system's security.
Update: It can now insert an encrypted password but the inserted password is different to the one on the csv since the devise automatically encrypts the password before inserting it to the database. Is there a way for devise model to detect if the password is already encrypted before inserting it to the database?
yes, you can set the encrypted_password value directly:
u = User.find(1)
u.update_attribute(:encrypted_password, "$2a$10$0.xEu5LvDbnmGVIqgIab8ej5o2b3eKa8KLevsg5bxIX0SHSOl5gye")
But you should make sure that you're not setting 'password', if you do this won't work as it'll encrypt it again, so
u = User.new
u.password = 'foo'
u.password_confirmation = 'foo'
u.encrypted_password = "$2a$10$0.xEu5LvDbnmGVIqgIab8ej5o2b3eKa8KLevsg5bxIX0SHSOl5gye" # this line will be ignored
u.save
u = User.new
u.encrypted_password = "$2a$10$0.xEu5LvDbnmGVIqgIab8ej5o2b3eKa8KLevsg5bxIX0SHSOl5gye" # this line will now work
u.save

How to update password attribute with has_secure_password?

I used has_secure_password for the User model. Now I am trying to use AJAX to update some of the user's attributes, including password. However, it looks like with has_secure_password, the password attribute no longer exists, replaced by password_digest. So when I am trying to do
user[:password] = "The password passed by AJAX"
user.save!
I got:
ActiveModel::MissingAttributeError (can't write unknown attribute
password)
The question is: What is the right way to update a user's password in this situation? Do I need to manually compute the hash and update the password_digest?
EDIT:
I am using Rails 4.2.1
Normally you just use:
user = User.find 1
user.password = 'Test123456789'
user.save
But, it sounds like you have not added a password_digest column to the users table or have not run the migrations.

Devise - create user account with confirmed without sending out an email?

I integrated devise with facebook. Now when I create a user account after the user has logged in with his/her facebook account,
user = User.create(:email => data["email"],
:password => Devise.friendly_token[0,20])
user.confirmed_at = DateTime.now
user.save!
even though the account has been confirmed, an confirmation email is still fired. Any idea how I can turn the email firing off?
The confirm callback happens after create, so it's happening on line 1 of your example, before you set confirmed_at manually.
As per the comments, the most correct thing to do would be to use the method provided for this purpose, #skip_confirmation!. Setting confirmed_at manually will work, but it circumvents the provided API, which is something which should be avoided when possible.
So, something like:
user = User.new(user_attrs)
user.skip_confirmation!
user.save!
Original answer:
If you pass the confirmed_at along with your create arguments, the mail should not be sent, as the test of whether or not an account is already "confirmed" is to look at whether or not that date is set.
User.create(
:email => data['email'],
:password => Devise.friendly_token[0,20],
:confirmed_at => DateTime.now
)
That, or just use new instead of create to build your user record.
If you just want to prevent sending the email, you can use #skip_confirmation_notification, like so:
user = User.new(your, args)
user.skip_confirmation_notification!
user.save!
See documentation
Skips sending the confirmation/reconfirmation notification email
after_create/after_update. Unlike #skip_confirmation!, record still
requires confirmation.
Open up the Rails console
rails c
Note the user (through id) or using rails helper methods, eg. first, last.
Create a variable to hold the user.
user = User.last
Use the skip_confirmation helper to confirm the user, then save.
user.skip_confirmation
user.save

Resources