Migrations from scratch breaking as column in model doesn't exist yet - ruby-on-rails

I have following models and association.
class Stock < ApplicationRecord
has_many :stock_components, -> {order(:position)}
...
before_save :set_default_values
def set_default_values
self.material_cost = 0.0 if self.material_cost.nil?
end
...
end
When I run migrations from scratch, a migration that inserts a record in this table, breaks at line self.material_cost = 0.0 if self.material_cost.nil? with following -
Caused by:
ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column 'stock_components.position' in 'order clause': SELECT `stocks`.* FROM `stocks` INNER JOIN `stock_components` ON `stocks`
.`id` = `stock_components`.`component_id` WHERE `stock_components`.`stock_id` = 1 ORDER BY `stock_components`.`position` ASC
This is because migration for adding position hasn't run yet.
What could be solution for this?

That's really unfortunate, but it's a common issue which is almost never addressed.
When a data migration on a model is written, either to change or set values, or to add data, typically one relies on the application model (with all the corresponding callbacks etc).
But then at a later time, more callbacks are added, and previous migrations are broken. Even worse, a later migration might rename or remove a column, and the inserted data is not valid anymore.
The solution is that you should NEVER use application models in a migration, but rather some local model that uses whatever is in the database at the time:
class PopulateStock < ActiveRecord::Migration
class Stock < ActiveRecord::Model
self.table_name = 'stocks'
end
def up
Stock.create(...)
end
end
Now the Stock model that is referenced in the migration is completely defined by the status of the table at the time of migration (no extra columns, no callbacks etc) instead of depending on all the application model.
Unfortunately this means that you have to go through all your app's migrations and create local models for them until migrations can be run.

Related

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

Rails: change database model on production enviroment

i need to convert some data, but not sure how to do it.
I have a professional model, which had an foreign key. We decided that wasn't enough and changed the "has many" to a HABTM model, but now in the production environment, i need to convert the data from the foo_id field to the professional_foo joint table.
The "add table" migration will be executed before the "drop column" one, but how should i set up a conversion knowing that i have databases in use that use the old form and i will have new setups of the system that will be made straight to the last code version and because that, wont need any conversion to be done. (initializes scripts on newest version are already fixed.
I recommend doing the conversion of data in the migration between the add table and the drop column. This migration should be run when the new code is deployed so the new code will work with the new data structure and new installations that don't have any data yet will just run the migration very fast since there won't be data to convert.
The migration would look something like:
class OldProfessional < ActiveRecord::Base
self.table_name = "professionals"
has_many :foos
end
class NewProfessional < ActiveRecord::Base
self.table_name = "professionals"
has_and_belongs_to_many :foos
end
class MigrateFoosToHasAndBelongsToMany < ActiveRecord::Migration
def up
OldProfessional.all.each do |old_pro|
new_pro = NewProfessinoal.find(old_pro.id)
old_pro.foos.each do |foo|
new_pro.foos << foo
end
new_pro.save!
end
end
def down
NewProfessional.all.each do |new_pro|
old_pro = OldProfessional.find(new_pro.id)
new_pro.foos.each do |foo|
old_pro.foos << foo
end
old_pro.save!
end
end
end

add a database column with Rails migration and populate it based on another column

I'm writing a migration to add a column to a table. The value of the column is dependent on the value of two more existing columns. What is the best/fastest way to do this?
Currently I have this but not sure if it's the best way since the groups table is can be very large.
class AddColorToGroup < ActiveRecord::Migration
def self.up
add_column :groups, :color, :string
Groups = Group.all.each do |g|
c = "red" if g.is_active && is_live
c = "green" if g.is_active
c = "orange"
g.update_attribute(:type, c)
end
end
def self.down
end
end
It's generally a bad idea to reference your models from your migrations like this. The problem is that the migrations run in order and change the database state as they go, but your models are not versioned at all. There's no guarantee that the model as it existed when the migration was written will still be compatible with the migration code in the future.
For example, if you change the behavior of the is_active or is_live attributes in the future, then this migration might break. This older migration is going to run first, against the new model code, and may fail. In your basic example here, it might not crop up, but this has burned me in deployment before when fields were added and validations couldn't run (I know your code is skipping validations, but in general this is a concern).
My favorite solution to this is to do all migrations of this sort using plain SQL. It looks like you've already considered that, so I'm going to assume you already know what to do there.
Another option, if you have some hairy business logic or just want the code to look more Railsy, is to include a basic version of the model as it exists when the migration is written in the migration file itself. For example, you could put this class in the migration file:
class Group < ActiveRecord::Base
end
In your case, that alone is probably sufficient to guarantee that the model will not break. Assuming active and live are boolean fields in the table at this time (and thus would be whenever this migration was run in the future), you won't need any more code at all. If you had more complex business logic, you could include it in this migration-specific version of model.
You might even consider copying whole methods from your model into the migration version. If you do that, bear in mind that you shouldn't reference any external models or libraries in your app from there, either, if there's any chance that they will change in the future. This includes gems and even possibly some core Ruby/Rails classes, because API-breaking changes in gems are very common (I'm looking at you, Rails 3.0, 3.1, and 3.2!).
I would highly suggest doing three total queries instead. Always leverage the database vs. looping over a bunch of items in an array. I would think something like this could work.
For the purposes of writing this, I'll assume is_active checks a field active where 1 is active. I'll assume live is the same as well.
Rails 3 approach
class AddColorToGroup < ActiveRecord::Migration
def self.up
add_column :groups, :color, :string
Group.where(active: 1, live: 1).update_all(type: "red")
Group.where(active: 1, live: 0).update_all(type: "green")
Group.where(active: 0, live: 0).update_all(type: "orange")
end
end
Feel free to review the documentation of update_all here.
Rails 2.x approach
class AddColorToGroup < ActiveRecord::Migration
def self.up
add_column :groups, :color, :string
Group.update_all("type = red", "active = 1 AND live = 1")
Group.update_all("type = red", "active = 1 AND live = 0")
Group.update_all("type = red", "active = 0 AND live = 0")
end
end
Rails 2 documentation
I would do this in a
after_create
# or
after_save
in your ActiveRecord model:
class Group < ActiveRecord::Base
attr_accessor :color
after_create :add_color
private
def add_color
self.color = #the color (wherever you get it from)
end
end
or in the migration you'd probably have to do some SQL like this:
execute('update groups set color = <another column>')
Here is an example in the Rails guides:
http://guides.rubyonrails.org/migrations.html#using-the-up-down-methods
In a similar situation I ended up adding the column using add_column and then using direct SQL to update the value of the column. I used direct SQL and not the model per Jim Stewart's answer, since then it doesn't depend on the current state of the model vs. the current state of the table based on migrations being run.
class AddColorToGroup < ActiveRecord::Migration
def up
add_column :groups, :color, :string
execute "update groups set color = case when is_active and is_live then 'red' when is_active then 'green' else 'orange' end"
end
def down
remove_column :groups, :color
end
end

Rails migration assumes a relationship where there is none

I have a Rails 3.1 app with a User model and a Venue model. These two models have a HABTM relationship - A user may manage many venues and a venue may be managed by many users.
I'd like users to be able to select a default venue so I'm trying to add a default_venue_id attribute to User with the following migration:
class AddDefaultVenueIdToUser < ActiveRecord::Migration
def self.up
add_column :users, :default_venue_id, :integer
end
def self.down
remove_column :users, :default_venue_id
end
end
The problem is that when I run that migration against my PostgreSQL database, it's assuming that default_venue_id is the foreign key for a relationship with the non-existent default_venues table and throws the following error:
PGError: ERROR: relation "default_venues" does not exist
: ALTER TABLE "users" ADD FOREIGN KEY ("default_venue_id") REFERENCES "default_venues" ("id")
Should I be doing something to tell the database that I'm not trying to create a relationship or am I going about this the wrong way?
Edit: I've just realised that another developer who worked on the project added the schema_plus gem which automatically defines constraints for columns ending in _id
That explains why I've never run into this behaviour before!
It would be helpful to see the Models as well to diagnose this problem.
However with that being said, I think that if you are using HABTM associations here it might be a good idea to take a look at a has many through relationship. example: VenueManagement which would have your user_id and venue_id. That way you could handle extra attributes on the association where it makes sense, like a default flag.
Hope that helps.
Since Rails seems to pick up on the _id part of the tag and tries linking it to a table, the easy solution to this is to try name it differently as convention says a field with _id links to a table.
An example of which could be
:default_id_for_venue, or just :defualt_venue

Rails model validators break earlier migrations

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

Resources