I added two migration which in the first one I add a column to a model, and in the second one, I execute a function that stores a value into the column recently added of some rows.
The problem is that when I run rake db:migrate the second migration throws an error because the first migration was loaded but the database hasn't changed yet, so one approach is to run the command twice (it works).
First Migration :
class AddRegisteredReportToSpecialOfferUse < ActiveRecord::Migration
def up
add_column :special_offer_uses, :registered_report, :boolean, default: false
end
def down
remove_column :special_offer_uses, :registered_report
end
end
Second Migration :
class CreateReportsFromMigrations < ActiveRecord::Migration
def change
OneClass.perform
end
end
the OneClass.perform is a method that makes an update of the attribute added previously
def perform
´´´
special_offer_uses.update_attribute(:registered_report, true)
´´´
end
The error thrown :
StandardError: An error has occurred, all later migrations canceled:
undefined method `registered_report=
Note that the method undefined is the name of the attribute added previously.
I wonder if there is a way to avoid running the command twice without throwing any error.
UPDATE:
I found a solution using reset_column_information method that causes the columns to be reloaded on the next request.
Resets all the cached information about columns, which will cause them to be reloaded on the next request.
The most common usage pattern for this method is probably in a migration, when just after creating a table you want to populate it with some default values
Further information : link
Why not do it all in one migration?
class AddRegisteredReportToSpecialOfferUse < ActiveRecord::Migration
def up
add_column :special_offer_uses, :registered_report, :boolean, default: false
OneClass.perform
end
def down
remove_column :special_offer_uses, :registered_report
end
end
Related
I have following two migrations:
One, Add column contextual_page_number to transcripts table:
class AddContextualPageNumberToTranscripts < ActiveRecord::Migration[5.2]
def change
add_column :transcripts, :contextual_page_number, :integer, default: 1
end
end
Second, changing the value of the previous added column contextual_page_number based on value of another column:
class ChangePageOffsetAndContextualPageNumberOfTranscripts < ActiveRecord::Migration[5.2]
def up
Firm.all.find_in_batches do |group|
group.each do |firm|
Apartment::Tenant.switch(firm.tenant) do
Transcript.where.not(page_offset: 0).each do |transcript|
transcript.update(
contextual_page_number: ((transcript.page_offset - 1) * -1),
page_offset: 1
)
end
end
end
end
end
def down
..
end
end
After running the migration, I am getting unknown attribute contextual_page_number error.
== 20211108132509 AddContextualPageNumberToTranscripts: migrating =============
-- add_column(:transcripts, :contextual_page_number, :integer, {:default=>1}) -> 0.0095s
== 20211108132509 AddContextualPageNumberToTranscripts: migrated (0.0096s) ====
== 20220113095658 ChangePageOffsetAndContextualPageNumberOfTranscripts: migrating rails
aborted! StandardError: An error has occurred, this and all later
migrations canceled:
unknown attribute 'contextual_page_number' for Transcript.
I have even tried reset_column_information, but no luck:
Apartment::Tenant.switch(firm.tenant) do
Transcript.connection.schema_cache.clear!
Transcript.reset_column_information
..
end
Any clue would be of great help, thanks.
As mentioned in one of the answer, I tried reset_column_information just right after the add_column, but that didn't worked. Finally, SQL to the rescue..
sql_cmd = "UPDATE transcripts
SET contextual_page_number = ((page_offset - 1) * -1),
page_offset = 1
WHERE page_offset != 0"
Transcript.connection.execute(sql_cmd)
You need two migration files.
First, try running the migration and check schema.rb for the table transcripts and verify that the newly added column contextual_page_number is being added or not.
Once you are sure that your new column is added, then again create a new migration like, eg: MigrateTransriptsCloningsData, and then add the desired changes in the up block, then execute db:migrate to update the required changes.
My choice would be To add a new rake task and executing it. like bundle exec rake migrate_transcripts_data:start instead of keeping that logic in the db/migrate/your_new_migration_file, choice is yours.
reset_column_information should be the correct way to resolve this sort of problem if you want to use models in a migration. This isn't without its problems though.
I suspect the issue is that you are calling it too late somehow. Put it first thing in the up method of the second migration or after the add_column in the first migration.
I may assume that the issue is in Apartment.tenant_names.
In the second migration, you are switching tenants by Apartment::Tenant.switch(firm.tenant), but I do not see similar in the first migrations. Probably tenant names are in DB, not in configs.
I am pretty sure that you may find samples of the appropriate add_column in your previous migrations.
Do not use structure migrations to modify data.
Use rake tasks, or data-migrate gem instead.
Also, do not use automatic data migrations, if you not ensure, that it working as expected on production server.
Always store data before modifications and write modification rollback code.
I generated a bad migration and the file looked like this
class AddActivexToMat < ActiveRecord::Migration
def change
add_column :mats, :is_active, :boolean
add_column :mats, :default, :true
end
end
Without checking the migration file, I ran rake db:migrate and now my schema reads
# Could not dump table "mats" because of following NoMethodError
#undefined method `[]' for nil:NilClass
I tried to remove columns but getting the following errors
StandardError: An error has occurred, this and all later migrations canceled:
undefined method to_sym' for nil:NilClass/Users/jhorsch/.rvm/gems/ruby-2.0.0-p247#global/gems/activerecord-4.0.2/lib/active_record/connection_adapters/abstract/schema_definitions.rb:215:incolumn'
I don't think changing the columns will work either and I am afraid to drop the table. I don't think I can just drop a table and add a new one with the same table name. In this case the table name was 'Mats'
Luckily this table only had one previous column 'name'
Any ideas how to fix this?
Seems like you specify incorrect column type here:
add_column :mats, :default, :true
:true is invalid column type.
I have a migration that creates a table, and I need to access the model for that table to create another table. The migration seems to not recognized that the original table has been created, so to make sure I put a debugger in my code and when I get to the model call, it says User(Table doesn't exist) even though in mysql I see it being created.
Looks like migrations can't see the current state of the database, any ideas how to get around that?
Just to be more specific about my question:
I'm trying to use Archivist to create archive of my current User table so I have
class ArchivedHeuristicReviewsTable < ActiveRecord::Migration
def self.up
create_table "users" do |t|
t.string "name"
...
end
debugger
Archivist.update User
end
def self.down
drop_table :users
drop_table :archived_users
end
end
the Archivist, doesn't create the archived_user table, so when I stopped at debugger and did User, I got User(Table doesn't exist).
I even tried Archivist call in a newer migration, so to make sure User creation is all done, but it still didn't recognize the user table.
Any ideas?
This should do the trick:
User.connection.schema_cache.clear!
User.reset_column_information
I've got a problem trying to rollback one of my migration. It seems as if Rails is generating a temporary table for the migration, with temporary indices. My actual index on this table is less than 64 characters, but whenever Rails tries to create a temporary index for it, it turns into a name longer than 64 characters, and throws an error.
Here's my simple migration:
class AddColumnNameToPrices < ActiveRecord::Migration
def self.up
add_column :prices, :column_name, :decimal
end
def self.down
remove_column :prices, :column_name
end
end
Here's the error I'm getting:
== AddColumnNameToPrices: reverting ============================================
-- remove_column(:prices, :column_name)
rake aborted!
An error has occurred, this and all later migrations canceled:
Index name 'temp_index_altered_prices_on_column_and_other_column_and_third_column' on table 'altered_prices' is too long; the limit is 64 characters
I've changed the column names, but the example is still there. I can just make my change in a second migration, but that still means I can't rollback migrations on this table. I can rename the index in a new migration, but that still locks me out of this single migration.
Does anyone have ideas on how to get around this problem?
It looks like your database schema actually has index called prices_on_column_and_other_column_and_third_column. You have probably defined the index in your previous play with migrations. But than just removed index definition from migrations.
If it is true you have 2 options:
The simplier one (works if you code is not in production). You can
recreate database from scratch using migrations (not from
db/schema.rb) by calling rake db:drop db:create db:migrate. Make sure that you do not create this index with long name in other migration files. If you do, add :name => 'short_index_name' options to add_index call to make rails generate shorter name for the index.
If you experience this problem on a production database it is a bit more complicated. You might need to manually drop the index from the database console.
Had this problem today and fixed it by changing the migration to include the dropping and adding of the index causing the long name issue. This way the alteration is not tracked while I am altering the column type (that is where the really long name is caused)
I added the following:
class FixBadColumnTypeInNotifications < ActiveRecord::Migration
def change
# replace string ID with integer so it is Postgres friendly
remove_index :notifications, ["notifiable_id","notifiable_type"]
change_column :notifications, :notifiable_id, :integer
# shortened index name
add_index "notifications", ["notifiable_id","notifiable_type"], :name => "notifs_on_poly_id_and_type"
end
end
An old one of my ruby on rails migrations contains both the actual migration but also an action to modify data:
class AddTypeFlagToGroup < ActiveRecord::Migration
def self.up
add_column :groups, :selection_type, :string
Group.reset_column_information
Group.transaction do
Group.all.each do |group|
group.selection_type = group.calculate_selection_type
group.save
end
end
end
def self.down
remove_column :groups, :selection_type
end
end
In this migration there are the usual add_column and remove_column migration statements. But there are also some model specific method calls.
I wrote this a couple of weeks ago. Since then, I have removed my Group model, which gives an error when I do a full migration with :reset.
rake db:migrate:reset
(in /Users/jesper/src/pet_project)
[...]
== AddTypeFlagToGroup: migrating =============================================
-- add_column(:groups, :selection_type, :string)
-> 0.0012s
rake aborted!
An error has occurred, this and all later migrations canceled:
uninitialized constant AddTypeFlagToGroup::Group
The thing is that in the current revision of my code, Group does not exist. How should I handle this "the rails way"??
I am thinking I could modify the migration by commenting out the Group.xxx stuff, but is this a wise way to go?
There is no value in leaving the group stuff in your migration now that it is gone from your project. I'd just edit the migration, drop everything from the db and migrate from scratch. There isn't even a reason to comment it out (you are using version control right?)
Also, I believe the "rails way" with migrations is spelled "Arrrrgh!"