简体   繁体   中英

How to completely remove a model and its associations in Rails?

Description

I'm on Ruby version 2.6.6 and Ruby on Rails version 6.0.3.2 .

Models

  1. Book
  2. Author

Associations

A Book belongs to an Author.
An Author has many Books.

Goal

Remove the Author model and add it as a column to a Book (of type string).

What I've Done

I created and ran 3 migrations, in the following order:

AddAuthorToBooks

class AddAuthorToBooks < ActiveRecord::Migration[6.0]
  def change
    add_column :books, :author, :string
  end
end

DropAuthors

class DropAuthors < ActiveRecord::Migration[6.0]
  def change
    drop_table :authors do |t|
      t.string "full_name", null: false
      t.timestamps null: false
    end
  end
end

RemoveAuthorForeignKeyFromBooks

class RemoveAuthorForeignKeyFromBooks < ActiveRecord::Migration[6.0]
  def change
    remove_foreign_key :books, :authors
  end
end

Schema

Unfortunately, I don't have the schema before I ran the migrations. (I tried checking out an older commit, but the schema file stubbornly refuses to change.)

Here is the current version:

ActiveRecord::Schema.define(version: 2020_08_11_125724) do

  create_table "books", force: :cascade do |t|
    t.string "title"
    t.text "description"
    t.string "cover_url"
    t.decimal "price"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.integer "author_id", null: false
    t.string "author"
    t.index ["author_id"], name: "index_books_on_author_id"
  end

  create_table "books_genres", id: false, force: :cascade do |t|
    t.integer "book_id", null: false
    t.integer "genre_id", null: false
    t.index ["book_id", "genre_id"], name: "index_books_genres_on_book_id_and_genre_id"
    t.index ["genre_id", "book_id"], name: "index_books_genres_on_genre_id_and_book_id"
  end

  create_table "genres", force: :cascade do |t|
    t.string "name"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

  create_table "reviews", force: :cascade do |t|
    t.string "username"
    t.decimal "rating"
    t.text "body"
    t.integer "book_id", null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["book_id"], name: "index_reviews_on_book_id"
  end

  add_foreign_key "reviews", "books"
end

Problem

The author table has been removed, the add_foreign_key "books", "authors" (I think that's how it went) is gone, too, but the author_id stubbornly remains in the books table.

Furthermore, there's an integer author_id and an index of the same name.

What I'm Planning

I thought of just deleting these 2 columns with another migration, but I don't know if that would...

  1. ...fix the issue and erase the old Author model completely,
  2. ...and that it's the clean/recommended way of doing things. If needed, I could roll back the migrations and try a better method.

About the unusual `schema.rb` behavior

I tried checking out an older commit, but the schema file stubbornly refuses to change.

This is quite unusual. Do verify that you're tracking the db/schema.rb file using git. If it is tracked, there's no reason why checking out an older commit shouldn't return it to the older state. At that point, you should be able to:

$ rails db:drop
$ rails db:create
$ rails db:schema:load

...to load the old schema into the database. Then, you should be able to return to the latest code with git, and run pending migrations after the date at which the older schema was created.

About a cleaner way to implement this

Before writing the below migration, the first step would be to remove any existing relationship written in the Book class. For example:

# app/models/book.rb
class Book < ApplicationRecord
  # The line below should be deleted! Otherwise, it will probably interfere
  # with the `book.update!(author: ...)` line in the migration.
  belongs_to :author
end

I've taken to writing related migrations in a single file, since they're all related. To me, this looks like:

class MoveAuthorToBooks < ActiveRecord::Migration[6.0]
  class Author < ApplicationRecord
  end

  class Book < ApplicationRecord
  end

  def up
    # Start by adding a string column.
    add_column :books, :author, :string

    # Let's preserve existing author names.
    Book.all.each do |book|
      author = Author.find(book.author_id)
      book.update!(author: author.name)
    end

    # Now that the names have been moved to the books table, we don't
    # need the relationship to `authors` table anymore. This should
    # also delete any related foreign keys - manual foreign key deletion
    # should not be required.
    remove_column :books, :author_id

    # Alternative: If you'd created the `authors_id` column using the
    # `add_reference` command, then it's probably best to use the opposite
    # `remove_reference` command.
    #
    #remove_reference :books, :author, index: true, foreign_key: true


    # Finally, remove the `authors` table.
    drop_table :authors 
  end

  def down
    # This can be technically be reversed, but that'll need some more code that
    # reverses the action of the `up` function, and it may not be needed.

    raise ActiveRecord::IrreversibleMigration
  end
end

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM