Model.reset_column_information does not reload columns in rails migration - ruby-on-rails

I'm using Rails 3.2 and have a migration that contains the code:
add_column :users, :gift_aid, :integer, :default => 2
# reset columns
User.reset_column_information
... code here to load legacy data from sqlite3 database ...
# now create a user with the loaded column data
user = User.create( ...other cols...,
:gift_aid => migrated_gift_aid_column_data,
...other cols... )
and I get unknown attribute: gift_aid when running the migration. User.column_names shows the same list before and after the call to reset_column_information.
Oddly when I manually drop the column in mysql and re-run the migration it works as expected. Starting from the first migration again with an empty database and it doesn't work so it's something to do with running all the migrations rather than the single one.
I have a couple of previous migrations on User model, both include reset_column_information and both work fine.
I'm really scratching my head on this one - anyone got any ideas

I think this must be some kind of bug related to schema caching... this might work:
User.connection.schema_cache.clear!
User.reset_column_information
(for Rails 3.2.2)

this isn't needed on rails 6 (tested on 6.0.0beta3).
I tried both with update!(new_column: ...) and update_all(new_column: ...)

Related

Rails: How to modify data using migrations due to change in the schema

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.

rails test error:"table projectname_test.destroys doesn't exist"

I just started to learn Ruby on Rails. And when I used the rails to do the test, an error occurred as:
UserTest#test_the_truth:
ActiveRecord::StatementInvalid: Mysql2::Error: Table 'eula_test.destroys' doesn't exist: SHOW FULL FIELDS FROM destroys
I don't know what is eula_test.destroys and how should I solve this problem.
check your migration files. on db/migrate.rb and db/shema.rb you can check out if you have that table.
Seems no-one has been able to answer this, but best guess based on the error you are getting it looks like you need a join table because you most likely have a "has_and_belongs_to_many" directive in both your models eula.rb and test.rb
So you need to create a migration: rails g migration CreateEulaTest
then edit the migration to include an index that tells the db to look in eula_test table for the data to join with.
you need edit your migration with the following:
def up
create_table :eula_test, :id => false do |t|
t.integer :eula_id
t.integer :test_id
end
add_index(:eula_test, [:eula_id, :test_id]
end
def down
drop_table :eula_test
end
end
Note: this table does not require it's own ID hence the :id => false
Also, the add_index portion are your foreign keys between the 2 models that allows the database to know that you are going through the table to establish your directive, such as create and destroy etc.
run rails db:migrate for rails 5 or greater, rake db:migrate <5
You may need to check your models to ensure you have the correct has_to_and_belongs_to_many settings.
It is likely you missed this part of the tutorial. Some tutorials don't explain why things are done, they just show you the code and let you figure it out.
rails db:reset should fix it :)

how i can i check the existence of the table

hy
A lot of time when i run rake db:migrate i get an error because the table user or foor or bar exist.
I try to check the existence of table, but that didn't work and i dont know why .
I use rails 3.2.2
class AddDeviseToUsers < ActiveRecord::Migration
#def self.table_exists?(users)
# ActiveRecord::Base.connection.tables.include?(users)
#end
if !table_exists?("users")
def change
create_table(:users) do |t|
## Database authenticatable
t.string :email, :null => false, :default => ""
t.string :encrypted_password, :null => false, :default => ""
end
end
end
You can check the existence of a table with the following command. This question has already been posted and answered here.
ActiveRecord::Base.connection.table_exists? 'users'
However, your code contains errors. You cannot define a method inside an if block with the syntax you're using. Put the block inside your method, instead.
def change
if !table_exists?("users")
# ...
end
end
Also, you should not be getting this kind of errors often, as you state. Migrations are applied sequentially. Creating your users table in a separate migration before adding Devise to it would take care of that problem. If you run into this problem while migrating an existing project from scratch, consider using rake db:schema:load instead.
In your command terminal type in
sqlite3 db/development.sqlite3
then you can search through your database by SQL command. Use .tables to list all tables in the database and you can see if yours is missing or not.
You can also just look at your schema file to see if the migration worked. The table along with all of it's attributes will be listed there.
You should never have multiple migrations that both try to create the same table. You also should not experience errors like this if you're working with rails migrations correctly.
The way rails migrations work is that every time you run rake db:migrate, for every migration file that runs successfully, it stores the number part of the migration file 123456789_create_users_table.rb (123456789 in this case) in a table in your db called schema_migrations. This way if a migration has already been run, it will never get run again.
It doesn't sound like you're working with rails migrations correctly. I suggest reading this thoroughly: http://guides.rubyonrails.org/migrations.html

Alter Schema in Rails 2

I need to add some columns to a table in my schema. Can someone tell me the best way to do this?
The following seems incomplete or wrong since the schema.rb file did not update to include the new column and all of the corresponding view files (edit,index,new,show) did not update to include the new column. Not to mention the bloat of all of those migration classes that get generated. Thanks
ruby script/generate migration RecordLabelToAlbums record_label:string
exists db/migrate
create db/migrate/20121130125859_record_label_to_albums.rb
Creates this:
class RecordLabelToAlbums < ActiveRecord::Migration
def self.up
end
def self.down
end
end
I then added this:
class RecordLabelToAlbums < ActiveRecord::Migration
def self.up
add_column :albums, :record_label, :text
end
def self.down
remove_column :albums, :record_label
end
end
The I ran:
rake db:migrate
Got This:
Mysql::Error: Table 'albums' already exists: CREATE TABLE albums (id int(11) DEFAULT NULL auto_increment PRIMARY KEY, created_at datetime, updated_at datetime)
The code you added is correct.
The error suggests that for some reason your system appears to think it has not yet run the original migration that created the albums table. The state of migrations (in Rails 2) is specified in a table in the database called schema_migrations -- if this gets confused then it will try to re-run migrations. I am not sure what might cause it to get confused, but I do recall this happened a couple times back in 2008 when I was using Rails 2.x.
The table is simple -- you can see what's in it from a SQL prompt -- just the names of migrations it thinks it has run, I think.
If you don't mind losing some data, you can try rake db:rollback or even rake db:reset to get back to the beginning. rake db:rollback STEP=2 will rollback the last 2 migrations.
If you need the data, correct the contents of the table by adding one or more new records referencing the migrations in app/db/migrations that may have been missed. The order is important, I think (the format changed a little in Rails 3, I don't recall how).
Any time you want to add or change the database schema, use rails to generate a migration, and then run rake db:migrate once it's ready to go.
And just asking: is there any way you can move to Rails 3. It's been out for years now, and Rails 4 is coming soon. You'll find yourself in a backwater of incompatibilities, deprecations, security and performance issues and so on if you don't take the hit and upgrade.

Can Rails Migrations be used to convert data?

I'm trying to convert a column in my Rails app, for arguments sake let's pretend I'm trying to change the age column in my users table to a string representation rather than an int.
In my migration I have this;
def.self up
add_column :users, :age_text, :string
users = User.find(:all)
users.each do |u|
u.age_text = convert_to_text(u.age)
u.save
end
end
def self.convert_to_text(number)
#code here to convert 1 to 'one' etc
end
But it doesn't seem to be working, is what I'm attempting here even possible with migrations?
What you're trying to do is possible, and I would say the correct thing to do.
You need, though, to reload the column info for the model classes you're updating in the migration, so that Rails knows about the new columns. Try this:
def.self up
add_column :users, :age_text, :string
User.reset_column_information
users = User.find(:all)
users.each do |u|
u.age_text = convert_to_text(u.age)
u.save
end
end
On a separate note, please note that if your table is large, doing updates one by one will take a looong time.. Be careful with that.
Since I'm new here I can't comment on the above so I'll add my own answer.
GENERALLY manipulating data in migrations is a BAD idea. Migrations with direct model access can get stuck if the model logic changes.
Imagine in your second migration you've added a new column. You want to seed that column with new data.
Let's also say a few weeks later you add a new validation to the model - a validation that operates on a field that does not yet exist in your second migration. if you ever were to construct the database from migration 0, you'd have some problems.
I strongly suggest using migrations to alter columns and other means to manage database data, especially when moving to production.
Here is an example migration I ran to convert data. You can easily convert it to use integers instead of strings. Making the conversion in SQL is much faster than loading each row in Rails.
class ConvertCommentTextToText < ActiveRecord::Migration
def up
add_column :comments, :text_tmp, :text
# copy text column
execute <<-SQL
update comments set text_tmp = text
SQL
remove_column :comments, :text
rename_column :comments, :text_tmp, :text
end
def down
add_column :comments, :text_tmp, :string
# copy text column
execute <<-SQL
update comments set text_tmp = text
SQL
remove_column :comments, :text
rename_column :comments, :text_tmp, :text
end
end
And to test it:
rake db:migrate
rake db:rollback
rake db:migrate
I would say that if you can "undo" the imported data when rolling back the migration version, then it's appropriate to put imports into the migration.
For example, I have a migration which sets up a lot of lookup tables and other meta-data. The data for these tables are populated during this phase. As the data for these lookup tables changes, I create new YAML files storing the meta-data and load those files in subsequent migrations (and un-do those YAMLS, re-loading the previous YAML file when backing out of a migration version). This is pretty clean. I have files (in different well-defined folders in my case) with these files:
002_setup_meta_data.rb
002_meta_data.yaml
007_change_meta_data.rb
007_meta_data.yaml
If you're importing "production" data from another system into transactional (non-static) tables, then I would say using migrations is not appropriate. Then I would follow Brian Hogan's advice of using rake tasks.

Resources