How to add a column with Rails
Posted December 15, 2021 - tagged ruby-on-rails
6 min read
How to add a database column with Ruby-on-Rails ? Here is the short, and the long answer.
Let's say you have a table named "books", and an associated model named "Book".
You want to add the column "author".
Tools used in this tutorial : Rails 6.1.3, Ruby 3
Note that this article should work pretty well with any version of Rails or Ruby.
For those who already know Rails, here is the short answer :
class AddAuthorToBooks < ActiveRecord::Migration[6.1] def change add_column :books, :author, :string end end
And then run
bin/rails db:migrate at the root of your project.
Install new minimal app
For this tutorial we don't need a full Rails app with bells and whistles, the bare minimum will suffice. From Rails 6.1, the --minimal flag is available when you create a new Rails app.
$> rails _6.1.3_ new myapp --minimal $> cd myapp
By default, Rails will use an in-memory database named SQLite3.
Side note If you want to start with PostgreSQL, you can enter the following command :
$> rails _6.1.3_ new myapp --minimal --database=postgresql $> cd myapp
Create the database
$/myapp> bin/rails db:create Created database 'myapp_development' Created database 'myapp_test'
Create a first model
$/myapp> bin/rails generate model Book title:string body:text
Note that "bin/rails" will use the installed rails binary for the current application, instead of the globally available one.
This will create many files :
invoke active_record create db/migrate/20210331122059_create_books.rb create app/models/book.rb invoke test_unit create test/models/book_test.rb create test/fixtures/books.yml
You may not need everything. The interesting thing is the migration file created under
db/migrate/20210131201925_create_books.rb (The timestamp represent the current time when file was created, of course you will have another than this example)
class CreateBooks < ActiveRecord::Migration[6.1] def change create_table :books do |t| t.string :title t.text :body t.timestamps end end end
Then run the migration
$/myapp> bin/rails db:migrate == 20210131201925 CreateBooks: migrating ====================================== -- create_table(:books) -> 0.0019s == 20210131201925 CreateBooks: migrated (0.0020s) =============================
Add a migration file
Create a file named db/migrate/20210131201926_add_column_to_books.rb
Check the timestamp, it has to be a greater number than the one of 20210131201925_create_books.rb previously created. Or the migrations won't be able to be run in the correct order.
class AddColumnToBooks < ActiveRecord::Migration[6.1] def change add_column :books, :author, :string end end
Migration file analyzed
We've already seen the timestamp. Now the name of the migration : it actually could be anything. But try to make the intent clear, for yourself or other developers : here I put
add_column_to_books, a better name could have been
add_author_to_books. Anyway, make sure that the filename (apart from the timestamp) is the same as the class name.
Now we inherit from ActiveRecord::Migration, for pretty obvious reasons : we want to migrate the data, which is a pretty common behavior, common enough to be hidden inside a standard Rails object, ActiveRecord::Migration.
The number indicates the 2 first numbers of the Rails version. Here I use Rails 6.1.3, thus the two numbers are [6.1]. It's because features could be available in ActiveRecord::Migration[6.1] that were not available in ActiveRecord::Migration[6.0].
Then the "change" method. You cannot (ahem) change the name, because this method is automagically called when you run
add_column is pretty clear : first arg is the name of the table, then the name of the column, and then the kind of column.
Migrate the data
bin/rails db:migrate Will actually add the column to your current database.
$/myapp> bin/rails db:migrate == 20210131201926 AddColumnToBooks: migrating ================================= -- add_column(:books, :author, :string) -> 0.0021s == 20210131201926 AddColumnToBooks: migrated (0.0022s) ========================
Check the schema.rb file
You can see that the column has been added inside your schema.rb file here :
ActiveRecord::Schema.define(version: 20210131201926) do create_table "books", force: :cascade do |t| t.string "title" t.text "body" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false t.string "author" end end
The added column appear here :
The migration "appears" in another place : the timestamp. Look at
ActiveRecord::Schema.define(version: 20210131201926) . The timestamp is the one of your migration files.
Bonus : Inspect created tables - if PostgreSQL is used
If you have used the default SQLite3, please read the next paragraph. If you have chosen PostgreSQL, this is the right paragraph :)
With Rails, you can inspect the actual database by entering the command :
$/myapp> bin/rails db psql (13.1) Type "help" for help. myapp_development=#
Great ! You just entered the PostgreSQL command-line interface (CLI).
myapp_development=# \dt List of relations Schema | Name | Type | Owner --------+----------------------+-------+------- public | ar_internal_metadata | table | shino public | books | table | shino public | schema_migrations | table | shino (3 rows)
Great ! As you may notice, Rails creates 2 tables by default : ar_internal_metadata, and schema_migrations.
We are only concerned by the "books" table.
Let's display it :
Bonus : Inspect created tables - if SQLite3 is used
This is the same paragraph as above, but the command will apply to a SQLite3 database only (which is the default one with Rails)
$/myapp> bin/rails db SQLite version 3.28.0 2019-04-15 14:49:49 Enter ".help" for usage hints. sqlite>
Then, to display tables
sqlite> .headers on sqlite> .mode columns sqlite> .table ar_internal_metadata books schema_migrations
Then, to display the internals of the "Books" table :
sqlite> PRAGMA table_info('books'); cid name type notnull dflt_value pk ---------- ---------- ---------- ---------- ---------- ---------- 0 id integer 1 1 1 title varchar 0 0 2 body text 0 0 3 created_at datetime(6 1 0 4 updated_at datetime(6 1 0 5 author varchar 0 0
The magic way : generate migration
Another possibility is to use the scaffolding, but in this case you have to pay extra attention to the naming of your file :
$/myapp> bin/rails generate migration AddAuthorToBooks author:string
$/myapp> bin/rails generate migration add_author_to_books author:string
And the migration file will be filled automagically with all correct values, timestamp included.
In everyday work, I don't use this magic. I simply copy/paste other migrations, paying attention to the name of the file, and that's it.
Options for add_column
You can see a full list of all available options in this article : https://api.rubyonrails.org/classes/ActiveRecord/Migration.html
Adding a column with Rails is easy, I would say the important thing is to know what's going on, and why.