Rails model validators break earlier migrations - ruby-on-rails

I have a sequence of migrations in a rails app which includes the following steps:
Create basic version of the 'user' model
Create an instance of this model - there needs to be at least one initial user in my system so that you can log in and start using it
Update the 'user' model to add a new field / column.
Now I'm using "validates_inclusion_of" on this new field/column. This worked fine on my initial development machine, which already had a database with these migrations applied. However, if I go to a fresh machine and run all the migrations, step 2 fails, because validates_inclusion_of fails, because the field from migration 3 hasn't been added to the model class yet.
As a workaround, I can comment out the "validates_..." line, run the migrations, and uncomment it, but that's not nice.
Better would be to re-order my migrations so the user creation (step 2) comes last, after all columns have been added.
I'm a rails newbie though, so I thought I'd ask what the preferred way to handle this situation is :)

The easiest way to avoid this issue is to use rake db:schema:load on the second machine, instead of db:migrate. rake db:schema:load uses schema.rb to load the most current version of your schema, as opposed to migrating it up form scratch.
If you run into this issue when deploying to a production machine (where preserving data is important), you'll probably have to consolidate your migrations into a single file without conflicts.

You can declare a class with the same name inside the migration, it will override your app/models one:
class YourMigration < ActiveRecord::Migration
class User < ActiveRecord::Base; end
def self.up
# User.create(:name => 'admin')
end
end
Unfortunately, your IDE may try to autocomplete based on this class (Netbeans does) and you can't use your model logic in there (except if you duplicate it).

I'm having to do this right now. Building upon BiHi's advice, I'm loading the model manually then redefining methods where I need to.
load(File.join(RAILS_ROOT,"app/models/user.rb"))
class User < ActiveRecord::Base
def before_validation; nil; end # clear out the breaking before_validation
def column1; "hello"; end # satisfy validates_inclusion_of :column1
end

In your migration, you can save your user skipping ActiveRecord validation:
class YourMigration < ActiveRecord::Migration
def up
user = User.new(name: 'admin')
user.save(validate: false)
end
end

Related

Rails old migrations fail due to validation in model for new column

I have an application using active admin with devise in production. I try to add user_role to the table admin_users. I also want to validate if the role name is chosen when creating a new admin user. so I add validates :role_id, :presence => true in rails model.
When I run my new migrations on the old database, everything works fine. But when I try to apply the migrations from scratch, the old migration for creating admin user fails because of the validation added in a model, saying undefined method error.
Without changing the old migrations and by having the validation when creating admin users from UI, Is it possible to overcome this situation
I have faced this issue and the best solution to me seems to be to modify the model inside the migration and remove the problematic validation (for the duration of the migration) by opening the model's class.
Let's say you have a model Employee and a validation on a column firstname is interfering with a particular migration. Assuming you're using Rails 4.2, this should work:
class CreateStoreFromPreferences < ActiveRecord::Migration
def change
Employee.class_eval do
_validators.delete(:firstname)
_validate_callbacks.each do |callback|
if callback.raw_filter.respond_to? :attributes
callback.raw_filter.attributes.delete :firstname
end
end
end
# actual migration code goes here
end
end
In general, no, there is no guarantee old migrations will remain reusable as models evolve. If you need to rebuild test or development databases use rake db:schema:load Google this command and you will find several good articles.

Join table error on running migration

I have two models in rails 5.1
class Category < ActiveRecord::Base
has_and_belongs_to_many :sub_categories, join_table: "categories_join_table"
end
class SubCategory < ActiveRecord::Base
has_and_belongs_to_many :categories, join_table: "categories_join_table"
end
I have added multiple migrations the problem is when I try to run migration I get the error ERROR: relation "comfort_factor_sub_categories" does not exist because in the migration to create table comfort_factor_sub_categories will run in later migrations. How can I handle this?
Note: I can't change the name of join_table as it is just an example I have long names.
This question is over three years old at the time of this writing, but I just ran into the same problem so I thought I'd share my solution here.
In my case, I was running Rails 6.1, so my error message looked a bit different:
StandardError: An error has occurred, this and all later migrations canceled:
Could not find table 'officers_users'
The officers_users table is a join table that is created in a later migration, but the migration in question doesn't make any use of it, so why was I getting this error?
At first, I thought it might be a Rails bug as my migration was using update_columns to modify the users table, which shouldn't run any callbacks, but then I noticed that the values that I was updating them with were dependent on a computed attribute, which in turn was dependent on the officers_users join table. So Rails was right and I was wrong (once again, hah)! The solution was simply to make the failing migration self-sufficient without needing that computed attribute. Once I did that, everything was good again.
So if you run into the same problem, I would suggest checking your migration with a fine toothed comb and look for any hidden dependencies that might be using the later migration's join table.
If I understood your problem correctly, you have added several migrations and you cannot run them because some relationship is not found.
In that case, you should duplicate the classes in migrations:
class CreateCategories < ActiveRecord::Migration[5.1]
class Category < ActiveRecord::Base
# without declaring the relationship with the table which does not exist yet
end
def up
create_table :categories do |t|
# t.something
end
end
def down
drop_table :categories
end
end
You should then do the same thing for SubCategory.
For creating a proper join_table, you can refer to Rails official documentation

I gat a error due to the influence of migration that I wrote a long time ago

I want to add new column 'piece_id' and I added it to Bucket table.
class AddPieceIdToBuckets < ActiveRecord::Migration
def change
add_column :buckets, :piece_id, :string
end
end
class Bucket < ActiveRecord::Base
validates :piece_id, presence: true
end
Also I need to validate piece_id. But Adding validation gives me the following error. I tried run migration without validation and then it passed.
undefined method `piece_id'
Although I do not have piece_id in migration I wrote in old days, I think that it is the influence that writing validation in the model. And I created several objects for migration I wrote before. Should I edit it?
def change
Bucket.reset_column_information
Bucket.create(
name: 'test_name',
about: 'test',
)
end
I have been bitten by this many times, I no longer put any data changes, via models, in my migrations for this reason.
Since you can be confident that the old migration where you create a Bucket has already run you could remove that bit of code, or better still add piece_id to the attributes and give it a value so it passes when migrating from an empty database.
I now use rake db:seeds to create data, in db/seeds.rb I have:
require_relative "seeds/#{Rails.env}/all"
and a directory structure such as:
db/seeds/development/all.rb
db/seeds/production/all.rb
In development/all.rb I do require_relative '../production/all'.
I run rake db:seed as part of deployment, this works well, just have sure that the seeds are idempotent, so check the record does not exist already, or use active record methods like find_or_create_by.

Migrating DATA - not just schema, Rails

Sometimes, data migrations are required. As time passes, code changes and migrations using your domain model are no longer valid and migrations fail. What are the best practices for migrating data?
I tried make an example to clarify the problem:
Consider this. You have a migration
class ChangeFromPartnerAppliedToAppliedAt < ActiveRecord::Migration
def up
User.all.each do |user|
user.applied_at = user.partner_application_at
user.save
end
end
this runs perfectly fine, of course. Later, you need a schema change
class AddAcceptanceConfirmedAt < ActiveRecord::Migration
def change
add_column :users, :acceptance_confirmed_at, :datetime
end
end
class User < ActiveRecord::Base
before_save :do_something_with_acceptance_confirmed_at
end
For you, no problem. It runs perfectly. But if your coworker pulls both these today, not having run the first migration yet, he'll get this error on running the first migration:
rake aborted!
An error has occurred, this and all later migrations canceled:
undefined method `acceptance_confirmed_at=' for #<User:0x007f85902346d8>
That's not being a team player, he'll be fixing the bug you introduced. What should you have done?
This is a perfect example of the Using Models in Your Migrations
class ChangeFromPartnerAppliedToAppliedAt < ActiveRecord::Migration
class User < ActiveRecord::Base
end
def up
User.all.each do |user|
user.applied_at = user.partner_application_at
user.save
end
end
Edited after Mischa's comment
class ChangeFromPartnerAppliedToAppliedAt < ActiveRecord::Migration
class User < ActiveRecord::Base
end
def up
User.update_all('applied_at = partner_application_at')
end
end
Best practice is: don't use models in migrations. Migrations change the way AR maps, so do not use them at all. Do it all with SQL. This way it will always work.
This:
User.all.each do |user|
user.applied_at = user.partner_application_at
user.save
end
I would do like this
update "UPDATE users SET applied_at=partner_application_at"
Some times 'migrating data' could not be performed as a part of schema migration, like discussed above. Sometimes 'migrating data' means 'fix historical data inconstancies' or 'update your Solr/Elasticsearch' index, so its a complex task. For these kind of tasks, check out this gem https://github.com/OffgridElectric/rails-data-migrations
This gem was designed to decouple Rails schema migrations from data migrations, so it wont cause downtimes at deploy time and make it easy to manage in overall

Where is the right place to put _changed? methods in Rails?

I have a user model that checks to see if a value has changed before_save (by running User.zipcode_changed?). The idea is that this will queue up a delayed job if it has.
Problem is, when I migrate the app from scratch I get an error:
An error has occurred, all later migrations canceled:
undefined method `postcode_changed?' for #<User:0x105db6158>
Therefore, where should I be putting these? Is the model the wrong place?
When you checkout a new project from scratch, you shouldn't use migrations to build the database.
You should use rake db:schema:load instead.
Let me show you why.
Let's assume you create a new Post model with a post table on migration 10.
On migration 11, you execute some special elaborations on the Post model.
After a while, you decide to drop the Post model and the post table because no longer required.
Six month later, you checkout the project from scratch. If you try to run rake db:migrate the migration 11 will fail complaining about missing model. It's true, the model has been removed many month before and it's no longer available.
Instead, if you run rake db:schema:load you'll initialize the database with the right schema version.
Talking about migrations, if you just created the postcode method and you are trying to use the _changed? magic method in the same migration, you need to reload the schema before.
class MigrationFile < ...
self.up
add_column :user, :postcode, :string
User.all.each { |user| puts user.postcode_changed? } # will fail
User.reset_column_information
User.all.each { |user| puts user.postcode_changed? } # now it works
end
...
end
You say both zipcode_changed? and postcode_changed? in your question. What's the actual column name in your database - zipcode or postcode? ActiveRecord will only create the _changed? convenience method for the actual column name.
What you are doing is reasonable model code, but not so good in a migration.
Using model code in your migrations is problematic because of problems like this. I recommend sticking with SQL-oriented code.

Resources