I'm trying to run this migration, and the goal is to not have any server downtime while adding new columns. Since these columns have no relationships to other tables, exist in the code, or are required, I assumed this would be possible. But, after running the migration I observed UnknownAttributeError's being thrown when instantiating a new instance of the model.
The error:
Controller/existing_table/some_method::ActiveRecord::UnknownAttributeError
/code/app/models/existing_table.rb:40:in `new’
The line from the error:
e = ExistingTable.new(existing_table.attributes.except("id"))
The migration:
class AddTestFieldToExistingTable < ActiveRecord::Migration
def change
add_column :existing_table, :test_field, :integer
end
end
When I go to recreate the issue by opening up a console and executing lines similar as above, I don't get the error. My guess is that there is some attributes cached and we do need to have downtime while running these "passive" migrations.
I wonder if the 'existing_table' is still in memory, doesn't have the 'test_field' attribute, and when creating a new instance of ExistingTable it doesn't know how to assign the field. Since I cannot recreate it without starting a new instance, I would be guessing on an alternative solution of re-writing the ExistingTable constructor.
Is there a way to avoid restarting the rails instance or re-writing this code? Am I on the right track?
I tried to replicate your doubt, and I did not get the error, besides, everything worked as expected.
I am using Rails 7.0.1
First, I started puma:
rails s -b 0.0.0.0
Then create the migration:
rails g migration AddPassiveToGiro passive:boolean
The migration looks like this:
class AddPassiveToGiro < ActiveRecord::Migration[7.0]
def change
add_column :giros, :passive, :boolean
end
end
On rails console I created a new record:
Giro.create giro: 'delete me', passive: true
Then I modify the view where I list those records (using haml)
.list-item-description
= giro.passive.blank? ? 'not passive' : 'is passive'
And everything worked fine, no errors.
Rails load the tables' columns only once when the Rails server is started.
Depending on your setup and how you run your migration at deployment it might be possible that the server or at least one server instance started before the migration was done. Then that server instance will not see the newly added column until the next server restart.
To be safe you need to make sure that the migration ran successfully before you deploy the code changes using that change. Depending on your server setup that might mean that you need two different deployments: One with the migration and then later another with the code changes.
Related
I am upgrading an app from 6.0 to 6.1, using the next_rails dual boot gem. As part of the upgrade I needed to execute rails active_storage:update which adds a service_name to the ActiveStorage::Blob table. When I run my tests with next rspec spec this works fine, but rspec spec fails with tests that use active_storage with messages like
ActiveRecord::NotNullViolation:
PG::NotNullViolation: ERROR: null value in column "service_name" violates not-null constraint
Is there a way of adding some conditional code (if Rails::VERSION::MINOR) that detects the version is 6.0 and somehow removes the null constraint for the service_name column.
There are two approaches.
Migrate down to the version before the update:
if Rails.version == "6.0.0"
ActiveRecord::Base.connection.migration_context.migrate(20220816031332)
else
ActiveRecord::Base.connection.migration_context.migrate
end
# or maybe invoke the db:migrate tasks instead
You might need to turn off pending migration check: config.active_record.migration_error = false
This approach is general and should work with any migration(s).
The second approach is to call migration helpers manually. This is specific to the problem posed in this question and just changes the columns without updating migration version in the database:
if Rails.version == "6.0.0"
ActiveRecord::Migration.change_column_null(:active_storage_blobs, :service_name, true)
else
ActiveRecord::Migration.change_column_null(:active_storage_blobs, :service_name, false)
end
The code can be placed in config/environment.rb after Rails.application.initialize!. If you wish to place it earlier, you would need to establish the connection to the database manually.
Note that both methods cause persistent changes to the database, e.g. will remain after the tests have run. The changes only affect the database of which ever environment the code is run in e.g. test or development.
I can't figure out why my migration worked but doesn't show up in corresponding model...
Ran a migration to add field to a table:
def up
add_column :quick_tests, :trace_route_data, :text, :null => true
end
def down
remove_column :quick_tests, :trace_route_data
end
Looked in schema.rb, and it's there.
Added :trace_route_data to attr_accessible in QuickTest model
Opened up rails console by doing bundle exec rails c, ran QuickTest.new and I can see the :trace_route_data field.
But, when I execute the same QuickTest.new statement when paused in the QuickTestController#show method, the field isn't there.
Why is this field showing up in the rails console but not my actual app??
I suspect something went wrong during the migration such that it never got applied to the database. Rails uses the actual schema in the database to build model objects, so the fact that your model doesn't show the change means the change is almost certainly not in the database (despite being in the schema.rb).
I'd recommend you rollback your migration and run it again in verbose mode. This should either show you the error, or at least show you the SQL being run. In that case, you can run the SQL manually at the db console and see what happens then.
I'm trying to update my heroku database. The problem: I created an old model & table that we no longer need. That table had previously been migrated to a production server (along with other changes). I did a destroy of the model using:
rails destroy model ReallyLongModelName
That also deleted the migration that created the table.
Later, I created a migration to drop that table.
class Drop_ReallyLongTableName < ActiveRecord::Migration
def change
drop_table :really_long_table_name
end
end
I'm now getting a couple of errors.
First error:
When trying to migrate the database to the production version of the application, I get this error.
Input string is longer than NAMEDATALEN-1 (63)
I'm not sure how to go back and edit the name to avoid the long name, so that it clears
Second error:
When trying to rollback the Drop_ReallyLongTableName migration, its aborts the rake because
rake aborted!
An error has occurred, this and all later migrations canceled:
SQLite3::SQLException: no such table: really_long_table_name
Does anyone have any ideas on how to solve this? Thanks!
Have you tried running this from your Terminal/console:
heroku run console
ActiveRecord::Migration.drop_table(:table_name)
The migration is the way to go but this might be worth a try to see if the table even exists any more (since your second error message seems to indicate that it is no longer there).
Also, and for what it's worth, you can add conditional logic to your DB migrations should you need to in the future
def change
drop_table :table_name if self.table_exists?("table_name")
end
I am currently working on a rails app where we are using mongoid/mongoDB on the back-end. I understand that I don't need ActiveRecord like migration to migrate the schema, but I do need to migrate data as I change mongoid model definitions. Is anyone else out there running into the same scenario, if so how are you handling it?
Even though you're not making schema changes, you may need to move data between fields, or remove fields that are no longer used in the codebase. It's nice to have migrations that you can run when you deploy new code. I recommend using a gem called mongoid_rails_migrations. This provides you with migration generators like you're used to and provides some organization to migrating data.
class MyMigration < Mongoid::Migration
def self.up
MyModel.all.each do |model|
# label was renamed to name
model.set :name, model[:label] # copy the data from the old field to the new one
model.remove_attribute :label # remove the old field from the document
model.save!
end
end
end
Write a custom rake task to migrate the data as needed
This question addresses the same issue of creating custom migrations in a mongoid setup.
Runtime changing model with mongodb/mongoid
I had the some scenario recently, where I have to do some data migration only once (basically update dirty data);
So what I did have a mongoid migrations in /db/migrate/ and override the db:migrate task so that it creates a collection in mongo db of that app itself, say "migrations", that record the migration that got fired, with that, none of the migration will run again, and you can keep adding migrations with some hierarchy (if in case migration is interdependent).
I'm fairly new to Ruby on Rails here.
I have 2 migrate files that were provided. The first one, prefixed with 001, creates a table and some columns for that table. The next migrate file, prefixed with 002, inserts rows into the table created in file 001.
Running the migration (rake db:migrate in command line) correctly creates the table but doesn't insert any of the data which is the problem. The code from the insertion looks like this (except with a lot more Student.create statements,
class AddStudentData < ActiveRecord::Migration
def self.up
...
Student.create(:name => "Yhi, Manfredo", :gender => "M")
...
end
def self.down
Student.delete_all
end
end
My understanding is that Student is a model object, so my Student model looks like this,
class Student < ActiveRecord::Base
end
Do I need to explicitly define a create method in Student or is that something that's given? (This project was made using scaffold)
Thanks.
Edit 1: I used Damien's suggestion and called create! instead of create but got the same response. Then what I did to see whether the code was even reaching that far was call this,
Student.create12312313!(:name => "foo", :gender => "M")
which is obviously invalid code and the migrate didn't throw any error.
Edit2: Answer found. The schema_migrations table had its version set to 3, and I only had 3 different migration files so it never ran any of the migration files I had. That's why nothing was ever updating, and the bogus creates I used were never throwing errors. The reason the student data wasn't inserted the first time was because a certain table was already in the database and it caused a conflict the first time I migrated. So what I was really looking for wasn't db:migrate but rather db:reset Several hours well spent.
The create method is inherited from ActiveRecord::Base.
So no, you don't need to define it.
One reason why your datas could not be included would be that you have validations that doesn't pass.
You can easily see the error making your datas not being included by using create! instead of create.
So if the model can't be created, an exception will be thrown and the migrations will fail.
You may want to look at Data Seeding in rails 2.3.4. And is your rails migrations really running 001_create_whatever.rb? or were you just using that as an example? since 2.2.2 (iirc) migrations have been using timestamps such as 10092009....create_whatever.rb
How old is your rails version?
The migrations won't run if their schema number is in the database.
For older versions of rails, there will be a single row with the highest migration performed in it.
For newer versions, every migration gets a unique time-stamp as its version number, and its own row in schema_migrations when it gets added.