2

TL;DR;

In the past (a few years ago now), I have used custom generators within Ruby to change how things are created when say you run rails g model Users. My question today is whether or not you can change how Rails generates a migration file.

What I am trying to do

Whenever I generate a migration through one of Rails' generation functions, I almost always have to go in and make changes; and usually to the same thing. If not to add in a def up and def down for reverting purposes; I almost always need to change the placement of the t.timestamps. I like to have my timestamps to be at the beginning of a table that way if I ever decide to add additional columns to a table (and let's face it, that always seems to happen as a project grows), the timestamps are not stuck in the middle of the table.

Example

Let's say, for ease of example, that I run rails g scaffold Notes body:text

This generates many files and the following migration document:

class CreateNotes < ActiveRecord::Migration[7.0]
  def change
    create_table :notes do |t|
      t.text    :body

      t.timestamps
    end
  end
end

with the following schema after db:migrate is run:

create_table "notes", force: :cascade do |t|
    t.text "body"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
end

Now, if say later on we want to add the ability to have a title to a note. So we run rails g migration AddTitleToNotes title:string which give us this migration:

class AddTitleToNotes < ActiveRecord::Migration[7.0]
  def change
    add_column :notes, :title, :string
  end
end

And, when db:migrate is run this time, our schema is updated to look like this:

create_table "notes", force: :cascade do |t|
    t.text "body"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.string "title"
end

Now, this small example may not seem too bad, but when you start off when a table of 7 columns and then add 3 columns later (No reason I came up with those numbers, lol), you get the timestamps at column 9 and 10 (because of the first column of id is always there) and the new columns at 11-13.

This may just be a "me" problem, but I like to keep my schema's easy to read, and same with the database. So, in this scenario, I would go into the first migration file and change it to:

class CreateNotes < ActiveRecord::Migration[7.0]
  def change
    create_table :notes do |t|
      t.timestamps

      t.text    :body
    end
  end
end

That way the resulting schema would look like:

create_table "notes", force: :cascade do |t|
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.text "body"
end

and after adding the 'title' later, the schema would look like:

create_table "notes", force: :cascade do |t|
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.text "body"
    t.string "title"
end

QUESTION

So, my question again is, can I change how Rails creates the migration file to always put t.timestamps at the beginning of the migration file right after the create_table clause?

Bonus Question to save my sanity

Can I also change how Rails updates the schema to have it stop adding the "helper" text that is commented out at the top of the schema every time I run a new migration? (Again, may be a me problem, but I like a clean schema, what can I say?)

"Helper" text at the top of the schema.rb file

# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# This file is the source Rails uses to define your schema when running `bin/rails
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
# be faster and is potentially less error prone than running all of your
# migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code.
#
# It's strongly recommended that you check this file into your version control system.
CWarrington
  • 659
  • 5
  • 12

1 Answers1

4

This is untested, but from poking around in the Rails source and tests...

  1. Add a migrations_path for your database.yml.
  2. Copy the default create table template into that path.
  3. Edit the template to your liking.

For example:

        default: &default
          adapter: sqlite3
          pool: 5
          timeout: 5000
        development:
          <<: *default
          database: storage/development.sqlite3
          migrations_paths:
            - db/migration_templates

However, the order of columns in a table is (almost) entirely inconsequential. You ultimately cannot control the exact ordering of your columns in a production database; future schema changes will result in "disordered" tables, or you may have to work with an existing production database.

I'd recommend instead just getting used to it. Flexibility is a very useful skill to have.

Schwern
  • 153,029
  • 25
  • 195
  • 336
  • 2
    Good answer. You can also override the templates simply by placing your code in the `/lib/templates` folder. I think in this case the file path should be something like `/lib/template/rails/generators/active_record/migration/templates/create_table_migration.rb.tt`. It's [briefly mentioned](https://guides.rubyonrails.org/generators.html#customizing-your-workflow-by-changing-generators-templates) in the guides but I can't find any simple documentation on how the paths are derived. – max Mar 20 '23 at 10:35
  • Thank you, I will test this answer today and see if that works. I definitely agree that flexibility is a great skill to have. And luckily I am not too OCD that it bothers me too much. But, if I can keep it orderly, I do my best to. Thank you again for the answers. I will test both and get back to you. :-) – CWarrington Mar 20 '23 at 17:18