New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for if_exists/if_not_exists
on remove_column/add_column
#38352
Add support for if_exists/if_not_exists
on remove_column/add_column
#38352
Conversation
455ae9d
to
d93578c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool, my comments are more if there's some extra stuff we want to look into.
``` | ||
class AddColumnTitle < ActiveRecord::Migration[6.1] | ||
def change | ||
add_column :posts, :title, :string, if_not_exists: true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we do something in the case where title exists, but it happens to be a text
, then rolling back the existing column's removed and then migrating again, the column would now be a string
? Though I suppose that's what you want and later migrations is the newest source of truth. Though it could be potentially confusing and happen silently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call. Turns out column_exists?
takes a type. I've updated this to still raise if you pass a different type and added a test for that case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Woot 🙌
``` | ||
class RemoveColumnTitle < ActiveRecord::Migration[6.1] | ||
def change | ||
remove_column :posts, :title, if_exists: true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's nice that both these options correspond to their SQL counterparts, but the sorta tailing if yet just an option reads odd to me. I find that I have to read both add_column
and remove_column
multiple times and sort of migrate/rollback the migration in my head. I wonder if there's something like this could work:
add_column :posts, :title, :string, skip_when_migrated: true
remove_column :posts, :title, skip_when_migrated: true
I don't care so much that add_column
requires if_not_exists
while if_exists
is only for remove_column
, it's more about describing what happens in case the migration is needless. Maybe only_when_required: true
or only_when_needed: true
. Maybe suppress_error: true
as another option.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm following the existing if_exists/if_not_exists that is used by create and drop table and I don't think they should be different because that's really confusing for users.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't love if_exists / if_not_exists but I definitely don't want create and drop to have one set of options and add/remove column to have totally different options. We could change the create/drop table but that creates a whole deprecation cycle.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah doh, I tried searching for if_not_exists
on api.rubyonrails.org and came back blank. Sorry about the derailing, all good from here 🙏
This PR adds support for `if_exists` on `remove_column` and `if_not_exists` on `add_column` to support silently ignoring migrations if the remove tries to remove a non-existent column or an add tries to add an already existing column. We (GitHub) have custom monkey-patched support for these features and would like to upstream this behavior. This matches the same behavior that is supported for `create_table` and `drop_table`. The behavior for sqlite is different from mysql/postgres and sqlite for remove column and that is reflected in the tests.
d93578c
to
95ed7e7
Compare
def remove_column(table_name, column_name, type = nil, **options) | ||
return if options[:if_exists] == true && !column_exists?(table_name, column_name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we pass the type into the exists check here too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about this and tried it out and there's an issue with passing type. For add_column
the type is required, you have to pass it so we can use that to check if the column_exists?
.
However, in remove_column
it's allowed but completely ignored by the actual column dropper. For remove_column
since type is ignored by the execute we don't need to pass type
there because the column will be removed regardless of type so we don't want to protect on nil or type. Does that make sense? I feel like I'm rambling...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Basically doesn't matter what you pass for type, remove column will always remove the column because type is ignored.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it, perfect 👌
This PR allows for passing `if_exists` options to the `remove_index` method so that we can ignore already removed indexes. This work follows column `if/if_not_exists` from rails#38352 and `:if_not_exists` on `add_index` from rails#38555. We've found this useful at GitHub, there are migrations where we don't want to raise if an index was already removed. This will allow us to remove a monkey patch on `remove_index`. I considered raising after the `index_name_for_remove` method is called but that method will raise if the index doesn't exist before we get to execute. I have a commit that refactors this but after much consideration this change is cleaner and more straightforward than other ways of implementing this. This change also adds a little extra validation to the `add_index` test. Fix `nodoc` on edited methods.
Hey, For example, I have:
During the initial migration, it will check if the column exists and creates a column. Most probably I'm missing something, but I can ask 🙂 |
If you're using this you should define |
This PR adds support for
if_exists
onremove_column
andif_not_exists
onadd_column
to support silently ignoring migrationsif the remove tries to remove a non-existent column or an add tries to
add an already existing column.
We (GitHub) have custom monkey-patched support for these features and
would like to upstream this behavior.
This matches the same behavior that is supported for
create_table
anddrop_table
. The behavior for sqlite is different from mysql/postgresand sqlite for remove column and that is reflected in the tests.
cc/ @rafaelfranca @tenderlove @jhawthorn