Seeding datetime into database fails - ruby-on-rails

I am trying to seed a datetime into my PostgreSQL database:
6.times do |spots|
title = "FUN STUFF NUMBER #{spots+1}"
content = Faker::Lorem.sentence
user_id = spots + 1
spots = spots + 1
starts_at = DateTime.now + 1
Cposting.create!(
title: title,
content: content,
user_id: user_id,
spots: spots,
starts_at: starts_at
)
end
The migration looks like this, nothing special
class AddStartsAtToCpostings < ActiveRecord::Migration
def change
add_column :cpostings, :starts_at, :datetime
end
end
The problem is that starts_at after seeding is nil. Any idea why? Everything else gets inserted without problems. Also if I insert something manually in the database through the form it works, too.

Just use:
starts_at = Time.now
or,
starts_at = Time.now + 1
Update 1:
You can try this:
Change your migration to be like this:
class AddStartsAtToCpostings < ActiveRecord::Migration
def change_table
add_column :cpostings, :starts_at, :datetime
end
end
Use change_table method instead of change.
Update 2:
I just tested your migration locally. Your migration works fine and I can create objects from rails console using your migration and schema where start_at works fine if I use Time.now.

Related

When I run a schema migration before a data migration, with ActiveRecord, data does not properly update in DB

As of now, I have a users table with columns
id, name, email, status
status field is an integer type with values 1 and 2 representing an Active and Inactive user, respectively.
I would like to change the status field to a string type and migrate the data -- Convert 1 to "Active" and 2 to "Inactive"
I generated 2 migration files with rails g migration
user.rb
class User < ApplicationRecord
module Status
ACTIVE = 'Active'.freeze
INACTIVE = 'Inactive'.freeze
ALL = [ACTIVE, INACTIVE].freeze
end
validates :status, presence: true
validates :status, inclusion: Status::ALL
end
db/migrate/20190906115523_update_user_status_type.rb
def UpdateUserStatusType < ActiveRecord::Migration[5.2]
def up
change_column :users, :status, :string, default: User::Status::ACTIVE,
end
def down
User.where(status: User::Status::ACTIVE).update_all(status: 1)
User.where(status: User::Status::INACTIVE).update_all(status: 2)
change_column :users, :status, :integer, default: 1
end
end
db/migrate/20190906115828_update_user_statuses.rb
def UpdateUserStatuses < ActiveRecord::Migration[5.2]
def data
User.where(status: 1).update_all(status: User::Status::ACTIVE)
User.where(status: 2).update_all(status: User::Status::INACTIVE)
end
end
After running rails db:migrate
Expected: Each user's status should be converted to either "Active" or "Inactive" after migrations are finished.
Actual Results: Each user's status are converted to "0" of string type.
You're assuming after the first migration runs (change_column :users, :status, :string, default: User::Status::ACTIVE) you can still fetch the old values from the status column which is not the case. When you change the type of that column to string all the integer values are invalid so I suspect your database just changes all the invalid values to be "0" instead.
If I was told to make this change to an application that is heavily used in production, I would be roll out this change in a few separate pull requests/migrations. I'd create a whole new separate column, iterate through all the users, set the value of the new column depending on what the value in the old column is, and then delete the old column. This is a much safer way to make this change.

Very slow migration

My latest migration runs very slowly (600 seconds) even though it is not doing much
I have a model that contains tags in a string format separated with commas like so :
Model.tags = "TAG1, TAG2, TAG3"
I want to create a new Tag model that has a has_and_belongs_to_many relation with my model
Here is the migration
def self.up
rename_column :pizzas, :tags, :tags_migration
create_table :tags do |t|
t.string :name
t.integer :count
t.timestamps
end
create_join_table :tags, :pizzas do |t|
t.index [:tag_id, :pizza_id]
end
Pizza.all.each do |pizza|
pizza_tags = pizza.tags_migration
unless pizza_tags.empty?
pizza_tags_array = pizza_tags.split(', ')
pizza_tags_array.each do |tag|
t = Tag.find_by(name: tag)
if t.nil?
t = Tag.new
t.name = tag
t.count = 1
else
t.count = t.count + 1
end
t.pizzas << pizza
t.save
pizza.tags << t
pizza.save
end
end
puts "pizza n" + pizza.id.to_s
end
end
I don't think this code is supposed to take this long ( i have around 2000 entries )
It looks like you may be holding quite a bit in memory (due to Pizza.all). A simple performance benefit would be to change Pizza.all.each to Pizza.find_each
A good point by #codenamev about find_each
You have n+1 query in Tag.find_by(name: tag)
And also here
-
t.pizzas << pizza
t.save
pizza.tags << t
pizza.save
You can run the whole thing in a transaction to commit all the changes at once (but it may cause locks)
I'm not sure about the implementation details but the code above could be cut in half as t.pizzas << pizza will assign both models. has_many ... :through association should handle this
Also, consider moving the update part outside of migration as it will lock the database for a while

Move data from removed column to just created one in Rails migration

I have a table 'Invoices' which contains two boolean columns:
Table name: invoices
id :integer not null, primary key
...
sent :boolean default(FALSE)
payment_received :boolean default(FALSE)
This two columns define status of invoice:
def status
if sent & payment_received
:paid
elsif sent | payment_received
:sent
else
:created
end
end
One day it was desided to remove these boolean columns and create new column that will contain invoice status by the help of Rails enum
status :integer
enum status: [ :created, :sent, :paid ]
So now I need to do 3 things:
Add new column 'status'
Calculate status for existing invoices, update status column
Remove columns 'sent' and 'payment_received'.
How can I do this? I can do this task easily on my local environment, but I can't understand how can I do this on production server. If, for example, I'll create a migration that update my table and a rake task that calculate status, migration pass first and my data from boolean columns will be removed before I can use them.
Note: if somehow it's important: I use Postgres.
Any help is appreciated!
Try the following migration.
class UpdateInvoicesTable < ActiveRecord::Migration
def self.up
add_column :invoices,:status,:string
Invoice.find_in_batches(batch_size: 2000) do |invoices|
invoices.each do |invoice|
if invoice.sent & invoice.payment_received
invoice.status = 'paid'
elsif invoice.sent | invoice.payment_received
invoice.status = 'sent'
else
invoice.status = 'created'
end
invoice.save
end
end
remove_column :invoices,:sent
remove_column :invoices,:payment_received
end
end

Rails cannot write to database field

I am trying to do a very simple update on a field that does not have any validation whatsoever. However, the update always fails. Here is what the code looks like:
# model
class Event < ActiveRecord::Base
attr_accessible :start_time
..
end
# migration
class CreateEvents < ActiveRecord::Migration
def change
t.datetime :start_time
end
end
# console
Event.first.update_attribute(:start_time, "02:00")
The query that was run in the Rails log does not even include the start_time attribute!
(0.2ms) BEGIN
(4.5ms) UPDATE events SET updated_at =
'2012-07-24 19:51:33', repeat_days = '--- \n- wed\n- sat\n- sun\n',
event_date_list = '--- []\n\n' WHERE events.id = 3763
(5.5ms) COMMIT
I cannot begin to make sense of this. Can anyone help me understand the root cause of this problem?
You are a passing it a string, not a Date, Time, or Datetime object.
It looks like you just want to store the time, not the date attached. But maybe you meant to attach a date as well. If you want to store the date as well, look up the Datetime class.
If you want to store just the time (hours, minutes, and seconds), then I would suggest you change your start_time field to be an integer, and store the seconds: 2.hours or 2.hours + 4.minutes + 6.seconds.
You can convert that easily in to time again.

How to create a "collate nocase" column in a table in migration?

This answer https://stackoverflow.com/a/973785/1297371 to the question: How to set Sqlite3 to be case insensitive when string comparing?
tells how to create table with "COLLATE NOCASE" column.
My question is how to create such column in a rails migration?
i.e. how from
create table Test
(
Text_Value text collate nocase
);
create index Test_Text_Value_Index
on Test (Text_Value collate nocase);
do:
create_table :tests do
#WHAT HERE?
end
add_index #... WHAT HERE?
I added new migration where I deleted and recreated the index like:
class AddIndexToWordVariations < ActiveRecord::Migration
def change
remove_index :word_variations, :variation_word
add_index :word_variations, :variation_word, :COLLATE => :NOCASE
end
end
where 'word_variations' is my table
For people searching, if 'collate' is still not available in your version of rails (you'll get "Unknown key: :COLLATE"), then you have to create the index manually:
execute("create index Test_Text_Value_Index on Test (Text_Value collate nocase);")
In your 'def up' (the 'drop_table' will drop the index as well in your 'def down')

Resources