Rename an ActiveRecord model
Choosing a good name for a class or a variable isn’t easy at all and sometimes there is a requirement to rename existing tables and models to match the updated product audience.
So how we should do this migration then:
1. Ask why
The first step is to ask why should we rename the model, with a productive mindset and a good set of questions you will be surprised that in most cases a renaming is not required at all!
2. Change the model name
It seems that this case requires to rename the ActiveRecord model, so let’s use the following example:
# app/models/author.rb
class Author < ApplicationRecord
has_many :books
end
# app/models/book.rb
class Book < ApplicationRecord
belongs_to :author
end
# db/schema.rb
ActiveRecord::Schema.define(version: 2018_06_09_111650) do
create_table "authors", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "books", force: :cascade do |t|
t.string "title"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "author_id"
t.index ["author_id"], name: "index_books_on_author_id"
end
end
As we can see we have an Author model who has many Books and our requirement is to rename Author model to Writer.
We start by renaming the Author model file and mapping the new model to the existing authors table. Also, we need to specify the foreign key for the association to be able to access associated books.
# app/models/author.rb -> app/models/writer.rb
class Writer < ApplicationRecord
self.table_name = "authors"
has_many :books, foreign_key: 'author_id'
end
Let’s update the Book model by specifying the existent foreign key. This step is important to maintain the belongs_to association.
class Book < ApplicationRecord
belongs_to :writer, foreign_key: 'author_id'
end
I guess that you are asking why should we bother with this step instead of renaming the models and tables at the same time.
To answer this good question, we should consider how applications look like in production. Usually, models may have different associations and used in different parts of the application like controllers and services. That’s why we do this process step by step to be sure that our application is still working as expected before changing the tables and columns in the database.
3. Run the test suite
At this point, we must run our test suite and be sure all specs are passing before moving to the last step. This step is very important to be sure that we didn’t break an existent feature during the migration.
4. Rename DB tables and columns
Now we are ready for the last step, so let’s write some migrations. We start by renaming the authors’ table to writers.
class RenameAuthorsToWriters < ActiveRecord::Migration[5.2]
def change
rename_table :authors, :writers
end
end
Then we need to rename the foreign key from author_id to writer_id.
class RenameFkInBooks < ActiveRecord::Migration[5.2]
def change
rename_column :books, :author_id, :writer_id
end
end
And finally, we clean up our models.
# app/models/writer.rb
class Writer < ApplicationRecord
has_many :books
end
# app/models/book.rb
class Book < ApplicationRecord
belongs_to :writer
end
Here is the updated schema, we can check that renaming the column took care of renaming the index. But we should keep in mind this only valid for Rails version >= 4.0 as stated in this changelog:
In Rails 4.0 when a column or a table is renamed the related indexes are also renamed. If you have migrations which rename the indexes, they are no longer needed.
ActiveRecord::Schema.define(version: 2018_06_09_115643) do
create_table "books", force: :cascade do |t|
t.string "title"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "writer_id"
t.index ["writer_id"], name: "index_books_on_writer_id"
end
create_table "writers", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
That’s it, have fun and keep coding 🙂