How to change structure of existing columns, based on old values during migration? - ruby-on-rails

During development, structure of column has been changed, so it's needed to adopt old users data to new format in production. It looks like issue that can be solved by migration. The problem is, I'm not an experienced ruby specialist, so it would be great to have advice how to implement it.
To make things clear, I'll give an example of what happened in my project.
There is table users. This table contains next columns,
id
user_type
description
description here is just JSON string that looks like that in old implementation,
first_name
last_name
address
After changes, instead of first_name and last_name we have full_name, only for users with type 'customer'.
So, how can I migrate my old data to new format? Thanks.

Your respective model User must have following,
serialize :description, Hash
Try to write rake in below path,
lib/tasks/update_users.rake
namespace :update_users do
desc 'Update description for full name for all user'
task update_description: :environment do
User.all.each do |user|
user.description[:full_name] = user.description.delete(:first_name) + ' ' + user.description.delete(:last_name)
user.save(validate: false)
end
end
end
And run rake as, rake update_users:update_description
Perhaps you can run code through rails console,
User.all.each do |user|
user.description[:full_name] = user.description.delete(:first_name) + ' ' + user.description.delete(:last_name)
user.save(validate: false)
end

Related

PaperTrial versions handling changeset error

here are my models looks like so I can address properly my issue
class Product
has_paper_trail
belongs_to :category
end
On my HTML side, I need to load all the logs using the PaperTrial versions. like this
#product.versions.each |version|
version.changeset.each do |k, v|
- if k.to_s == "category_id"
- old_record = v[0].blank? ? " No Record " : Category.find(v[0].to_i).name
- new_record = v[1].blank? ? " No Record " : Category.find(v[1].to_i).name
= "Category" + " From: " + "#{old_record} " + " To: " + "#{new_record}"
in my HTML looks fine but. there is a scenario. that category will be deleted. and I will get an error in this view. because of my "Category.find". the find method cant find the Category that already deleted.
is there a way to store the name of the category and not the ID. so I can get rid of using "find"
or there is a better way to implement these things?
I'd recommend adding category_name attribute to your product model for caching.
# migration
rails g migration AddCategoryNameToProducts category_name
# Product model
before_validation :cache_category_name
def cache_category_name
self.category_name = category&.name if category_name != category&.name
end
But if you had already gone production, you must fill in the data first. And you are not able to fill data, that is already deleted.
# console or migration
Category.find_each(&:save!)
This scenario preserves the deleted category name, additionally it will be much more efficient than a lot of Category.find.
Or you can do some kind of soft-delete as others suggested.

find_or_create by, if found, does it update?

https://apidock.com/rails/v4.0.2/ActiveRecord/Relation/find_or_create_by
After reading the docs, it does say: "find the first user named "Penélope" or create a new one." and "We already have one so the existing record will be returned."
But I do want to be 100% clear about this .
If I do:
User.find_or_create_by(first_name: 'Scarlett') do |user|
user.last_name = 'Johansson'
end
and User does exist with both first_name: 'Scarlett' and `last_name: 'Johansson'``, will it update it or completely ignore it?
In my case, I would like to completely ignore it if it exists at all and wondering if find_or_create is the way to go. Because I don't want to bother updating records with the same information. I am not trying to return anything either.
Should I be using find_or_create, or just use exists?
Also, if find_or_create does act as a way to check if it exists and would ignore if it does, would I be able to use it that way?
For example:
User.find_or_create_by(first_name: 'Scarlett') do |user|
user.last_name = 'Johansson'
puts "Hello" #if it doesn't exist
end
Would "Hello" puts if it doesn't exist and not puts if it does?
In the example, if you have one or more User records with the first name 'Scarlett', then find_or_create_by will return one of those records (using a LIMIT 1 query). Your code, as provided, will set - but not save - the last_name of that record to 'Johansson'.
If you do not have one or more records with the first name 'Scarlett', then a record will be created and the field first_name will have the value 'Scarlett'. Again, the last_name field will be set to 'Johansson', but will not be saved (in the code you provide; you might save it elsewhere).
In this code:
User.find_or_create_by(first_name: 'Scarlett') do |user|
user.last_name = 'Johansson'
puts "Hello" #if it doesn't exist
end
...you will always see "Hello" because find_or_create_by will always return a record (either a found one or a created one).

How to update rails db records

I have a rails app. I have a table User and a column Number which is a string. Some users saved their phone number with spaces (for example 1234 1234) and now I want to remove the space from their phone numbers.
I tried this but it didn't work:
space = " "
phones = User.where("number like ?", "%#{space}%").pluck(:number)
phones.each do |phone|
phone = phone.gsub(/\s+/, "")
phone.save
end
I got the error NoMethodError: undefined method 'save' How can I do this properly?
You need to have the user object to save it. Read inline comments below
space = " "
users = User.where("number like ?", "%#{space}%") # collect users with number having space character here.
# then iterate on those users
users.each do |user|
user.number = user.number.gsub(/\s+/, "") # notice here, changing the phone number of that user
user.save # and saving that user with the updated `number`
end
You pluck data from User table. Thus, phones variable contains a number array not USER objects. You can't use save on a array element. That's why the error occurs.
You can do the following:
space = " "
phones = User.where("number like ?", "%#{space}%")
phones.each do |phone|
phone.number = phone.number.gsub(/\s+/, "")
phone.save
end
The way you could do is create a rake task to update the existing records on the system.
namespace :update do
desc 'Strip space from existing numbers from Users'
task(:number => ::environment) do
space = ' '
numbers_with_space = User.where("number like ?", "%#{space}%")
numbers_with_space.each do |a|
a.number = a.number.gsub!(/\s+/, '')
a.save(validate: false) # You would like to use
# validate false in order
# to stop other validation from updating the record.
end
end
Then execute the rake task.
bundle exec rake update:number
Another way to handle this beforehand can be through reformatting number during validation. This way you'll not need to run the rake task or code to reformat and save when new data are entered in app.
class User < ApplicationRecord
before_validation :reformat_number, on: [:create, :update]
private
def reformat_number
self.number.gsub!(/\s+/, '')
end
end

How to populate empty attributes with random values from seeds.rb

I've generated User model with attributes: first_name, last_name, city. Then, I've created 200 instances of the class using seed.rb and gem 'faker'. After all, I have added one more attribute to this model - age:string, so now in every instance of the class the age = 'nil'. Now, I want to populate every single user with randomly generated number from range 10..99.
Here is my code from seed.rb:
users = User.all
users.each do |element|
element.age = rand(10..99).to_s
end
When I type rake db:seed it seems to be successfull but when I check the database, each age:string is still 'nil'. Do you have any idea what might have gone wrong?
You are assigning the value, but you don't save it. You should change your code to
users = User.all
users.each do |element|
element.age = rand(10..99).to_s
element.save!
end
or just
users = User.all
users.each do |user|
user.update_attribute(:age, rand(10..99))
end
If you don't need to validate the record, the following is even faster
users = User.all
users.each do |user|
user.update_column(:age, rand(10..99))
end
#Simone Carletti's answer is good... maybe you'd benefit from find_each:
User.find_each do |user|
user.update_attribute :age, rand(10..99)
end
Also, you can combine Faker with Fabrication for more cool seeds.
Faker::Name.name #=> "Christophe Bartell"
Faker::Internet.email #=> "kirsten.greenholt#corkeryfisher.info"
You can use it to generate random data from db/seed.rbrunning rake db:seed.

How to update all records that were created yesterday with Rails?

I need to update the info on all records that were created yesterday, for that I created a migration with the following code:
class UpdateFbidToVotes < ActiveRecord::Migration
def up
Vote.all.each do |v|
if v.created_at == Date.today-1
v.fbid = v.fbid + 1
v.update!
end
end
end
end
This, however, doesn't work. Can you please point me to what am I doing wrong?
Thanks!
Something like this:
Vote.where(created_at: Date.yesterday).update_all('fbid = fbid + 1')
Try:
yesterday = 1.day.ago # or Time.now.yesterday
Vote.where(created_at: yesterday.beginning_of_day..yesterday.end_of_day).update_all('fbid = fbid + 1')
Also, migration files are meant for managing table schema, I'd encourage you to move such db update/delete queries to a rake task instead of migration file, such queries are not reversible(i.e. you can not rely on getting the changes back to previous state) and should not be a part of migrations.

Resources